@underpostnet/cyberia 3.3.73 → 3.3.77

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 (269) hide show
  1. package/.env.example +17 -5
  2. package/.github/workflows/coverall.cyberia.ci.yml +10 -0
  3. package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
  4. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
  5. package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
  6. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
  7. package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
  8. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
  9. package/.github/workflows/engine-cyberia.ci.yml +1 -1
  10. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  11. package/AGENTS.md +61 -12
  12. package/CHANGELOG.md +159 -161
  13. package/CLI-HELP.md +20 -13
  14. package/Dockerfile +1 -1
  15. package/Dockerfile.dev +1 -1
  16. package/Dockerfile.test +2 -2
  17. package/README.md +28 -28
  18. package/bin/build.js +96 -21
  19. package/bin/cyberia.js +1247 -1373
  20. package/bin/index.js +1247 -1373
  21. package/compose.env +17 -5
  22. package/conf.js +7 -4
  23. package/deploy/cyberia-client/deploy.sh +10 -11
  24. package/deploy/cyberia-client/package.sh +0 -1
  25. package/deploy/cyberia-client/state.sh +3 -2
  26. package/deploy/cyberia-server/deploy.sh +10 -11
  27. package/deploy/cyberia-server/package.sh +0 -1
  28. package/deploy/cyberia-server/state.sh +3 -2
  29. package/deploy/dd-cyberia/deploy.sh +5 -5
  30. package/deploy/dd-cyberia/init.sh +15 -14
  31. package/deploy/dd-cyberia/package.sh +0 -1
  32. package/deploy/dd-cyberia/state.sh +2 -2
  33. package/deploy/dd-cyberia/sync-deploy.sh +139 -75
  34. package/deploy/lib/config.sh +29 -0
  35. package/deploy/lib/github-actions-logging.sh +92 -76
  36. package/deploy/lib/host.sh +28 -19
  37. package/deploy/lib/state.sh +4 -1
  38. package/deploy/pwa-microservices-template/deploy.sh +10 -8
  39. package/deploy/release/deploy.sh +0 -2
  40. package/deployment.yaml +1 -1
  41. package/docker-compose.yml +23 -17
  42. package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
  43. package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
  44. package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
  45. package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
  46. package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
  47. package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
  48. package/docs/coverage/cyberia/base.css +362 -0
  49. package/docs/coverage/cyberia/block-navigation.js +82 -0
  50. package/docs/coverage/cyberia/favicon.png +0 -0
  51. package/docs/coverage/cyberia/index.html +161 -0
  52. package/docs/coverage/cyberia/prettify.css +101 -0
  53. package/docs/coverage/cyberia/prettify.js +937 -0
  54. package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
  55. package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
  56. package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
  57. package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
  58. package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
  59. package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
  60. package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
  61. package/docs/coverage/cyberia/sorter.js +205 -0
  62. package/hardhat/package-lock.json +2 -2
  63. package/hardhat/package.json +1 -1
  64. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  65. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
  66. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
  67. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  68. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  69. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  70. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  71. package/manifests/deployment/mongo-express/service.yaml +16 -0
  72. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  73. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  74. package/package.json +19 -17
  75. package/scripts/test-monitor.sh +3 -3
  76. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
  77. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  78. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  79. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
  80. package/src/api/cyberia-action/cyberia-action.router.js +2 -2
  81. package/src/api/cyberia-action/cyberia-action.service.js +3 -4
  82. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  83. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  84. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  85. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  86. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
  87. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  88. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
  89. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
  90. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  91. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  92. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  93. package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
  94. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  95. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  96. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
  97. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
  98. package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
  99. package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
  100. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  101. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  102. package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
  103. package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
  104. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  105. package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
  106. package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
  107. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  108. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  109. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
  110. package/src/api/cyberia-map/cyberia-map.service.js +6 -2
  111. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  112. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  113. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  114. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  115. package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
  116. package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
  117. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  118. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
  119. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  120. package/src/api/file/file.ref.js +153 -0
  121. package/src/api/file/file.ref.json +11 -2
  122. package/src/api/object-layer/object-layer.model.js +47 -12
  123. package/src/api/object-layer/object-layer.router.js +10 -3
  124. package/src/api/object-layer/object-layer.service.js +113 -120
  125. package/src/cli/app.js +12 -1
  126. package/src/cli/client.js +2 -2
  127. package/src/cli/cluster.js +80 -18
  128. package/src/cli/deploy.js +215 -34
  129. package/src/cli/docker-compose.js +1 -1
  130. package/src/cli/fs-selection.js +115 -0
  131. package/src/cli/fs.js +158 -342
  132. package/src/cli/index.js +22 -10
  133. package/src/cli/release.js +3 -1
  134. package/src/cli/repository.js +35 -9
  135. package/src/cli/run.js +80 -199
  136. package/src/cli/test.js +4 -0
  137. package/src/cli/wireguard.js +32 -31
  138. package/src/client/components/core/Badge.js +0 -1
  139. package/src/client/components/core/CssCore.js +8 -1
  140. package/src/client/components/core/Docs.js +77 -100
  141. package/src/client/components/core/Modal.js +77 -23
  142. package/src/client/components/core/ToolTip.js +15 -0
  143. package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
  144. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  145. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  146. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  147. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  148. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  149. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  150. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  151. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  152. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  153. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
  154. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  155. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  156. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
  157. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  158. package/src/client/components/default/AppShellDefault.js +1 -4
  159. package/src/client/components/itemledger/AppShellItemledger.js +0 -2
  160. package/src/client/components/underpost/AppShellUnderpost.js +1 -1
  161. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  162. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
  163. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  164. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  165. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  166. package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
  167. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  168. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  169. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  170. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  171. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  172. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  173. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  174. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  175. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  176. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  177. package/src/client/services/object-layer/object-layer.management.js +6 -10
  178. package/src/client/services/object-layer/object-layer.service.js +3 -1
  179. package/src/client/ssr/views/Cyberia404.js +3 -3
  180. package/src/client-builder/client-build-docs.js +52 -52
  181. package/src/client-builder/client-build.js +67 -44
  182. package/src/client-builder/client-bundle.js +347 -0
  183. package/src/db/mongo/MongoExpress.js +187 -0
  184. package/src/grpc/cyberia/grpc-server.js +5 -7
  185. package/src/index.js +1 -1
  186. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
  187. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  188. package/src/projects/cyberia/besu-genesis-generator.js +5 -14
  189. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  190. package/src/projects/cyberia/catalog-cyberia.js +3 -2
  191. package/src/projects/cyberia/gemini-client.js +3 -3
  192. package/src/projects/cyberia/generate-saga.js +4 -11
  193. package/src/projects/cyberia/hot-reload-trigger.js +17 -28
  194. package/src/projects/cyberia/instance-backup.js +210 -0
  195. package/src/projects/cyberia/instance-data.js +177 -257
  196. package/src/projects/cyberia/map-preview-generator.js +32 -68
  197. package/src/projects/cyberia/object-layer.js +152 -319
  198. package/src/projects/cyberia/seed-audio.js +139 -0
  199. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  200. package/src/projects/cyberia/semantic-layer-generator.js +2 -18
  201. package/src/projects/cyberia/stat-balance.js +81 -0
  202. package/src/projects/cyberia/stat-commands.js +23 -0
  203. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  204. package/src/runtime/cyberia-client/Dockerfile +8 -4
  205. package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
  206. package/src/runtime/cyberia-server/Dockerfile +9 -2
  207. package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
  208. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  209. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  210. package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
  211. package/src/runtime/engine-cyberia/compose.env +17 -5
  212. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  213. package/src/server/build/coverage.js +113 -42
  214. package/src/server/build/package.js +55 -12
  215. package/src/server/build/testing.js +59 -9
  216. package/src/server/network/middlewares.js +8 -2
  217. package/src/server/network/underpost-gateway.js +113 -14
  218. package/src/server/ops/logger.js +70 -35
  219. package/src/server/runtime/conf.js +40 -9
  220. package/src/server/runtime/start.js +11 -2
  221. package/src/server/storage/data-query.js +16 -0
  222. package/src/server/storage/downloader.js +62 -14
  223. package/src/server/storage/zip.js +153 -0
  224. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  225. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  226. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
  227. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  228. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  229. package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
  230. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  231. package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
  232. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  233. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  234. package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
  235. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  236. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  237. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  238. package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
  239. package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
  240. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  241. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  242. package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
  243. package/test/unit/catalog.test.js +13 -2
  244. package/test/unit/client-build-docs.test.js +97 -70
  245. package/test/unit/client-bundle.test.js +313 -0
  246. package/test/unit/conf-resolution.test.js +58 -7
  247. package/test/unit/coverage-artifact.test.js +120 -29
  248. package/test/unit/cyberia/instance-backup.test.js +183 -0
  249. package/test/unit/cyberia/instance-data.test.js +314 -0
  250. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  251. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  252. package/test/unit/cyberia/stat-balance.test.js +69 -0
  253. package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
  254. package/test/unit/deploy-log-table.test.js +76 -5
  255. package/test/unit/downloader-integrity.test.js +98 -0
  256. package/test/unit/file-reference-registry.test.js +141 -0
  257. package/test/unit/fs-storage-paths.test.js +596 -0
  258. package/test/unit/gateway-pod-fetch.test.js +95 -0
  259. package/test/unit/letsencrypt-issuer.test.js +101 -0
  260. package/test/unit/logger-redaction.test.js +29 -0
  261. package/test/unit/package.test.js +70 -16
  262. package/test/unit/prepare-host.test.js +84 -1
  263. package/test/unit/release-bump.test.js +3 -1
  264. package/test/unit/start-options.test.js +12 -0
  265. package/test/unit/test-tiers.test.js +27 -0
  266. package/test/unit/zip-archive.test.js +142 -0
  267. package/vitest.config.js +11 -3
  268. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  269. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
@@ -16,6 +16,12 @@
16
16
  * reads from code defaults INSTEAD of Mongo; a persisted instance reads
17
17
  * them from Mongo, so they must exist there for the capture to serve the
18
18
  * same world (see instance-data.js#fetchFullInstance).
19
+ * - CyberiaMapAudioConf — one per captured map. The client asks for audio by
20
+ * map code, so a namespaced capture would otherwise play nothing.
21
+ *
22
+ * CyberiaAudio assets are NOT captured: an asset is global, identified by its
23
+ * code, and imported once by `cyberia audio --import`. The capture reports the
24
+ * codes it bound that no asset carries yet.
19
25
  *
20
26
  * ObjectLayer/atlas assets are NOT generated here — they come from the asset
21
27
  * pipeline (`bin/cyberia ol <ids> --import`). The capture reports the missing
@@ -31,6 +37,8 @@
31
37
 
32
38
  import { loggerFactory } from '../../server/ops/logger.js';
33
39
  import { collectReferencedItemIds } from './cyberia-fallback-world.js';
40
+ import { collectInstanceItemIds } from './cyberia-instance-items.js';
41
+ import { fallbackAudioConfig } from '../../projects/cyberia/seed-audio.js';
34
42
  import {
35
43
  DefaultCyberiaActions,
36
44
  DefaultCyberiaQuests,
@@ -189,7 +197,6 @@ function planFallbackCapture({
189
197
  ...(spawn.sourceMapCode ? { sourceMapCode: mapCodes.get(spawn.sourceMapCode) || spawn.sourceMapCode } : {}),
190
198
  },
191
199
  topologyMode: 'procedural',
192
- itemIds: (world.instance.itemIds || []).map((entry) => ({ ...entry })),
193
200
  };
194
201
 
195
202
  const conf = { ...fillInstanceConfDefaults(world.config || {}), instanceCode };
@@ -219,57 +226,24 @@ function planFallbackCapture({
219
226
  codeMaps,
220
227
  skippedActionCodes,
221
228
  skippedQuestCodes,
222
- itemIds: collectCaptureItemIds({ instance, conf, maps, actions: capturedActions, quests: capturedQuests }),
229
+ itemIds: collectCaptureItemIds({ maps, actions: capturedActions, quests: capturedQuests }),
223
230
  };
224
231
  }
225
232
 
226
233
  /**
227
- * Every ObjectLayer item id the captured instance needs an atlas for: what the
228
- * maps place, what the instance and conf declare, what the seeded skills and
229
- * entity-type defaults reference, and what the vendor/assembler/quest catalogs
230
- * draw icons for.
234
+ * Every ObjectLayer item id the captured instance needs an atlas for: the canonical world's
235
+ * own ids, plus what this instance names its maps, its entity-type defaults, and the
236
+ * vendor/assembler/quest catalogs the interact modal draws icons for.
231
237
  *
232
238
  * @returns {string[]} Sorted, de-duplicated item ids.
233
239
  */
234
- function collectCaptureItemIds({ instance, conf, maps, actions, quests }) {
240
+ function collectCaptureItemIds({ maps, actions, quests, entityDefaults = [] }) {
241
+ // The canonical world's own ids — every fallback default, including the skill triggers and
242
+ // summoned entities DefaultSkillConfig names. What the captured instance adds on top follows
243
+ // the same rule the export and the boot payload use.
235
244
  const ids = collectReferencedItemIds();
236
- const push = (id) => {
237
- if (typeof id === 'string' && id.length > 0 && !id.startsWith('$')) ids.add(id);
238
- };
239
-
240
- for (const map of maps || []) {
241
- for (const entity of map.entities || []) (entity.objectLayerItemIds || []).forEach(push);
242
- }
243
- for (const entry of instance?.itemIds || []) push(typeof entry === 'string' ? entry : entry?.id);
244
-
245
- for (const entityDefault of conf?.entityDefaults || []) {
246
- (entityDefault.liveItemIds || []).forEach(push);
247
- (entityDefault.deadItemIds || []).forEach(push);
248
- (entityDefault.dropItemIds || []).forEach(push);
249
- for (const slot of entityDefault.defaultObjectLayers || []) push(slot.itemId);
250
- }
251
- for (const skillConfig of conf?.skillConfig || []) {
252
- push(skillConfig.triggerItemId);
253
- for (const skill of skillConfig.skills || []) push(skill.summonedEntityItemId);
254
- }
255
-
256
- for (const action of actions || []) {
257
- for (const shopItem of action.shopItems || []) {
258
- push(shopItem.itemId);
259
- push(shopItem.priceItemId);
260
- }
261
- for (const recipe of action.craftRecipes || []) {
262
- (recipe.ingredients || []).forEach((ingredient) => push(ingredient.itemId));
263
- (recipe.outputItems || []).forEach((output) => push(output.itemId));
264
- }
265
- }
266
- for (const quest of quests || []) {
267
- for (const step of quest.steps || []) {
268
- for (const objective of step.objectives || []) push(objective.itemId);
269
- }
270
- for (const reward of quest.rewards || []) push(reward.itemId);
271
- }
272
-
245
+ // The conf references its entity-type defaults by _id, so the documents arrive resolved.
246
+ for (const itemId of collectInstanceItemIds({ maps, entityDefaults, actions, quests })) ids.add(itemId);
273
247
  return [...ids].sort();
274
248
  }
275
249
 
@@ -309,7 +283,8 @@ async function seedMissingContentDefaults({ CyberiaSkill, CyberiaEntityTypeDefau
309
283
  liveItemIds: entityDefault.liveItemIds || [],
310
284
  deadItemIds: entityDefault.deadItemIds || [],
311
285
  dropItemIds: entityDefault.dropItemIds || [],
312
- defaultObjectLayers: entityDefault.defaultObjectLayers || [],
286
+ inventoryItemsIds: entityDefault.inventoryItemsIds || [],
287
+ overrideItemsIdsState: entityDefault.overrideItemsIdsState || [],
313
288
  behavior: entityDefault.behavior || '',
314
289
  },
315
290
  'entityTypeDefaults',
@@ -329,10 +304,59 @@ async function seedMissingContentDefaults({ CyberiaSkill, CyberiaEntityTypeDefau
329
304
  }
330
305
 
331
306
  /**
332
- * Remove documents left over in this capture's own namespace by a previous,
333
- * larger capture (e.g. a world that used to have more maps). Only ever touches
334
- * codes prefixed with the instance code, and never runs in `keepFallbackCodes`
335
- * mode where the codes are the globally shared canonical ones.
307
+ * Write the audio configuration of the captured maps.
308
+ *
309
+ * Audio reaches the client per map code, so a world captured under namespaced codes needs its
310
+ * bindings rewritten under those codes the same reason the actions and quests are re-coded.
311
+ * Only codes an imported asset actually carries are bound: a binding to an absent asset would be
312
+ * a dangling name, and the caller is told which ones are missing instead.
313
+ *
314
+ * @param {object} params
315
+ * @param {object} params.models - `{ CyberiaMapAudioConf, CyberiaAudio }`.
316
+ * @param {object} params.plan - Result of `planFallbackCapture()`.
317
+ * @returns {Promise<{audioConfs: number, missingAudioCodes: string[]}>}
318
+ */
319
+ async function captureAudioConfig({ models, plan }) {
320
+ const { CyberiaMapAudioConf, CyberiaAudio } = models;
321
+ if (!CyberiaMapAudioConf) return { audioConfs: 0, missingAudioCodes: [] };
322
+
323
+ const config = fallbackAudioConfig();
324
+ const bound = new Set(
325
+ [...config.events.map(({ audioCode }) => audioCode), ...config.maps.map(({ defaultMusic }) => defaultMusic)].filter(
326
+ Boolean,
327
+ ),
328
+ );
329
+ const present = new Set(
330
+ CyberiaAudio
331
+ ? (await CyberiaAudio.find({ code: { $in: [...bound] } }, { code: 1 }).lean()).map((doc) => doc.code)
332
+ : [],
333
+ );
334
+ const events = config.events.filter(({ audioCode }) => present.has(audioCode));
335
+
336
+ let audioConfs = 0;
337
+ for (const map of config.maps) {
338
+ const mapCode = plan.codeMaps.mapCodes.get(map.mapCode);
339
+ if (!mapCode) continue;
340
+ await CyberiaMapAudioConf.findOneAndUpdate(
341
+ { mapCode },
342
+ {
343
+ $set: {
344
+ defaultMusic: present.has(map.defaultMusic) ? map.defaultMusic : '',
345
+ settings: config.settings,
346
+ events,
347
+ },
348
+ },
349
+ { upsert: true },
350
+ );
351
+ audioConfs++;
352
+ }
353
+ return { audioConfs, missingAudioCodes: [...bound].filter((code) => !present.has(code)).sort() };
354
+ }
355
+
356
+ /**
357
+ * Remove documents that a larger previous capture left in this capture's own
358
+ * namespace. Touches only codes prefixed with the instance code, and never runs
359
+ * in `keepFallbackCodes` mode, where the codes are the shared canonical ones.
336
360
  */
337
361
  async function pruneStaleCaptureDocs({ models, instanceCode, plan }) {
338
362
  const namespace = new RegExp(`^${escapeRegExp(instanceCode)}-`);
@@ -373,7 +397,8 @@ async function pruneStaleCaptureDocs({ models, instanceCode, plan }) {
373
397
  * @param {object} params
374
398
  * @param {object} params.models `{ CyberiaInstance, CyberiaInstanceConf, CyberiaMap, CyberiaAction,
375
399
  * CyberiaQuest, CyberiaSkill, CyberiaEntityTypeDefault,
376
- * CyberiaDialogue, ObjectLayer }`
400
+ * CyberiaDialogue, CyberiaMapAudioConf, CyberiaAudio,
401
+ * ObjectLayer }`
377
402
  * @param {object} params.world Result of `generateFallbackWorld()`.
378
403
  * @param {string} params.instanceCode
379
404
  * @param {boolean} [params.keepFallbackCodes=false]
@@ -437,6 +462,16 @@ async function captureFallbackWorld({ models, world, instanceCode, keepFallbackC
437
462
  const inserted = await seedMissingContentDefaults(models);
438
463
  logger.info('Seeded missing content defaults', inserted);
439
464
 
465
+ const audio = await captureAudioConfig({ models, plan });
466
+ logger.info(`Captured ${audio.audioConfs} CyberiaMapAudioConf document(s)`);
467
+ if (audio.missingAudioCodes.length > 0) {
468
+ logger.warn(
469
+ 'Audio codes with no imported CyberiaAudio document were left unbound — ' +
470
+ 'run `node bin/cyberia run-workflow seed-audio` to record and import them',
471
+ { codes: audio.missingAudioCodes },
472
+ );
473
+ }
474
+
440
475
  // `keepFallbackCodes` reuses the globally shared canonical codes, so nothing
441
476
  // in that namespace belongs exclusively to this capture.
442
477
  const pruned = keepFallbackCodes
@@ -452,11 +487,12 @@ async function captureFallbackWorld({ models, world, instanceCode, keepFallbackC
452
487
  );
453
488
  const missingObjectLayerItemIds = plan.itemIds.filter((id) => !presentItemIds.has(id));
454
489
 
455
- return { plan, inserted, pruned, missingObjectLayerItemIds };
490
+ return { plan, inserted, pruned, audio, missingObjectLayerItemIds };
456
491
  }
457
492
 
458
493
  export {
459
494
  planFallbackCapture,
495
+ captureAudioConfig,
460
496
  captureFallbackWorld,
461
497
  collectCaptureItemIds,
462
498
  captureMapCode,
@@ -53,16 +53,16 @@ import {
53
53
  const DEFAULT_MAP_COUNT = 4;
54
54
  const DEFAULT_GRID_SIZE = 64;
55
55
 
56
+ const getFallbackMapCodes = (mapCount = DEFAULT_MAP_COUNT) =>
57
+ Array.from({ length: mapCount }, (_, index) => `fallback-map-${index}`);
58
+
56
59
  // ── Asset-id integrity ──────────────────────────────────────────────────────
57
60
  //
58
- // Every item id that the fallback world can place on a map must also be
59
- // present in DefaultCyberiaItems, otherwise the `import-default-items` seed
60
- // will not create an ObjectLayer for it and the runtime client will fall
61
- // back to a solid-colour rectangle instead of the intended sprite.
61
+ // Every item id the fallback world can place must also be in
62
+ // DefaultCyberiaItems. Otherwise the `import-default-items` seed creates no
63
+ // ObjectLayer for it and the client draws a solid-colour rectangle.
62
64
  //
63
- // auditFallbackItemIds() returns the set of unknown ids encountered in the
64
- // canonical defaults so callers (CLI, test harness, gRPC fallback path)
65
- // can surface drift immediately. Returns an empty array when in sync.
65
+ // auditFallbackItemIds() returns the unknown ids, empty when in sync.
66
66
 
67
67
  const KNOWN_DEFAULT_ITEM_IDS = new Set(DefaultCyberiaItems.map((e) => e.item.id));
68
68
 
@@ -83,7 +83,7 @@ function collectReferencedItemIds() {
83
83
  (e.liveItemIds || []).forEach(push);
84
84
  (e.deadItemIds || []).forEach(push);
85
85
  (e.dropItemIds || []).forEach(push);
86
- (e.defaultObjectLayers || []).forEach((ol) => push(ol.itemId));
86
+ (e.inventoryItemsIds || []).forEach(push);
87
87
  }
88
88
  for (const r of RESOURCE_ENTITY_TYPE_DEFAULTS) {
89
89
  (r.liveItemIds || []).forEach(push);
@@ -239,11 +239,6 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
239
239
  * @param {number} [opts.resourceCount] Resources per map (random if omitted).
240
240
  * @param {number} [opts.staticCount] Static decorators per map (random if omitted).
241
241
  * @param {Array} [opts.colors] Override palette.
242
- * @param {Array<{id: string, defaultPlayerInventory: boolean}>} [opts.itemIds=[]]
243
- * Instance-level default items, mirroring `CyberiaInstance.itemIds`. Entries
244
- * flagged `defaultPlayerInventory` are merged into the player entity default
245
- * by `applyInstanceDefaultPlayerInventory`; every id is also resolved to an
246
- * atlas at boot. Layout is unaffected, so this stays outside the world seed.
247
242
  * @returns {{
248
243
  * instance: object,
249
244
  * maps: object[],
@@ -262,18 +257,13 @@ function generateFallbackWorld(opts = {}) {
262
257
  botCount,
263
258
  resourceCount,
264
259
  staticCount,
265
- // Palette lives in SharedDefaultsCyberia (presentation-owned). The
266
- // world generator only uses it to stamp a cosmetic rgba(...) string on
267
- // entities so the browser editor / preview can render a coloured
268
- // fallback before atlases load. The C client resolves real colours
269
- // through domain/presentation_runtime — it does not read this value.
260
+ // Palette from SharedDefaultsCyberia. The generator only stamps a cosmetic
261
+ // rgba(...) string on entities, for previews drawn before atlases load. The
262
+ // game client resolves its own colours and ignores this value.
270
263
  colors = PALETTE,
271
- itemIds = [],
272
264
  } = opts;
273
265
 
274
- // Surface item-id drift loudly on the very first build, so a missing
275
- // `bin/cyberia run-workflow import-default-items` run shows up at startup
276
- // instead of as silent grey rectangles later.
266
+ // Report item-id drift at the first build, not as grey rectangles later.
277
267
  const missing = auditFallbackItemIds();
278
268
  if (missing.length > 0) {
279
269
  // eslint-disable-next-line no-console
@@ -284,18 +274,12 @@ function generateFallbackWorld(opts = {}) {
284
274
  );
285
275
  }
286
276
 
287
- // Generate map codes.
288
- const mapCodes = [];
289
- for (let i = 0; i < mapCount; i++) {
290
- mapCodes.push(`fallback-map-${i}`);
291
- }
277
+ const mapCodes = getFallbackMapCodes(mapCount);
292
278
 
293
- // Deterministic layout: seed the shared random source so every call
294
- // reproduces the SAME world. The instance-map `/static` POIs and the
295
- // `/preview` node image are built by independent requests (and survive
296
- // restarts) they must agree on where every entity sits, so a resource POI
297
- // lands exactly where the preview draws that resource. The seed folds in the
298
- // layout-affecting options so a custom count set stays internally consistent.
279
+ // Deterministic layout: the seeded random source makes every call reproduce
280
+ // the same world. The `/static` POIs and the `/preview` image come from
281
+ // independent requests and must agree on where each entity sits. The seed
282
+ // folds in the layout options, so a custom count set stays consistent.
299
283
  const seed = `cyberia-fallback-world-v1:${mapCount}:${gridSize ?? ''}:${obstacleCount ?? ''}:${
300
284
  foregroundCount ?? ''
301
285
  }:${botCount ?? ''}:${resourceCount ?? ''}:${staticCount ?? ''}`;
@@ -327,7 +311,6 @@ function generateFallbackWorld(opts = {}) {
327
311
  portals,
328
312
  topologyMode: 'procedural',
329
313
  playerSpawn: { ...DEFAULT_PLAYER_SPAWN },
330
- itemIds: itemIds.map((entry) => ({ ...entry })),
331
314
  };
332
315
 
333
316
  return {
@@ -343,12 +326,9 @@ function generateFallbackWorld(opts = {}) {
343
326
 
344
327
  // ── Selection-view fallback instance ─────────────────────────────────────────
345
328
  //
346
- // A dummy, never-persisted instance surfaced in the world/instance selection
347
- // list so it is never empty: it points at the always-on TEST variant (a real
348
- // deployed world in the mmo-server multiInstance block). Shaped like a
349
- // CyberiaInstance list row — only the fields InstanceSelectionView reads
350
- // (code/name/description/tags/status/thumbnail). Injected into the instance list
351
- // return by CyberiaInstanceService.get when the caller opts in (?fallback=true).
329
+ // A never-persisted instance row that keeps the selection list non-empty. It
330
+ // points at the always-on TEST variant and carries only the fields the
331
+ // selection view reads. CyberiaInstanceService.get injects it on ?fallback=true.
352
332
 
353
333
  const DEFAULT_FALLBACK_INSTANCE_CODE = 'TEST';
354
334
 
@@ -370,6 +350,7 @@ function fallbackListInstance() {
370
350
  // ── Public API ───────────────────────────────────────────────────────────────
371
351
 
372
352
  export {
353
+ getFallbackMapCodes,
373
354
  generateFallbackWorld,
374
355
  generateFallbackMap,
375
356
  auditFallbackItemIds,
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * REST boot service — the /api/cyberia-instance/boot/* fallback transport.
3
3
  *
4
- * Serves the exact CyberiaDataService payloads (world load, hot reload) over
5
- * REST so cyberia-server can boot from ENGINE_API_BASE_URL when the engine
6
- * gRPC server (ENGINE_GRPC_ADDRESS) is not enabled for the deploy. All data
7
- * assembly lives in src/projects/cyberia/instance-data.js, shared with
8
- * src/grpc/cyberia/grpc-server.js so both transports stay equivalent.
4
+ * Serves the CyberiaDataService payloads (world load, hot reload) over REST, so
5
+ * the game server can boot from its --data-server-url when the engine gRPC
6
+ * server is off for the deploy. The data assembly lives in
7
+ * src/projects/cyberia/instance-data.js, shared with
8
+ * src/grpc/cyberia/grpc-server.js, so both transports stay equivalent.
9
9
  *
10
10
  * Endpoints (all GET, internal server-to-server, mirroring the RPCs):
11
11
  * /boot/ping → { serverTimeMs }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * What content an instance owns, and which skills follow from it.
3
+ *
4
+ * An instance is a set of maps plus the content bound to them. Everything else it needs —
5
+ * atlases to draw, skills to fire — follows from the item ids that content names. Deriving
6
+ * that set in one place is what keeps the export, the capture, the boot payload and the
7
+ * editor's report agreeing: they used to each answer "what belongs to this world" slightly
8
+ * differently, and a skill would export but not boot, or boot but not export.
9
+ */
10
+
11
+ /** A '$'-prefixed id is a runtime placeholder the simulation resolves, never a document. */
12
+ const isMaterialItemId = (itemId) => typeof itemId === 'string' && itemId.length > 0 && !itemId.startsWith('$');
13
+
14
+ /**
15
+ * Every ObjectLayer item id an instance's own content names.
16
+ *
17
+ * Four sources, and they are the whole list: what its maps place, what its entity-type
18
+ * defaults wire, what its vendors and assemblers trade, and what its quests ask for or pay
19
+ * out. A skill trigger is not a source — skills are selected from this set, not folded into
20
+ * it — and neither is another instance's content.
21
+ *
22
+ * @param {object} content
23
+ * @param {Array<{entities?: Array<{objectLayerItemIds?: string[]}>}>} [content.maps]
24
+ * @param {Array<object>} [content.entityDefaults] - Resolved documents, not references.
25
+ * @param {Array<object>} [content.actions] - Vendor / assembler catalogs.
26
+ * @param {Array<object>} [content.quests]
27
+ * @returns {Set<string>} Item ids, placeholders excluded.
28
+ */
29
+ function collectInstanceItemIds({ maps = [], entityDefaults = [], actions = [], quests = [] } = {}) {
30
+ const itemIds = new Set();
31
+ const push = (itemId) => {
32
+ if (isMaterialItemId(itemId)) itemIds.add(itemId);
33
+ };
34
+
35
+ for (const map of maps) {
36
+ for (const entity of map?.entities || []) (entity?.objectLayerItemIds || []).forEach(push);
37
+ }
38
+ for (const entityDefault of entityDefaults) {
39
+ for (const list of ['liveItemIds', 'deadItemIds', 'dropItemIds', 'inventoryItemsIds']) {
40
+ (entityDefault?.[list] || []).forEach(push);
41
+ }
42
+ }
43
+ for (const action of actions) {
44
+ for (const shopItem of action?.shopItems || []) {
45
+ push(shopItem?.itemId);
46
+ push(shopItem?.priceItemId);
47
+ }
48
+ for (const recipe of action?.craftRecipes || []) {
49
+ (recipe?.ingredients || []).forEach((ingredient) => push(ingredient?.itemId));
50
+ (recipe?.outputItems || []).forEach((output) => push(output?.itemId));
51
+ }
52
+ }
53
+ for (const quest of quests) {
54
+ for (const step of quest?.steps || []) {
55
+ for (const objective of step?.objectives || []) push(objective?.itemId);
56
+ }
57
+ for (const reward of quest?.rewards || []) push(reward?.itemId);
58
+ }
59
+ return itemIds;
60
+ }
61
+
62
+ /**
63
+ * The skills an instance runs: one per trigger item its content names.
64
+ *
65
+ * This is the whole membership rule, and it is why `hatchet` belongs to a world whose maps
66
+ * place no hatchet — a quest objective or a vendor's shelf names it, so an entity there can
67
+ * come to hold one and fire it. A skill for a trigger nothing in the world names is
68
+ * unreachable and is left out.
69
+ *
70
+ * @param {Array<{triggerItemId?: string}>} skills - Candidate CyberiaSkill documents.
71
+ * @param {Set<string>|string[]} itemIds - {@link collectInstanceItemIds} output.
72
+ * @returns {Array<object>} The subset that belongs, in the order given.
73
+ */
74
+ function selectInstanceSkills(skills = [], itemIds = new Set()) {
75
+ const owned = itemIds instanceof Set ? itemIds : new Set(itemIds);
76
+ return skills.filter((skill) => skill?.triggerItemId && owned.has(skill.triggerItemId));
77
+ }
78
+
79
+ /**
80
+ * Item ids the given skills summon, which need atlases of their own.
81
+ *
82
+ * Folded back into the instance's item set after selection, never before: a summoned bullet
83
+ * is drawn by the world, but it does not make the world own a skill that summons it.
84
+ *
85
+ * @param {Array<{skills?: Array<{summonedEntityItemId?: string}>}>} skills
86
+ * @returns {Set<string>}
87
+ */
88
+ function collectSummonedItemIds(skills = []) {
89
+ const itemIds = new Set();
90
+ for (const skill of skills) {
91
+ for (const definition of skill?.skills || []) {
92
+ if (isMaterialItemId(definition?.summonedEntityItemId)) itemIds.add(definition.summonedEntityItemId);
93
+ }
94
+ }
95
+ return itemIds;
96
+ }
97
+
98
+ export { collectInstanceItemIds, collectSummonedItemIds, isMaterialItemId, selectInstanceSkills };