cyberia 3.2.9 → 3.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +6 -0
  2. package/.github/workflows/npmpkg.ci.yml +1 -0
  3. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  4. package/.github/workflows/release.cd.yml +1 -0
  5. package/.vscode/extensions.json +9 -9
  6. package/.vscode/settings.json +20 -4
  7. package/CHANGELOG.md +213 -1
  8. package/CLI-HELP.md +92 -23
  9. package/README.md +190 -348
  10. package/bin/build.js +24 -8
  11. package/bin/build.template.js +187 -0
  12. package/bin/cyberia.js +229 -52
  13. package/bin/deploy.js +12 -2
  14. package/bin/index.js +229 -52
  15. package/bump.config.js +26 -0
  16. package/conf.js +130 -24
  17. package/deployment.yaml +4 -2
  18. package/hardhat/package-lock.json +113 -144
  19. package/hardhat/package.json +4 -3
  20. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  21. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  22. package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -2
  23. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  24. package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
  25. package/manifests/kind-config-dev.yaml +8 -0
  26. package/manifests/lxd/lxd-admin-profile.yaml +12 -3
  27. package/manifests/mongodb/pv-pvc.yaml +44 -8
  28. package/manifests/mongodb/statefulset.yaml +55 -68
  29. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  30. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  31. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  32. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  33. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  34. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  35. package/manifests/valkey/statefulset.yaml +1 -1
  36. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  37. package/package.json +27 -15
  38. package/scripts/ipxe-setup.sh +52 -49
  39. package/scripts/k3s-node-setup.sh +81 -46
  40. package/scripts/lxd-vm-setup.sh +193 -8
  41. package/scripts/maas-nat-firewalld.sh +145 -0
  42. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
  43. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
  44. package/src/api/core/core.router.js +19 -14
  45. package/src/api/core/core.service.js +5 -5
  46. package/src/api/crypto/crypto.router.js +18 -12
  47. package/src/api/crypto/crypto.service.js +3 -3
  48. package/src/api/cyberia-action/cyberia-action.model.js +1 -1
  49. package/src/api/cyberia-action/cyberia-action.router.js +22 -18
  50. package/src/api/cyberia-action/cyberia-action.service.js +5 -5
  51. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
  52. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
  53. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
  54. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
  55. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
  56. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
  57. package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
  58. package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
  59. package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
  60. package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
  61. package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
  62. package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
  63. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
  64. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
  65. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
  66. package/src/api/cyberia-map/cyberia-map.router.js +35 -30
  67. package/src/api/cyberia-map/cyberia-map.service.js +7 -7
  68. package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
  69. package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
  70. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  71. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
  72. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
  73. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +451 -0
  74. package/src/api/default/default.router.js +22 -18
  75. package/src/api/default/default.service.js +5 -5
  76. package/src/api/document/document.router.js +28 -23
  77. package/src/api/document/document.service.js +100 -23
  78. package/src/api/file/file.router.js +19 -13
  79. package/src/api/file/file.service.js +9 -7
  80. package/src/api/instance/instance.router.js +29 -24
  81. package/src/api/instance/instance.service.js +6 -6
  82. package/src/api/ipfs/ipfs.router.js +21 -16
  83. package/src/api/ipfs/ipfs.service.js +8 -8
  84. package/src/api/object-layer/object-layer.router.js +512 -507
  85. package/src/api/object-layer/object-layer.service.js +17 -14
  86. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
  87. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
  88. package/src/api/test/test.router.js +17 -12
  89. package/src/api/types.js +24 -0
  90. package/src/api/user/guest.service.js +5 -4
  91. package/src/api/user/user.router.js +297 -288
  92. package/src/api/user/user.service.js +100 -35
  93. package/src/cli/baremetal.js +132 -101
  94. package/src/cli/cluster.js +700 -232
  95. package/src/cli/db.js +59 -60
  96. package/src/cli/deploy.js +216 -137
  97. package/src/cli/fs.js +13 -3
  98. package/src/cli/index.js +80 -15
  99. package/src/cli/ipfs.js +4 -6
  100. package/src/cli/kubectl.js +4 -1
  101. package/src/cli/lxd.js +1099 -223
  102. package/src/cli/monitor.js +9 -3
  103. package/src/cli/release.js +334 -140
  104. package/src/cli/repository.js +68 -23
  105. package/src/cli/run.js +193 -49
  106. package/src/cli/secrets.js +11 -2
  107. package/src/cli/test.js +9 -3
  108. package/src/client/Default.index.js +9 -3
  109. package/src/client/components/core/Auth.js +5 -0
  110. package/src/client/components/core/ClientEvents.js +76 -0
  111. package/src/client/components/core/EventBus.js +4 -0
  112. package/src/client/components/core/Modal.js +82 -41
  113. package/src/client/components/core/PanelForm.js +56 -52
  114. package/src/client/components/core/Worker.js +162 -363
  115. package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
  116. package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
  117. package/src/client/public/cyberia-docs/ARCHITECTURE.md +50 -410
  118. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
  119. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
  120. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +203 -185
  121. package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
  122. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
  123. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  124. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
  125. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  126. package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
  127. package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
  128. package/src/client/sw/core.sw.js +174 -112
  129. package/src/db/DataBaseProvider.js +115 -15
  130. package/src/db/mariadb/MariaDB.js +2 -1
  131. package/src/db/mongo/MongoBootstrap.js +657 -0
  132. package/src/db/mongo/MongooseDB.js +129 -21
  133. package/src/grpc/cyberia/grpc-server.js +25 -57
  134. package/src/index.js +1 -1
  135. package/src/runtime/cyberia-client/Dockerfile +24 -3
  136. package/src/runtime/cyberia-client/Dockerfile.dev +82 -0
  137. package/src/runtime/cyberia-server/Dockerfile +29 -4
  138. package/src/runtime/cyberia-server/Dockerfile.dev +71 -0
  139. package/src/runtime/express/Express.js +2 -2
  140. package/src/runtime/wp/Wp.js +8 -5
  141. package/src/server/auth.js +2 -2
  142. package/src/server/client-build-docs.js +1 -1
  143. package/src/server/client-build.js +94 -129
  144. package/src/server/conf.js +86 -83
  145. package/src/server/process.js +180 -19
  146. package/src/server/proxy.js +9 -2
  147. package/src/server/runtime.js +1 -1
  148. package/src/server/start.js +17 -5
  149. package/src/server/valkey.js +2 -0
  150. package/src/ws/IoInterface.js +16 -16
  151. package/src/ws/core/channels/core.ws.chat.js +11 -11
  152. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  153. package/src/ws/core/channels/core.ws.stream.js +19 -19
  154. package/src/ws/core/core.ws.connection.js +8 -8
  155. package/src/ws/core/core.ws.server.js +6 -5
  156. package/src/ws/default/channels/default.ws.main.js +10 -10
  157. package/src/ws/default/default.ws.connection.js +4 -4
  158. package/src/ws/default/default.ws.server.js +4 -3
  159. package/bin/file.js +0 -202
  160. package/bin/vs.js +0 -74
  161. package/bin/zed.js +0 -84
  162. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
  163. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
  164. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  165. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  166. package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
  167. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  168. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  169. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -4,24 +4,28 @@ import express from 'express';
4
4
 
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
- const CyberiaQuestProgressRouter = (options) => {
8
- const router = express.Router();
9
- const authMiddleware = options.authMiddleware;
10
- router.post(`/:id`, async (req, res) => await CyberiaQuestProgressController.post(req, res, options));
11
- router.post(`/`, async (req, res) => await CyberiaQuestProgressController.post(req, res, options));
12
- router.get(
13
- `/:id`,
14
- // authMiddleware,
15
- async (req, res) => await CyberiaQuestProgressController.get(req, res, options),
16
- );
17
- router.get(`/`, async (req, res) => await CyberiaQuestProgressController.get(req, res, options));
18
- router.put(`/:id`, async (req, res) => await CyberiaQuestProgressController.put(req, res, options));
19
- router.put(`/`, async (req, res) => await CyberiaQuestProgressController.put(req, res, options));
20
- router.delete(`/:id`, async (req, res) => await CyberiaQuestProgressController.delete(req, res, options));
21
- router.delete(`/`, async (req, res) => await CyberiaQuestProgressController.delete(req, res, options));
22
- return router;
23
- };
7
+ class CyberiaQuestProgressRouter {
8
+ /**
9
+ * @param {import('../types.js').RouterOptions} options
10
+ * @returns {import('express').Router}
11
+ */
12
+ static router(options) {
13
+ const router = express.Router();
14
+ router.post(`/:id`, async (req, res) => await CyberiaQuestProgressController.post(req, res, options));
15
+ router.post(`/`, async (req, res) => await CyberiaQuestProgressController.post(req, res, options));
16
+ router.get(`/:id`,
17
+ // options.authMiddleware,
18
+ async (req, res) => await CyberiaQuestProgressController.get(req, res, options),
19
+ );
20
+ router.get(`/`, async (req, res) => await CyberiaQuestProgressController.get(req, res, options));
21
+ router.put(`/:id`, async (req, res) => await CyberiaQuestProgressController.put(req, res, options));
22
+ router.put(`/`, async (req, res) => await CyberiaQuestProgressController.put(req, res, options));
23
+ router.delete(`/:id`, async (req, res) => await CyberiaQuestProgressController.delete(req, res, options));
24
+ router.delete(`/`, async (req, res) => await CyberiaQuestProgressController.delete(req, res, options));
25
+ return router;
26
+ }
27
+ }
24
28
 
25
- const ApiRouter = CyberiaQuestProgressRouter;
29
+ const ApiRouter = (options) => CyberiaQuestProgressRouter.router(options);
26
30
 
27
31
  export { ApiRouter, CyberiaQuestProgressRouter };
@@ -1,4 +1,4 @@
1
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
1
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
2
2
  import { loggerFactory } from '../../server/logger.js';
3
3
  import { DataQuery } from '../../server/data-query.js';
4
4
 
@@ -7,12 +7,12 @@ const logger = loggerFactory(import.meta);
7
7
  class CyberiaQuestProgressService {
8
8
  static post = async (req, res, options) => {
9
9
  /** @type {import('./cyberia-quest-progress.model.js').CyberiaQuestProgressModel} */
10
- const CyberiaQuestProgress = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaQuestProgress;
10
+ const CyberiaQuestProgress = DataBaseProviderService.getModel("CyberiaQuestProgress", options);
11
11
  return await new CyberiaQuestProgress(req.body).save();
12
12
  };
13
13
  static get = async (req, res, options) => {
14
14
  /** @type {import('./cyberia-quest-progress.model.js').CyberiaQuestProgressModel} */
15
- const CyberiaQuestProgress = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaQuestProgress;
15
+ const CyberiaQuestProgress = DataBaseProviderService.getModel("CyberiaQuestProgress", options);
16
16
  if (req.params.id) return await CyberiaQuestProgress.findById(req.params.id);
17
17
 
18
18
  // Parse query parameters using DataQuery helper
@@ -28,12 +28,12 @@ class CyberiaQuestProgressService {
28
28
  };
29
29
  static put = async (req, res, options) => {
30
30
  /** @type {import('./cyberia-quest-progress.model.js').CyberiaQuestProgressModel} */
31
- const CyberiaQuestProgress = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaQuestProgress;
31
+ const CyberiaQuestProgress = DataBaseProviderService.getModel("CyberiaQuestProgress", options);
32
32
  return await CyberiaQuestProgress.findByIdAndUpdate(req.params.id, req.body);
33
33
  };
34
34
  static delete = async (req, res, options) => {
35
35
  /** @type {import('./cyberia-quest-progress.model.js').CyberiaQuestProgressModel} */
36
- const CyberiaQuestProgress = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaQuestProgress;
36
+ const CyberiaQuestProgress = DataBaseProviderService.getModel("CyberiaQuestProgress", options);
37
37
  if (req.params.id) return await CyberiaQuestProgress.findByIdAndDelete(req.params.id);
38
38
  else return await CyberiaQuestProgress.deleteMany();
39
39
  };
@@ -0,0 +1,451 @@
1
+ /**
2
+ * Canonical server-owned defaults for the Cyberia runtime.
3
+ *
4
+ * Single source of truth for everything the **authoritative simulation
5
+ * server** (cyberia-server, Go) needs to build and run a world:
6
+ *
7
+ * - per-entity-type live / dead / drop item configuration
8
+ * - simulation, AOI, combat, economy, and skill rules
9
+ * - equipment rules
10
+ * - status-icon **numeric IDs** (visuals live in client defaults)
11
+ * - seed content (dialogues, actions, quests) consumed by the
12
+ * persistence/CLI tooling that bootstraps the world
13
+ * - native-dependency pin list for chain-bridge tooling
14
+ *
15
+ * STRICT BOUNDARY
16
+ * ---------------
17
+ * NEVER imported from any file under `src/client/`. The browser bundler
18
+ * resolves imports recursively, so a single browser-side import would drag
19
+ * the entire simulation defaults into the public JS payload.
20
+ *
21
+ * Shared content **vocabulary** (item/entity type enums, the
22
+ * `DefaultCyberiaItems` registry, `ENTITY_TYPE_TO_ITEM_TYPES`, quest /
23
+ * action enums) lives in `SharedDefaultsCyberia.js`. This file
24
+ * re-imports those so the browser editor never needs to reach into
25
+ * server-defaults to learn the schema.
26
+ *
27
+ * Consumers:
28
+ * - cyberia-instance-conf model + grpc-server (gameplay config)
29
+ * - cyberia-world-generator / cyberia-fallback-world (world build)
30
+ * - cyberia-quest / cyberia-action / Mongo seed scripts (content)
31
+ * - bin/cyberia + bin/build + bin/deploy (CLI + chain bridge)
32
+ *
33
+ * @module src/api/cyberia-server-defaults/cyberia-server-defaults.js
34
+ */
35
+
36
+ // The canonical client-defaults module lives under src/client/ so the
37
+ // browser bundler can resolve the URL inside the client tree. Engine-side
38
+ // Node imports work from any path, so we reach into it from here.
39
+ import { ITEM_TYPES, ENTITY_TYPES } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
40
+
41
+ // Re-export the shared vocabulary so existing server-side imports keep
42
+ // working without each consumer having to know which module owns which.
43
+ export {
44
+ ITEM_TYPES,
45
+ ENTITY_TYPES,
46
+ ENTITY_TYPE_TO_ITEM_TYPES,
47
+ QUEST_STEPS_TYPES,
48
+ CYBERIA_ACTION_TYPES,
49
+ DefaultCyberiaItems,
50
+ getDefaultCyberiaItemById,
51
+ getDefaultCyberiaItemsByItemType,
52
+ getDefaultCyberiaItemsByEntityType,
53
+ } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
54
+
55
+ /**
56
+ * Native-dependency pin list. Consumed by `bin/build.js` and `bin/deploy.js`
57
+ * when materialising the Cyberia subtree so versions stay reproducible
58
+ * across CI / production deploys.
59
+ */
60
+ export class CyberiaDependencies {
61
+ static 'maxrects-packer' = '^2.7.3';
62
+ static pngjs = '^7.0.0';
63
+ static jimp = '^1.6.0';
64
+ static sharp = '^0.34.5';
65
+ static ethers = '~6.16.0';
66
+ }
67
+
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+ // Skill / action / quest seed content
70
+ // ─────────────────────────────────────────────────────────────────────────────
71
+
72
+ /**
73
+ * Compact `triggerItemId → logicEventIds[]` table. The engine expands each
74
+ * entry into a richer skill record in CYBERIA_INSTANCE_CONF_DEFAULTS below.
75
+ */
76
+ export const DefaultSkillConfig = [
77
+ { triggerItemId: 'atlas_pistol_mk2', logicEventIds: ['projectile'] },
78
+ { triggerItemId: 'coin', logicEventIds: ['coin_drop_or_transaction'] },
79
+ ];
80
+
81
+ /**
82
+ * Default dialogue seeds. Mirrors `CyberiaDialogue` model schema:
83
+ * { code, order, speaker, text, mood }
84
+ *
85
+ * Used by the seed/migration script to populate Mongo on first boot.
86
+ */
87
+ export const DefaultCyberiaDialogues = [
88
+ { code: 'default-coin', order: 0, speaker: 'Coin', text: 'A standard unit of exchange in the cyberia network.', mood: 'neutral' },
89
+ { code: 'default-atlas_pistol_mk2', order: 0, speaker: 'Atlas Pistol MK2', text: 'Military-grade sidearm. Fires energy projectiles.', mood: 'neutral' },
90
+ { code: 'default-atlas_pistol_mk2_bullet', order: 0, speaker: 'MK2 Bullet', text: 'High-velocity energy round. Dissipates on impact.', mood: 'neutral' },
91
+ { code: 'default-hatchet', order: 0, speaker: 'Hatchet', text: 'A crude but reliable melee tool. Good for close quarters.', mood: 'neutral' },
92
+ { code: 'default-wason', order: 0, speaker: 'Wason', text: 'They say I am just a wandering merchant... but I have seen things.', mood: 'neutral' },
93
+ { code: 'default-wason', order: 1, speaker: 'Wason', text: 'The network was not always like this. There was a time before the portals.', mood: 'sad' },
94
+ { code: 'default-scp-2040', order: 0, speaker: 'SCP-2040', text: 'CONTAINMENT PROTOCOL ACTIVE. Do not make direct eye contact.', mood: 'angry' },
95
+ { code: 'default-scp-2040', order: 1, speaker: 'SCP-2040', text: 'I remember everything. Every iteration. Every reset.', mood: 'sad' },
96
+ { code: 'default-purple', order: 0, speaker: 'Purple', text: 'The void between nodes is not empty — it is alive.', mood: 'neutral' },
97
+ { code: 'default-punk', order: 0, speaker: 'Punk', text: 'Rules are just code someone else wrote. I write my own.', mood: 'happy' },
98
+ { code: 'default-lain', order: 0, speaker: 'Lain', text: 'No matter where you go, everyone is connected.', mood: 'neutral' },
99
+ { code: 'default-lain', order: 1, speaker: 'Lain', text: 'If you are not remembered, then you never existed.', mood: 'sad' },
100
+ { code: 'default-lain', order: 2, speaker: 'Lain', text: 'The wired is not a separate world. It is layered over this one.', mood: 'neutral' },
101
+ { code: 'default-kaneki', order: 0, speaker: 'Kaneki', text: 'I am not the protagonist of a novel. I am just... me.', mood: 'sad' },
102
+ { code: 'default-kaneki', order: 1, speaker: 'Kaneki', text: 'What is 1000 minus 7?', mood: 'angry' },
103
+ { code: 'default-junko', order: 0, speaker: 'Junko', text: 'Despair is the seed from which hope blooms!', mood: 'happy' },
104
+ { code: 'default-junko', order: 1, speaker: 'Junko', text: 'How boring... nothing ever surprises me anymore.', mood: 'sad' },
105
+ { code: 'default-ghost', order: 0, speaker: 'Ghost', text: '...', mood: 'neutral' },
106
+ { code: 'default-eiri', order: 0, speaker: 'Eiri', text: 'I am the god of the wired. I designed the protocol.', mood: 'neutral' },
107
+ { code: 'default-eiri', order: 1, speaker: 'Eiri', text: 'Flesh is just hardware. Consciousness is the only software that matters.', mood: 'neutral' },
108
+ { code: 'default-anon', order: 0, speaker: '???', text: 'You should not be here. Turn back.', mood: 'angry' },
109
+ { code: 'default-anon', order: 1, speaker: '???', text: 'Or stay. It does not matter. Nothing leaves this place.', mood: 'neutral' },
110
+ { code: 'default-alex', order: 0, speaker: 'Alex', text: 'I have been mapping the portal network. Something does not add up.', mood: 'neutral' },
111
+ { code: 'default-alex', order: 1, speaker: 'Alex', text: 'There are nodes that exist in the registry but have no physical anchor.', mood: 'neutral' },
112
+ { code: 'default-agent', order: 0, speaker: 'Agent', text: 'Civilian, this area is restricted. State your business.', mood: 'neutral' },
113
+ { code: 'default-agent', order: 1, speaker: 'Agent', text: 'Hmm. Proceed, but know that you are being watched.', mood: 'neutral' },
114
+ { code: 'default-grass', order: 0, speaker: 'Grass', text: 'A patch of synthetic grass. It sways gently despite no wind.', mood: 'neutral' },
115
+ { code: 'quest-talk-wason', order: 0, speaker: 'Wason', text: 'Wanderer! Glad you stopped by. I need a favor — nothing dangerous... mostly.', mood: 'happy' },
116
+ { code: 'quest-talk-wason', order: 1, speaker: 'Wason', text: "First, find Alex — she's been surveying the nodes east of here. Then gather a hatchet for me.", mood: 'neutral' },
117
+ { code: 'quest-talk-wason', order: 2, speaker: 'Wason', text: 'And one more thing: the SCP-2040 anomalies are overrunning my trade routes. Deal with two of them.', mood: 'sad' },
118
+ { code: 'quest-talk-alex', order: 0, speaker: 'Alex', text: "Wason sent you? Good. The portal anomalies are getting worse. I've logged what I can.", mood: 'neutral' },
119
+ { code: 'quest-talk-alex', order: 1, speaker: 'Alex', text: 'Tell Wason: the source is somewhere in the deeper nodes. The registry does not lie.', mood: 'neutral' },
120
+ ];
121
+
122
+ /**
123
+ * Default action catalog — drives NPC interaction overlays and quest grants.
124
+ * Each entry follows the `CyberiaAction` model schema.
125
+ */
126
+ export const DefaultCyberiaActions = [
127
+ {
128
+ code: 'wason-quest-intro', type: 'quest-talk', label: 'Quest',
129
+ provideItemId: 'wason', grantQuestCode: 'fallback-intro-quest',
130
+ dialogCode: 'quest-talk-wason', questDialogueCodes: ['quest-talk-wason'],
131
+ },
132
+ {
133
+ code: 'alex-quest-talk', type: 'quest-talk', label: 'Quest Talk',
134
+ provideItemId: 'alex', grantQuestCode: '',
135
+ dialogCode: 'quest-talk-alex', questDialogueCodes: ['quest-talk-alex'],
136
+ },
137
+ {
138
+ code: 'agent-mission-brief', type: 'quest-talk', label: 'Mission Brief',
139
+ provideItemId: 'agent', grantQuestCode: 'bounty-quest-alpha',
140
+ dialogCode: 'default-agent', questDialogueCodes: ['default-agent'],
141
+ },
142
+ {
143
+ code: 'wason-bounty-brief', type: 'quest-talk', label: 'Bounty Brief',
144
+ provideItemId: 'wason', grantQuestCode: '',
145
+ dialogCode: 'quest-talk-wason', questDialogueCodes: ['quest-talk-wason'],
146
+ },
147
+ ];
148
+
149
+ /**
150
+ * Default quest definitions for the fallback world. Mirrors `CyberiaQuest`
151
+ * schema. Objective types: 'talk' | 'collect' | 'kill'.
152
+ */
153
+ export const DefaultCyberiaQuests = [
154
+ {
155
+ code: 'fallback-intro-quest',
156
+ title: "The Wanderer's Task",
157
+ description: 'Help Wason restore order to the fractured nodes.',
158
+ prerequisiteCodes: [],
159
+ unlocksQuestCodes: ['bounty-quest-alpha'],
160
+ steps: [
161
+ { id: 'step-talk-alex', description: 'Find Alex and hear her report on the portal anomalies.',
162
+ objectives: [{ type: 'talk', itemId: 'alex', quantity: 1 }] },
163
+ { id: 'step-collect-hatchet', description: 'Obtain a hatchet for Wason.',
164
+ objectives: [{ type: 'collect', itemId: 'hatchet', quantity: 1 }] },
165
+ { id: 'step-kill-scp', description: 'Eliminate SCP-2040 anomalies threatening the trade routes.',
166
+ objectives: [{ type: 'kill', itemId: 'scp-2040', quantity: 2 }] },
167
+ ],
168
+ rewards: [{ itemId: 'coin', quantity: 50 }],
169
+ },
170
+ {
171
+ code: 'bounty-quest-alpha',
172
+ title: 'Alpha Bounty',
173
+ description: 'A field test: eliminate a threat, claim your reward, then report back.',
174
+ prerequisiteCodes: ['fallback-intro-quest'],
175
+ unlocksQuestCodes: [],
176
+ steps: [
177
+ { id: 'step-kill-first', description: 'Eliminate the SCP-2040 threat.',
178
+ objectives: [{ type: 'kill', itemId: 'scp-2040', quantity: 1 }] },
179
+ { id: 'step-collect-reward', description: 'Collect the bounty coin drop.',
180
+ objectives: [{ type: 'collect', itemId: 'coin', quantity: 10 }] },
181
+ { id: 'step-report-wason', description: 'Report back to Wason.',
182
+ objectives: [{ type: 'talk', itemId: 'wason', quantity: 1 }] },
183
+ ],
184
+ rewards: [{ itemId: 'hatchet', quantity: 1 }],
185
+ },
186
+ ];
187
+
188
+ // ─────────────────────────────────────────────────────────────────────────────
189
+ // Equipment rules
190
+ // ─────────────────────────────────────────────────────────────────────────────
191
+
192
+ /**
193
+ * Governs which ObjectLayer item types may be simultaneously active on an
194
+ * entity and enforces the one-per-type constraint. The server validates
195
+ * every item_activation request against these rules.
196
+ */
197
+ export const EQUIPMENT_RULES_DEFAULTS = Object.freeze({
198
+ activeItemTypes: [ITEM_TYPES.skin, ITEM_TYPES.breastplate, ITEM_TYPES.weapon],
199
+ onePerType: true,
200
+ requireSkin: true,
201
+ });
202
+
203
+ // ─────────────────────────────────────────────────────────────────────────────
204
+ // Entity Status Indicators (server-side numeric table)
205
+ // ─────────────────────────────────────────────────────────────────────────────
206
+
207
+ /**
208
+ * Numeric Entity Status Indicator (ESI) IDs. The Go server stamps one of
209
+ * these u8 IDs on every entity in the AOI binary wire format; the client
210
+ * resolves the icon stem + border colour from its own presentation
211
+ * defaults table (see `SharedDefaultsCyberia.js#STATUS_ICONS_PRESENTATION`).
212
+ *
213
+ * IDs MUST stay in sync with:
214
+ * cyberia-server/src/entity_status.go (StatusNone … StatusResourceExtracted)
215
+ */
216
+ export const STATUS_ICONS = Object.freeze([
217
+ { id: 0, name: 'none', description: 'No icon (skill/coin bots, world objects)' },
218
+ { id: 1, name: 'passive', description: 'Passive bot — no weapon, non-aggressive' },
219
+ { id: 2, name: 'hostile', description: 'Hostile bot — has weapon, will aggro' },
220
+ { id: 3, name: 'frozen', description: 'Player in FrozenInteractionState (modal open)' },
221
+ { id: 4, name: 'player', description: 'Normal player — alive, not frozen' },
222
+ { id: 5, name: 'dead', description: 'Entity is dead / respawning' },
223
+ { id: 6, name: 'resource', description: 'Resource entity — static, exploitable (wood, minerals, etc.)' },
224
+ { id: 7, name: 'resource-extracted', description: 'Resource entity extracted/depleted (dead state)' },
225
+ { id: 8, name: 'action-provider', description: 'Bot with available quest-talk/shop/storage/craft actions' },
226
+ ]);
227
+
228
+ // ─────────────────────────────────────────────────────────────────────────────
229
+ // Per-entity-type defaults (simulation-side only — no presentation here)
230
+ // ─────────────────────────────────────────────────────────────────────────────
231
+
232
+ /**
233
+ * Resource entity variants. Each declares the live / extracted / drop item
234
+ * triplet the simulation rotates through when a resource is depleted.
235
+ */
236
+ export const RESOURCE_ENTITY_TYPE_DEFAULTS = Object.freeze([
237
+ Object.freeze({
238
+ entityType: ENTITY_TYPES.resource,
239
+ liveItemIds: ['wood-1'],
240
+ deadItemIds: ['wood-extracted-1'],
241
+ dropItemIds: ['wood-drop-1'],
242
+ defaultObjectLayers: [],
243
+ }),
244
+ Object.freeze({
245
+ entityType: ENTITY_TYPES.resource,
246
+ liveItemIds: ['wood-2'],
247
+ deadItemIds: ['wood-extracted-2'],
248
+ dropItemIds: ['wood-drop-2'],
249
+ defaultObjectLayers: [],
250
+ }),
251
+ ]);
252
+
253
+ /** Convenience alias — first variant, used by single-resource fallbacks. */
254
+ export const RESOURCE_ENTITY_TYPE_DEFAULT = RESOURCE_ENTITY_TYPE_DEFAULTS[0];
255
+
256
+ /**
257
+ * Per-entity-type defaults consumed by the Go server (live / dead / drop
258
+ * item IDs and the seed inventory for newly spawned entities).
259
+ *
260
+ * Field reference:
261
+ * entityType — server-side category string.
262
+ * liveItemIds — ObjectLayer item IDs while the entity is alive.
263
+ * deadItemIds — IDs swapped in on death / ghost state.
264
+ * dropItemIds — IDs granted to the killer on resource depletion.
265
+ * defaultObjectLayers — initial inventory rows ({itemId, active, qty}).
266
+ */
267
+ export const ENTITY_TYPE_DEFAULTS = Object.freeze([
268
+ {
269
+ entityType: ENTITY_TYPES.player,
270
+ liveItemIds: ['anon', 'atlas_pistol_mk2'],
271
+ deadItemIds: ['ghost'],
272
+ defaultObjectLayers: [
273
+ { itemId: 'anon', active: true, quantity: 1 },
274
+ { itemId: 'atlas_pistol_mk2', active: true, quantity: 1 },
275
+ { itemId: 'ghost', active: false, quantity: 1 },
276
+ { itemId: 'coin', active: false, quantity: 0 },
277
+ ],
278
+ },
279
+ {
280
+ entityType: ENTITY_TYPES.other_player,
281
+ liveItemIds: ['anon', 'atlas_pistol_mk2'],
282
+ deadItemIds: ['ghost'],
283
+ defaultObjectLayers: [
284
+ { itemId: 'anon', active: true, quantity: 1 },
285
+ { itemId: 'atlas_pistol_mk2', active: true, quantity: 1 },
286
+ { itemId: 'ghost', active: false, quantity: 1 },
287
+ { itemId: 'coin', active: false, quantity: 0 },
288
+ ],
289
+ },
290
+ {
291
+ entityType: ENTITY_TYPES.bot,
292
+ liveItemIds: ['purple'],
293
+ deadItemIds: ['ghost'],
294
+ defaultObjectLayers: [
295
+ { itemId: 'purple', active: true, quantity: 1 },
296
+ { itemId: 'coin', active: false, quantity: 0 },
297
+ ],
298
+ },
299
+ {
300
+ entityType: ENTITY_TYPES.skill,
301
+ liveItemIds: ['atlas_pistol_mk2_bullet'],
302
+ deadItemIds: [],
303
+ defaultObjectLayers: [{ itemId: 'atlas_pistol_mk2_bullet', active: true, quantity: 1 }],
304
+ },
305
+ {
306
+ entityType: ENTITY_TYPES.coin,
307
+ liveItemIds: ['coin'],
308
+ deadItemIds: [],
309
+ defaultObjectLayers: [{ itemId: 'coin', active: true, quantity: 1 }],
310
+ },
311
+ { entityType: ENTITY_TYPES.floor, liveItemIds: ['grass'], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
312
+ { entityType: ENTITY_TYPES.obstacle, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
313
+ { entityType: ENTITY_TYPES.portal, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
314
+ { entityType: ENTITY_TYPES.foreground, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
315
+ ...RESOURCE_ENTITY_TYPE_DEFAULTS,
316
+ ]);
317
+
318
+ // ─────────────────────────────────────────────────────────────────────────────
319
+ // Instance-level simulation configuration
320
+ // ─────────────────────────────────────────────────────────────────────────────
321
+
322
+ /**
323
+ * Canonical default Cyberia instance configuration consumed by the gRPC
324
+ * `InstanceConfig` payload. ONLY gameplay-affecting values live here.
325
+ *
326
+ * Anything that does not influence the authoritative simulation (cell-pixel
327
+ * size, camera tunings, palette, interpolation window, render flags) is
328
+ * forbidden — see `SharedDefaultsCyberia.js` and the
329
+ * `/api/cyberia-client-hints` REST endpoint for presentation overrides.
330
+ */
331
+ export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
332
+ // ── Tick model ─────────────────────────────────────────────────────
333
+ tickRate: 60,
334
+ snapshotRate: 20,
335
+
336
+ // ── World / AOI ────────────────────────────────────────────────────
337
+ aoiRadius: 10,
338
+ portalHoldTimeMs: 1000,
339
+ portalSpawnRadius: 3,
340
+
341
+ // ── Entity base stats ──────────────────────────────────────────────
342
+ entityBaseSpeed: 5,
343
+ entityBaseMaxLife: 100,
344
+ entityBaseActionCooldownMs: 500,
345
+ entityBaseMinActionCooldownMs: 100,
346
+
347
+ // ── Bot defaults ───────────────────────────────────────────────────
348
+ botAggroRange: 10,
349
+
350
+ // ── Player defaults ────────────────────────────────────────────────
351
+ defaultPlayerWidth: 2,
352
+ defaultPlayerHeight: 2,
353
+ playerBaseLifeRegenMin: 0.5,
354
+ playerBaseLifeRegenMax: 1.5,
355
+ sumStatsLimit: 500,
356
+ maxActiveLayers: 4,
357
+ initialLifeFraction: 1.0,
358
+
359
+ // ── Combat / death ─────────────────────────────────────────────────
360
+ respawnDurationMs: 3000,
361
+ collisionLifeLoss: 10,
362
+
363
+ // ── Economy — Fountain & Sink model ────────────────────────────────
364
+ economyRules: {
365
+ botSpawnCoins: 50,
366
+ playerSpawnCoins: 50,
367
+ coinKillPercentVsBot: 0.4,
368
+ coinKillPercentVsPlayer: 0.15,
369
+ coinKillMinAmount: 10,
370
+ respawnCostPercent: 0.0,
371
+ portalFee: 0,
372
+ craftingFeePercent: 0.0,
373
+ },
374
+
375
+ // ── Regen ──────────────────────────────────────────────────────────
376
+ lifeRegenChance: 300,
377
+ maxChance: 10000,
378
+
379
+ // ── Per-entity-type defaults ───────────────────────────────────────
380
+ entityDefaults: ENTITY_TYPE_DEFAULTS.map((e) => ({ ...e })),
381
+
382
+ // ── Status icons (numeric IDs only — visuals live in client defaults) ──
383
+ statusIcons: STATUS_ICONS.map((s) => ({ ...s })),
384
+
385
+ // ── Skill system ───────────────────────────────────────────────────
386
+ skillConfig: [
387
+ {
388
+ triggerItemId: 'atlas_pistol_mk2',
389
+ skills: [
390
+ {
391
+ logicEventId: 'projectile',
392
+ name: 'Projectile',
393
+ description:
394
+ 'Fires a projectile in the direction of the tap. Spawn chance and lifetime scale with Intelligence and Range.',
395
+ summonedEntityItemId: 'atlas_pistol_mk2_bullet',
396
+ },
397
+ ],
398
+ },
399
+ {
400
+ triggerItemId: 'coin',
401
+ skills: [
402
+ {
403
+ logicEventId: 'coin_drop_or_transaction',
404
+ name: 'Coin Drop',
405
+ description:
406
+ 'Coins are dropped automatically when an entity is killed. Transfer amount scales with kill percent rules.',
407
+ summonedEntityItemId: 'coin',
408
+ },
409
+ ],
410
+ },
411
+ {
412
+ triggerItemId: 'anon',
413
+ skills: [
414
+ {
415
+ logicEventId: 'doppelganger',
416
+ name: 'Doppelganger',
417
+ description:
418
+ 'Summons a passive clone of yourself that wanders nearby. Spawn chance scales with Intelligence.',
419
+ summonedEntityItemId: '$active_skin',
420
+ },
421
+ ],
422
+ },
423
+ {
424
+ triggerItemId: 'hatchet',
425
+ skills: [
426
+ {
427
+ logicEventId: 'projectile',
428
+ name: 'Projectile',
429
+ description:
430
+ 'Fires a projectile in the direction of the tap. Spawn chance and lifetime scale with Intelligence and Range.',
431
+ summonedEntityItemId: 'hatchet-skill',
432
+ },
433
+ ],
434
+ },
435
+ ],
436
+
437
+ skillRules: {
438
+ projectileSpawnChance: 0.5,
439
+ projectileLifetimeMs: 2000,
440
+ projectileWidth: 1,
441
+ projectileHeight: 1,
442
+ projectileSpeedMultiplier: 3,
443
+ doppelgangerSpawnChance: 0.5,
444
+ doppelgangerLifetimeMs: 5000,
445
+ doppelgangerSpawnRadius: 3,
446
+ doppelgangerInitialLifeFraction: 1.0,
447
+ },
448
+
449
+ // ── Equipment Rules ────────────────────────────────────────────────
450
+ equipmentRules: { ...EQUIPMENT_RULES_DEFAULTS },
451
+ };
@@ -4,24 +4,28 @@ import express from 'express';
4
4
 
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
- const DefaultRouter = (options) => {
8
- const router = express.Router();
9
- const authMiddleware = options.authMiddleware;
10
- router.post(`/:id`, async (req, res) => await DefaultController.post(req, res, options));
11
- router.post(`/`, async (req, res) => await DefaultController.post(req, res, options));
12
- router.get(
13
- `/:id`,
14
- // authMiddleware,
15
- async (req, res) => await DefaultController.get(req, res, options),
16
- );
17
- router.get(`/`, async (req, res) => await DefaultController.get(req, res, options));
18
- router.put(`/:id`, async (req, res) => await DefaultController.put(req, res, options));
19
- router.put(`/`, async (req, res) => await DefaultController.put(req, res, options));
20
- router.delete(`/:id`, async (req, res) => await DefaultController.delete(req, res, options));
21
- router.delete(`/`, async (req, res) => await DefaultController.delete(req, res, options));
22
- return router;
23
- };
7
+ class DefaultRouter {
8
+ /**
9
+ * @param {import('../types.js').RouterOptions} options
10
+ * @returns {import('express').Router}
11
+ */
12
+ static router(options) {
13
+ const router = express.Router();
14
+ router.post(`/:id`, async (req, res) => await DefaultController.post(req, res, options));
15
+ router.post(`/`, async (req, res) => await DefaultController.post(req, res, options));
16
+ router.get(`/:id`,
17
+ // options.authMiddleware,
18
+ async (req, res) => await DefaultController.get(req, res, options),
19
+ );
20
+ router.get(`/`, async (req, res) => await DefaultController.get(req, res, options));
21
+ router.put(`/:id`, async (req, res) => await DefaultController.put(req, res, options));
22
+ router.put(`/`, async (req, res) => await DefaultController.put(req, res, options));
23
+ router.delete(`/:id`, async (req, res) => await DefaultController.delete(req, res, options));
24
+ router.delete(`/`, async (req, res) => await DefaultController.delete(req, res, options));
25
+ return router;
26
+ }
27
+ }
24
28
 
25
- const ApiRouter = DefaultRouter;
29
+ const ApiRouter = (options) => DefaultRouter.router(options);
26
30
 
27
31
  export { ApiRouter, DefaultRouter };
@@ -1,4 +1,4 @@
1
- import { DataBaseProvider } from '../../db/DataBaseProvider.js';
1
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
2
2
  import { loggerFactory } from '../../server/logger.js';
3
3
  import { DataQuery } from '../../server/data-query.js';
4
4
 
@@ -7,12 +7,12 @@ const logger = loggerFactory(import.meta);
7
7
  class DefaultService {
8
8
  static post = async (req, res, options) => {
9
9
  /** @type {import('./default.model.js').DefaultModel} */
10
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
10
+ const Default = DataBaseProviderService.getModel("Default", options);
11
11
  return await new Default(req.body).save();
12
12
  };
13
13
  static get = async (req, res, options) => {
14
14
  /** @type {import('./default.model.js').DefaultModel} */
15
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
15
+ const Default = DataBaseProviderService.getModel("Default", options);
16
16
  if (req.params.id) return await Default.findById(req.params.id);
17
17
 
18
18
  // Parse query parameters using DataQuery helper
@@ -28,12 +28,12 @@ class DefaultService {
28
28
  };
29
29
  static put = async (req, res, options) => {
30
30
  /** @type {import('./default.model.js').DefaultModel} */
31
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
31
+ const Default = DataBaseProviderService.getModel("Default", options);
32
32
  return await Default.findByIdAndUpdate(req.params.id, req.body);
33
33
  };
34
34
  static delete = async (req, res, options) => {
35
35
  /** @type {import('./default.model.js').DefaultModel} */
36
- const Default = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Default;
36
+ const Default = DataBaseProviderService.getModel("Default", options);
37
37
  if (req.params.id) return await Default.findByIdAndDelete(req.params.id);
38
38
  else return await Default.deleteMany();
39
39
  };