cyberia 3.2.9 → 3.2.22

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 (184) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +7 -0
  2. package/.github/workflows/engine-cyberia.ci.yml +14 -2
  3. package/.github/workflows/ghpkg.ci.yml +1 -0
  4. package/.github/workflows/npmpkg.ci.yml +10 -5
  5. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  6. package/.github/workflows/release.cd.yml +1 -0
  7. package/.vscode/extensions.json +9 -9
  8. package/.vscode/settings.json +20 -4
  9. package/CHANGELOG.md +363 -1
  10. package/CLI-HELP.md +975 -1061
  11. package/README.md +190 -348
  12. package/bin/build.js +102 -125
  13. package/bin/build.template.js +33 -0
  14. package/bin/cyberia.js +238 -56
  15. package/bin/deploy.js +16 -3
  16. package/bin/index.js +238 -56
  17. package/bump.config.js +26 -0
  18. package/conf.js +131 -24
  19. package/deployment.yaml +76 -2
  20. package/hardhat/package-lock.json +113 -144
  21. package/hardhat/package.json +4 -3
  22. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  23. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  24. package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
  25. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  26. package/manifests/kind-config-dev.yaml +8 -0
  27. package/manifests/lxd/lxd-admin-profile.yaml +12 -3
  28. package/manifests/mongodb/pv-pvc.yaml +44 -8
  29. package/manifests/mongodb/statefulset.yaml +55 -68
  30. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  31. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  32. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  33. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  34. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  35. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  36. package/manifests/valkey/statefulset.yaml +1 -1
  37. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  38. package/package.json +31 -19
  39. package/scripts/ipxe-setup.sh +52 -49
  40. package/scripts/k3s-node-setup.sh +81 -46
  41. package/scripts/link-local-underpost-cli.sh +6 -0
  42. package/scripts/lxd-vm-setup.sh +193 -8
  43. package/scripts/maas-nat-firewalld.sh +145 -0
  44. package/scripts/test-monitor.sh +250 -0
  45. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
  46. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
  47. package/src/api/core/core.router.js +19 -14
  48. package/src/api/core/core.service.js +5 -5
  49. package/src/api/crypto/crypto.router.js +18 -12
  50. package/src/api/crypto/crypto.service.js +3 -3
  51. package/src/api/cyberia-action/cyberia-action.model.js +1 -1
  52. package/src/api/cyberia-action/cyberia-action.router.js +22 -18
  53. package/src/api/cyberia-action/cyberia-action.service.js +5 -5
  54. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
  55. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
  56. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
  57. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
  58. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
  59. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
  60. package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
  61. package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
  62. package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
  63. package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
  64. package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
  65. package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
  69. package/src/api/cyberia-map/cyberia-map.router.js +35 -30
  70. package/src/api/cyberia-map/cyberia-map.service.js +7 -7
  71. package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
  72. package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
  73. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  74. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
  75. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
  76. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +458 -0
  77. package/src/api/default/default.router.js +22 -18
  78. package/src/api/default/default.service.js +5 -5
  79. package/src/api/document/document.router.js +28 -23
  80. package/src/api/document/document.service.js +100 -23
  81. package/src/api/file/file.router.js +19 -13
  82. package/src/api/file/file.service.js +9 -7
  83. package/src/api/instance/instance.router.js +29 -24
  84. package/src/api/instance/instance.service.js +6 -6
  85. package/src/api/ipfs/ipfs.router.js +21 -16
  86. package/src/api/ipfs/ipfs.service.js +8 -8
  87. package/src/api/object-layer/object-layer.router.js +512 -507
  88. package/src/api/object-layer/object-layer.service.js +17 -14
  89. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
  90. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
  91. package/src/api/test/test.router.js +17 -12
  92. package/src/api/types.js +24 -0
  93. package/src/api/user/guest.service.js +5 -4
  94. package/src/api/user/user.router.js +297 -288
  95. package/src/api/user/user.service.js +100 -35
  96. package/src/cli/baremetal.js +132 -101
  97. package/src/cli/cluster.js +700 -232
  98. package/src/cli/db.js +59 -60
  99. package/src/cli/deploy.js +291 -294
  100. package/src/cli/env.js +1 -4
  101. package/src/cli/fs.js +13 -3
  102. package/src/cli/image.js +58 -4
  103. package/src/cli/index.js +127 -15
  104. package/src/cli/ipfs.js +4 -6
  105. package/src/cli/kubectl.js +4 -1
  106. package/src/cli/lxd.js +1099 -223
  107. package/src/cli/monitor.js +396 -9
  108. package/src/cli/release.js +355 -146
  109. package/src/cli/repository.js +169 -30
  110. package/src/cli/run.js +347 -117
  111. package/src/cli/secrets.js +11 -2
  112. package/src/cli/test.js +9 -3
  113. package/src/client/Default.index.js +9 -3
  114. package/src/client/components/core/Auth.js +5 -0
  115. package/src/client/components/core/ClientEvents.js +76 -0
  116. package/src/client/components/core/EventBus.js +4 -0
  117. package/src/client/components/core/Modal.js +82 -41
  118. package/src/client/components/core/PanelForm.js +14 -10
  119. package/src/client/components/core/Worker.js +162 -363
  120. package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
  121. package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
  122. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +55 -1
  123. package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
  124. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
  125. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
  126. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +212 -185
  127. package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
  128. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
  129. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +23 -1
  130. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  131. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
  132. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  133. package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
  134. package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
  135. package/src/client/sw/core.sw.js +174 -112
  136. package/src/db/DataBaseProvider.js +115 -15
  137. package/src/db/mariadb/MariaDB.js +2 -1
  138. package/src/db/mongo/MongoBootstrap.js +657 -0
  139. package/src/db/mongo/MongooseDB.js +130 -21
  140. package/src/grpc/cyberia/grpc-server.js +25 -57
  141. package/src/index.js +1 -1
  142. package/src/runtime/cyberia-client/Dockerfile +10 -7
  143. package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
  144. package/src/runtime/cyberia-server/Dockerfile +11 -6
  145. package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
  146. package/src/runtime/express/Express.js +2 -2
  147. package/src/runtime/wp/Dockerfile +3 -3
  148. package/src/runtime/wp/Wp.js +8 -5
  149. package/src/server/auth.js +2 -2
  150. package/src/server/catalog-underpost.js +61 -0
  151. package/src/server/catalog.js +77 -0
  152. package/src/server/client-build-docs.js +1 -1
  153. package/src/server/client-build.js +94 -129
  154. package/src/server/conf.js +496 -135
  155. package/src/server/ipfs-client.js +5 -3
  156. package/src/server/process.js +180 -19
  157. package/src/server/proxy.js +9 -2
  158. package/src/server/runtime-status.js +235 -0
  159. package/src/server/runtime.js +1 -1
  160. package/src/server/start.js +44 -11
  161. package/src/server/valkey.js +2 -0
  162. package/src/ws/IoInterface.js +16 -16
  163. package/src/ws/core/channels/core.ws.chat.js +11 -11
  164. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  165. package/src/ws/core/channels/core.ws.stream.js +19 -19
  166. package/src/ws/core/core.ws.connection.js +8 -8
  167. package/src/ws/core/core.ws.server.js +6 -5
  168. package/src/ws/default/channels/default.ws.main.js +10 -10
  169. package/src/ws/default/default.ws.connection.js +4 -4
  170. package/src/ws/default/default.ws.server.js +4 -3
  171. package/test/deploy-monitor.test.js +251 -0
  172. package/bin/file.js +0 -202
  173. package/bin/vs.js +0 -74
  174. package/bin/zed.js +0 -84
  175. package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
  176. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  177. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
  178. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
  179. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  180. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  181. package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
  182. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  183. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  184. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @module src/api/cyberia-client-hints
3
+ *
4
+ * Client presentation hints — read-only REST endpoint.
5
+ *
6
+ * Purpose
7
+ * -------
8
+ * The cyberia-server (Go authoritative simulation) and the WS init payload
9
+ * carry *only* simulation contracts. Client-render policy (palette, camera
10
+ * defaults, status-icon visuals, interpolation window, dev-overlay flag)
11
+ * lives off the simulation path entirely.
12
+ *
13
+ * This endpoint exposes optional per-instance overrides of those values.
14
+ * The client is required to function with no calls to this endpoint at
15
+ * all (it ships built-in defaults that match the canonical engine
16
+ * defaults 1-to-1 — see cyberia-client/src/domain/presentation_defaults.h).
17
+ *
18
+ * Endpoint
19
+ * --------
20
+ * GET /api/cyberia-client-hints/:instanceCode
21
+ * -> 200 { palette, entityColorKeys, statusIcons, cameraSmoothing,
22
+ * cameraZoom, defaultWidthScreenFactor,
23
+ * defaultHeightScreenFactor, interpolationMs, devUi }
24
+ * -> 404 if no instance with that code exists in the database — the
25
+ * client falls back to its built-in defaults on 404 (this is
26
+ * the normal path for stateless servers / fresh deployments).
27
+ *
28
+ * GET /api/cyberia-client-hints/
29
+ * -> 200 canonical defaults — same shape as above, no DB read.
30
+ *
31
+ * What it intentionally does NOT do
32
+ * ---------------------------------
33
+ * - It does not touch gameplay state (no entity, map, economy, skill,
34
+ * equipment, or stat fields).
35
+ * - It is not consumed by cyberia-server; the Go process never calls
36
+ * this endpoint.
37
+ * - It is not authenticated. Presentation hints are not secret.
38
+ */
39
+
40
+ import express from 'express';
41
+ import { loggerFactory } from '../../server/logger.js';
42
+ import { CYBERIA_CLIENT_HINTS_DEFAULTS } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
43
+ import { resolveClientHints } from './cyberia-client-hints.service.js';
44
+
45
+ const logger = loggerFactory(import.meta);
46
+
47
+ class CyberiaClientHintsRouter {
48
+ /**
49
+ * @param {import('../types.js').RouterOptions} options
50
+ * @returns {import('express').Router}
51
+ */
52
+ static router(options) {
53
+ const router = express.Router();
54
+
55
+ // GET /:code -> resolved hints.
56
+ // Resolution order is documented in cyberia-client-hints.service.js:
57
+ // 1. In-memory TTL cache.
58
+ // 2. CyberiaClientHints collection (preferred).
59
+ // 3. Legacy presentation fields on CyberiaInstanceConf (back-compat).
60
+ // 4. Canonical client defaults (never cached so a later DB insert wins).
61
+ router.get('/:code', async (req, res) => {
62
+ try {
63
+ if (req && req.headers && req.headers.origin) {
64
+ res.set('Access-Control-Allow-Origin', req.headers.origin);
65
+ } else res.setHeader('Access-Control-Allow-Origin', '*');
66
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
67
+ const { data, source } = await resolveClientHints(req.params.code, {
68
+ host: options.host || 'default',
69
+ path: options.path || '/',
70
+ });
71
+ // Surface the resolution source as a non-authoritative header so
72
+ // operators can see whether the runtime fetched from the new
73
+ // collection, the compatibility read on instance-conf, the cache, or defaults.
74
+ res.setHeader('X-Cyberia-Hints-Source', source);
75
+ return res.status(200).json({ status: 'success', data });
76
+ } catch (err) {
77
+ logger.error('cyberia-client-hints GET failed:', err);
78
+ return res.status(500).json({ status: 'error', message: err.message });
79
+ }
80
+ });
81
+
82
+ // GET / -> canonical defaults. No DB read. Documentation/diagnostic.
83
+ router.get('/', async (_req, res) => {
84
+ if (_req && _req.headers && _req.headers.origin) {
85
+ res.set('Access-Control-Allow-Origin', _req.headers.origin);
86
+ } else res.setHeader('Access-Control-Allow-Origin', '*');
87
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
88
+ res.setHeader('X-Cyberia-Hints-Source', 'defaults');
89
+ return res.status(200).json({ status: 'success', data: CYBERIA_CLIENT_HINTS_DEFAULTS });
90
+ });
91
+
92
+ return router;
93
+ }
94
+ }
95
+
96
+ const ApiRouter = (options) => CyberiaClientHintsRouter.router(options);
97
+
98
+ export { ApiRouter, CyberiaClientHintsRouter };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * @module src/api/cyberia-client-hints/cyberia-client-hints.service.js
3
+ *
4
+ * Read-only service layer for client presentation hints.
5
+ *
6
+ * Resolution order (highest priority first):
7
+ * 1. CyberiaClientHints collection — the dedicated presentation-overrides
8
+ * collection. Always preferred when present.
9
+ * 2. Compatibility read on CyberiaInstanceConf with the same `code` —
10
+ * covers instances seeded before CyberiaClientHints existed.
11
+ * 3. Canonical compile-time defaults from
12
+ * SharedDefaultsCyberia.js. The C client bakes the
13
+ * same defaults at compile time, so this branch returns exactly the
14
+ * values the client already has.
15
+ *
16
+ * Caching: in-memory TTL cache keyed by instance code. Read-only on the
17
+ * hot path; cache writes happen on cache miss + DB hit. CMS writes that
18
+ * mutate the underlying collection should call `clientHintsInvalidate(code)`.
19
+ *
20
+ * Output is a plain JSON-friendly object whose shape matches the C
21
+ * client's compile-time layout.
22
+ */
23
+
24
+ import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
25
+ import { loggerFactory } from '../../server/logger.js';
26
+ import { resolveHostKeyContext } from '../../server/conf.js';
27
+ import {
28
+ buildClientHints,
29
+ CYBERIA_CLIENT_HINTS_DEFAULTS,
30
+ } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
31
+
32
+ const logger = loggerFactory(import.meta);
33
+
34
+ // TTL chosen long enough that bursty client fetches are absorbed, short
35
+ // enough that an editor change shows up within ~30s without manual flush.
36
+ const CACHE_TTL_MS = 30_000;
37
+
38
+ // One per instance code. value: { data, expiresAt }
39
+ const cache = new Map();
40
+
41
+ function now() {
42
+ return Date.now();
43
+ }
44
+
45
+ function cacheGet(code) {
46
+ const entry = cache.get(code);
47
+ if (!entry) return null;
48
+ if (entry.expiresAt < now()) {
49
+ cache.delete(code);
50
+ return null;
51
+ }
52
+ return entry.data;
53
+ }
54
+
55
+ function cacheSet(code, data) {
56
+ cache.set(code, { data, expiresAt: now() + CACHE_TTL_MS });
57
+ }
58
+
59
+ /** Invalidate a cache entry. CMS write paths should call this after
60
+ * mutating either CyberiaClientHints or CyberiaInstanceConf for
61
+ * the given code. */
62
+ export function clientHintsInvalidate(code) {
63
+ if (code) {
64
+ cache.delete(code);
65
+ } else {
66
+ cache.clear();
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Resolve the merged client-hints document for an instance code.
72
+ *
73
+ * @param {string} code
74
+ * @param {Object} options Engine routing context.
75
+ * @param {string} [options.host='default'] DataBaseProviderService host key.
76
+ * @param {string} [options.path='/'] DataBaseProviderService path key.
77
+ * @returns {Promise<{data: object, source: 'cache'|'presentation-hints'|'instance-conf'|'defaults'}>}
78
+ */
79
+ export async function resolveClientHints(code, options = {}) {
80
+ if (code) {
81
+ const cached = cacheGet(code);
82
+ if (cached) {
83
+ return { data: cached, source: 'cache' };
84
+ }
85
+ }
86
+
87
+ const host = options.host || 'default';
88
+ const path = options.path || '/';
89
+ const context = { host, path };
90
+ const id = resolveHostKeyContext(context);
91
+
92
+ let HintsModel = null;
93
+ let ConfModel = null;
94
+ try {
95
+ HintsModel = DataBaseProviderService.getModel('CyberiaClientHints', context);
96
+ } catch {
97
+ // model may not be mounted on this context
98
+ }
99
+ try {
100
+ ConfModel = DataBaseProviderService.getModel('CyberiaInstanceConf', context);
101
+ } catch {
102
+ // model may not be mounted on this context
103
+ }
104
+
105
+ if (!HintsModel && !ConfModel) {
106
+ logger.warn('client-hints: mongoose models not available for', id, '— returning defaults');
107
+ return { data: CYBERIA_CLIENT_HINTS_DEFAULTS, source: 'defaults' };
108
+ }
109
+
110
+ // 1. Preferred — CyberiaClientHints collection (src/api/cyberia-client-hints/cyberia-client-hints.model.js).
111
+ if (HintsModel && code) {
112
+ const hint = await HintsModel.findOne({ code }).lean().catch(() => null);
113
+ if (hint) {
114
+ const merged = buildClientHints(hint);
115
+ cacheSet(code, merged);
116
+ return { data: merged, source: 'presentation-hints' };
117
+ }
118
+ }
119
+
120
+ // 2. Compatibility read — for instances seeded before CyberiaClientHints
121
+ // existed, look up the same code in CyberiaInstanceConf and read its
122
+ // presentation-shaped fields directly.
123
+ if (ConfModel && code) {
124
+ const fromConf =
125
+ (await ConfModel.findOne({ code }).lean().catch(() => null)) ||
126
+ (await ConfModel.findById(code).lean().catch(() => null));
127
+ if (fromConf) {
128
+ const merged = buildClientHints(fromConf);
129
+ cacheSet(code, merged);
130
+ return { data: merged, source: 'instance-conf' };
131
+ }
132
+ }
133
+
134
+ // 3. Any available CyberiaClientHints document — used when the requested
135
+ // code has no record yet but another instance (e.g. the one the Go
136
+ // server is currently running) does. This avoids pure built-in
137
+ // defaults in fresh environments where only a different code is seeded.
138
+ if (HintsModel) {
139
+ const anyHint = await HintsModel.findOne({}).lean().catch(() => null);
140
+ if (anyHint) {
141
+ const merged = buildClientHints(anyHint);
142
+ // Cache under the requested code so subsequent requests are fast,
143
+ // but with a shorter TTL (5 s) so a proper seed wins quickly.
144
+ if (code) cache.set(code, { data: merged, expiresAt: now() + 5_000 });
145
+ return { data: merged, source: 'presentation-hints-fallback' };
146
+ }
147
+ }
148
+
149
+ // 4. Canonical defaults. Not cached — we do not poison the cache with
150
+ // a default that could mask a later DB insert.
151
+ return { data: CYBERIA_CLIENT_HINTS_DEFAULTS, source: 'defaults' };
152
+ }
@@ -4,26 +4,31 @@ import express from 'express';
4
4
 
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
- const CyberiaDialogueRouter = (options) => {
8
- const router = express.Router();
9
- const authMiddleware = options.authMiddleware;
10
- router.post(`/:id`, async (req, res) => await CyberiaDialogueController.post(req, res, options));
11
- router.post(`/`, async (req, res) => await CyberiaDialogueController.post(req, res, options));
12
- // Direct lookup by code — C client fetches dialogue by code (e.g. "default-lain")
13
- router.get(`/code/:code`, async (req, res) => await CyberiaDialogueController.getByCode(req, res, options));
14
- router.get(
15
- `/:id`,
16
- // authMiddleware,
17
- async (req, res) => await CyberiaDialogueController.get(req, res, options),
18
- );
19
- router.get(`/`, async (req, res) => await CyberiaDialogueController.get(req, res, options));
20
- router.put(`/:id`, async (req, res) => await CyberiaDialogueController.put(req, res, options));
21
- router.put(`/`, async (req, res) => await CyberiaDialogueController.put(req, res, options));
22
- router.delete(`/:id`, async (req, res) => await CyberiaDialogueController.delete(req, res, options));
23
- router.delete(`/`, async (req, res) => await CyberiaDialogueController.delete(req, res, options));
24
- return router;
25
- };
7
+ class CyberiaDialogueRouter {
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 CyberiaDialogueController.post(req, res, options));
15
+ router.post(`/`, async (req, res) => await CyberiaDialogueController.post(req, res, options));
16
+ // Direct lookup by code — C client fetches dialogue by code (e.g. "default-lain")
17
+ router.get(`/code/:code`, async (req, res) => await CyberiaDialogueController.getByCode(req, res, options));
18
+ router.get(
19
+ `/:id`,
20
+ // options.authMiddleware,
21
+ async (req, res) => await CyberiaDialogueController.get(req, res, options),
22
+ );
23
+ router.get(`/`, async (req, res) => await CyberiaDialogueController.get(req, res, options));
24
+ router.put(`/:id`, async (req, res) => await CyberiaDialogueController.put(req, res, options));
25
+ router.put(`/`, async (req, res) => await CyberiaDialogueController.put(req, res, options));
26
+ router.delete(`/:id`, async (req, res) => await CyberiaDialogueController.delete(req, res, options));
27
+ router.delete(`/`, async (req, res) => await CyberiaDialogueController.delete(req, res, options));
28
+ return router;
29
+ }
30
+ }
26
31
 
27
- const ApiRouter = CyberiaDialogueRouter;
32
+ const ApiRouter = (options) => CyberiaDialogueRouter.router(options);
28
33
 
29
34
  export { ApiRouter, CyberiaDialogueRouter };
@@ -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 CyberiaDialogueService {
8
8
  static post = async (req, res, options) => {
9
9
  /** @type {import('./cyberia-dialogue.model.js').CyberiaDialogueModel} */
10
- const CyberiaDialogue = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaDialogue;
10
+ const CyberiaDialogue = DataBaseProviderService.getModel("CyberiaDialogue", options);
11
11
  return await new CyberiaDialogue(req.body).save();
12
12
  };
13
13
  static get = async (req, res, options) => {
14
14
  /** @type {import('./cyberia-dialogue.model.js').CyberiaDialogueModel} */
15
- const CyberiaDialogue = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaDialogue;
15
+ const CyberiaDialogue = DataBaseProviderService.getModel("CyberiaDialogue", options);
16
16
  if (req.params.id) return await CyberiaDialogue.findById(req.params.id);
17
17
 
18
18
  // Parse query parameters using DataQuery helper
@@ -28,18 +28,18 @@ class CyberiaDialogueService {
28
28
  };
29
29
  static put = async (req, res, options) => {
30
30
  /** @type {import('./cyberia-dialogue.model.js').CyberiaDialogueModel} */
31
- const CyberiaDialogue = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaDialogue;
31
+ const CyberiaDialogue = DataBaseProviderService.getModel("CyberiaDialogue", options);
32
32
  return await CyberiaDialogue.findByIdAndUpdate(req.params.id, req.body);
33
33
  };
34
34
  static delete = async (req, res, options) => {
35
35
  /** @type {import('./cyberia-dialogue.model.js').CyberiaDialogueModel} */
36
- const CyberiaDialogue = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaDialogue;
36
+ const CyberiaDialogue = DataBaseProviderService.getModel("CyberiaDialogue", options);
37
37
  if (req.params.id) return await CyberiaDialogue.findByIdAndDelete(req.params.id);
38
38
  else return await CyberiaDialogue.deleteMany();
39
39
  };
40
40
  static getByCode = async (req, res, options) => {
41
41
  /** @type {import('./cyberia-dialogue.model.js').CyberiaDialogueModel} */
42
- const CyberiaDialogue = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaDialogue;
42
+ const CyberiaDialogue = DataBaseProviderService.getModel("CyberiaDialogue", options);
43
43
  const { code } = req.params;
44
44
  if (!code) throw new Error('code parameter is required');
45
45
  const data = await CyberiaDialogue.find({ code }).sort({ order: 1 }).lean();
@@ -4,24 +4,28 @@ import express from 'express';
4
4
 
5
5
  const logger = loggerFactory(import.meta);
6
6
 
7
- const CyberiaEntityRouter = (options) => {
8
- const router = express.Router();
9
- const authMiddleware = options.authMiddleware;
10
- router.post(`/:id`, async (req, res) => await CyberiaEntityController.post(req, res, options));
11
- router.post(`/`, async (req, res) => await CyberiaEntityController.post(req, res, options));
12
- router.get(
13
- `/:id`,
14
- // authMiddleware,
15
- async (req, res) => await CyberiaEntityController.get(req, res, options),
16
- );
17
- router.get(`/`, async (req, res) => await CyberiaEntityController.get(req, res, options));
18
- router.put(`/:id`, async (req, res) => await CyberiaEntityController.put(req, res, options));
19
- router.put(`/`, async (req, res) => await CyberiaEntityController.put(req, res, options));
20
- router.delete(`/:id`, async (req, res) => await CyberiaEntityController.delete(req, res, options));
21
- router.delete(`/`, async (req, res) => await CyberiaEntityController.delete(req, res, options));
22
- return router;
23
- };
7
+ class CyberiaEntityRouter {
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 CyberiaEntityController.post(req, res, options));
15
+ router.post(`/`, async (req, res) => await CyberiaEntityController.post(req, res, options));
16
+ router.get(`/:id`,
17
+ // options.authMiddleware,
18
+ async (req, res) => await CyberiaEntityController.get(req, res, options),
19
+ );
20
+ router.get(`/`, async (req, res) => await CyberiaEntityController.get(req, res, options));
21
+ router.put(`/:id`, async (req, res) => await CyberiaEntityController.put(req, res, options));
22
+ router.put(`/`, async (req, res) => await CyberiaEntityController.put(req, res, options));
23
+ router.delete(`/:id`, async (req, res) => await CyberiaEntityController.delete(req, res, options));
24
+ router.delete(`/`, async (req, res) => await CyberiaEntityController.delete(req, res, options));
25
+ return router;
26
+ }
27
+ }
24
28
 
25
- const ApiRouter = CyberiaEntityRouter;
29
+ const ApiRouter = (options) => CyberiaEntityRouter.router(options);
26
30
 
27
31
  export { ApiRouter, CyberiaEntityRouter };
@@ -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 CyberiaEntityService {
8
8
  static post = async (req, res, options) => {
9
9
  /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
10
- const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
10
+ const CyberiaEntity = DataBaseProviderService.getModel("CyberiaEntity", options);
11
11
  return await new CyberiaEntity(req.body).save();
12
12
  };
13
13
  static get = async (req, res, options) => {
14
14
  /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
15
- const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
15
+ const CyberiaEntity = DataBaseProviderService.getModel("CyberiaEntity", options);
16
16
  if (req.params.id) return await CyberiaEntity.findById(req.params.id);
17
17
 
18
18
  // Parse query parameters using DataQuery helper
@@ -28,12 +28,12 @@ class CyberiaEntityService {
28
28
  };
29
29
  static put = async (req, res, options) => {
30
30
  /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
31
- const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
31
+ const CyberiaEntity = DataBaseProviderService.getModel("CyberiaEntity", options);
32
32
  return await CyberiaEntity.findByIdAndUpdate(req.params.id, req.body);
33
33
  };
34
34
  static delete = async (req, res, options) => {
35
35
  /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
36
- const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
36
+ const CyberiaEntity = DataBaseProviderService.getModel("CyberiaEntity", options);
37
37
  if (req.params.id) return await CyberiaEntity.findByIdAndDelete(req.params.id);
38
38
  else return await CyberiaEntity.deleteMany();
39
39
  };
@@ -31,13 +31,69 @@ import {
31
31
  OccupancyGrid,
32
32
  } from './cyberia-world-generator.js';
33
33
 
34
- import { CYBERIA_INSTANCE_CONF_DEFAULTS } from '../cyberia-instance-conf/cyberia-instance-conf.defaults.js';
34
+ import {
35
+ CYBERIA_INSTANCE_CONF_DEFAULTS,
36
+ ENTITY_TYPE_DEFAULTS,
37
+ RESOURCE_ENTITY_TYPE_DEFAULTS,
38
+ DefaultCyberiaItems,
39
+ } from '../cyberia-server-defaults/cyberia-server-defaults.js';
40
+ import {
41
+ CYBERIA_CLIENT_HINTS_DEFAULTS,
42
+ PALETTE,
43
+ } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
35
44
 
36
45
  // ── Defaults ─────────────────────────────────────────────────────────────────
37
46
 
38
47
  const DEFAULT_MAP_COUNT = 4;
39
48
  const DEFAULT_GRID_SIZE = 64;
40
49
 
50
+ // ── Asset-id integrity ──────────────────────────────────────────────────────
51
+ //
52
+ // Every item id that the fallback world can place on a map must also be
53
+ // present in DefaultCyberiaItems, otherwise the `import-default-items` seed
54
+ // will not create an ObjectLayer for it and the runtime client will fall
55
+ // back to a solid-colour rectangle instead of the intended sprite.
56
+ //
57
+ // auditFallbackItemIds() returns the set of unknown ids encountered in the
58
+ // canonical defaults so callers (CLI, test harness, gRPC fallback path)
59
+ // can surface drift immediately. Returns an empty array when in sync.
60
+
61
+ const KNOWN_DEFAULT_ITEM_IDS = new Set(DefaultCyberiaItems.map((e) => e.item.id));
62
+
63
+ function collectReferencedItemIds() {
64
+ const ids = new Set();
65
+ const push = (id) => { if (typeof id === 'string' && id.length > 0) ids.add(id); };
66
+
67
+ for (const e of ENTITY_TYPE_DEFAULTS) {
68
+ (e.liveItemIds || []).forEach(push);
69
+ (e.deadItemIds || []).forEach(push);
70
+ (e.dropItemIds || []).forEach(push);
71
+ (e.defaultObjectLayers || []).forEach((ol) => push(ol.itemId));
72
+ }
73
+ for (const r of RESOURCE_ENTITY_TYPE_DEFAULTS) {
74
+ (r.liveItemIds || []).forEach(push);
75
+ (r.deadItemIds || []).forEach(push);
76
+ (r.dropItemIds || []).forEach(push);
77
+ }
78
+ return ids;
79
+ }
80
+
81
+ /**
82
+ * Audit every item id the fallback world can place against the canonical
83
+ * DefaultCyberiaItems registry. Returns an array of missing ids; an empty
84
+ * array means the registry is in sync.
85
+ *
86
+ * @returns {string[]}
87
+ */
88
+ function auditFallbackItemIds() {
89
+ const referenced = collectReferencedItemIds();
90
+ const missing = [];
91
+ for (const id of referenced) {
92
+ if (!KNOWN_DEFAULT_ITEM_IDS.has(id)) missing.push(id);
93
+ }
94
+ return missing.sort();
95
+ }
96
+
41
97
  // ── Single map generator ─────────────────────────────────────────────────────
42
98
 
43
99
  /**
@@ -45,7 +101,7 @@ const DEFAULT_GRID_SIZE = 64;
45
101
  * Everything except floor tiles is randomized.
46
102
  *
47
103
  * @param {string} mapCode
48
- * @param {Array} colors Palette from CYBERIA_INSTANCE_CONF_DEFAULTS.
104
+ * @param {Array} colors Palette (PALETTE from SharedDefaultsCyberia).
49
105
  * @param {object} [opts]
50
106
  * @param {number} [opts.gridSize]
51
107
  * @param {number} [opts.obstacleCount]
@@ -129,9 +185,27 @@ function generateFallbackWorld(opts = {}) {
129
185
  foregroundCount,
130
186
  botCount,
131
187
  resourceCount,
132
- colors = CYBERIA_INSTANCE_CONF_DEFAULTS.colors,
188
+ // Palette lives in SharedDefaultsCyberia (presentation-owned). The
189
+ // world generator only uses it to stamp a cosmetic rgba(...) string on
190
+ // entities so the browser editor / preview can render a coloured
191
+ // fallback before atlases load. The C client resolves real colours
192
+ // through domain/presentation_runtime — it does not read this value.
193
+ colors = PALETTE,
133
194
  } = opts;
134
195
 
196
+ // Surface item-id drift loudly on the very first build, so a missing
197
+ // `bin/cyberia run-workflow import-default-items` run shows up at startup
198
+ // instead of as silent grey rectangles later.
199
+ const missing = auditFallbackItemIds();
200
+ if (missing.length > 0) {
201
+ // eslint-disable-next-line no-console
202
+ console.warn(
203
+ '[cyberia-fallback-world] item ids referenced by defaults but absent from DefaultCyberiaItems:',
204
+ missing.join(', '),
205
+ '— run `node bin/cyberia run-workflow import-default-items` after adding them.',
206
+ );
207
+ }
208
+
135
209
  // Generate map codes.
136
210
  const mapCodes = [];
137
211
  for (let i = 0; i < mapCount; i++) {
@@ -169,10 +243,11 @@ function generateFallbackWorld(opts = {}) {
169
243
  portals,
170
244
  topology,
171
245
  config: CYBERIA_INSTANCE_CONF_DEFAULTS,
246
+ presentationHints: CYBERIA_CLIENT_HINTS_DEFAULTS,
172
247
  _fallback: true,
173
248
  };
174
249
  }
175
250
 
176
251
  // ── Public API ───────────────────────────────────────────────────────────────
177
252
 
178
- export { generateFallbackWorld, generateFallbackMap };
253
+ export { generateFallbackWorld, generateFallbackMap, auditFallbackItemIds };