@underpostnet/cyberia 3.3.73 → 3.3.76

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 (228) hide show
  1. package/.env.example +17 -5
  2. package/.github/workflows/coverall.cyberia.ci.yml +5 -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/publish.cyberia.ci.yml +44 -0
  10. package/AGENTS.md +61 -12
  11. package/CHANGELOG.md +126 -41
  12. package/CLI-HELP.md +19 -12
  13. package/Dockerfile +1 -1
  14. package/Dockerfile.dev +1 -1
  15. package/Dockerfile.test +2 -2
  16. package/README.md +28 -28
  17. package/bin/build.js +37 -17
  18. package/bin/cyberia.js +1247 -1373
  19. package/bin/index.js +1247 -1373
  20. package/compose.env +17 -5
  21. package/conf.js +7 -3
  22. package/deploy/cyberia-client/deploy.sh +10 -11
  23. package/deploy/cyberia-client/package.sh +0 -1
  24. package/deploy/cyberia-client/state.sh +3 -2
  25. package/deploy/cyberia-server/deploy.sh +10 -11
  26. package/deploy/cyberia-server/package.sh +0 -1
  27. package/deploy/cyberia-server/state.sh +3 -2
  28. package/deploy/dd-cyberia/deploy.sh +5 -5
  29. package/deploy/dd-cyberia/init.sh +15 -14
  30. package/deploy/dd-cyberia/package.sh +0 -1
  31. package/deploy/dd-cyberia/state.sh +2 -2
  32. package/deploy/dd-cyberia/sync-deploy.sh +139 -75
  33. package/deploy/lib/config.sh +29 -0
  34. package/deploy/lib/github-actions-logging.sh +92 -76
  35. package/deploy/lib/host.sh +27 -18
  36. package/deploy/lib/state.sh +4 -1
  37. package/deploy/pwa-microservices-template/deploy.sh +10 -8
  38. package/deploy/release/deploy.sh +0 -2
  39. package/deployment.yaml +1 -1
  40. package/docker-compose.yml +23 -17
  41. package/hardhat/package-lock.json +2 -2
  42. package/hardhat/package.json +1 -1
  43. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  44. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
  45. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
  46. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  47. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  48. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  49. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  50. package/manifests/deployment/mongo-express/service.yaml +16 -0
  51. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  52. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  53. package/package.json +19 -17
  54. package/scripts/test-monitor.sh +3 -3
  55. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
  56. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  57. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  58. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
  59. package/src/api/cyberia-action/cyberia-action.router.js +2 -2
  60. package/src/api/cyberia-action/cyberia-action.service.js +3 -4
  61. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  62. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  63. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  64. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  65. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
  66. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  67. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
  68. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
  69. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  70. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  71. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  72. package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
  73. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  74. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  75. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
  76. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
  77. package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
  78. package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
  79. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  80. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  81. package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
  82. package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
  83. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  84. package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
  85. package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
  86. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  87. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  88. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
  89. package/src/api/cyberia-map/cyberia-map.service.js +6 -2
  90. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  91. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  92. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  93. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  94. package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
  95. package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
  96. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  97. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
  98. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  99. package/src/api/file/file.ref.js +153 -0
  100. package/src/api/file/file.ref.json +11 -2
  101. package/src/api/object-layer/object-layer.model.js +47 -12
  102. package/src/api/object-layer/object-layer.router.js +10 -3
  103. package/src/api/object-layer/object-layer.service.js +113 -120
  104. package/src/cli/app.js +12 -1
  105. package/src/cli/client.js +2 -2
  106. package/src/cli/cluster.js +7 -0
  107. package/src/cli/deploy.js +4 -4
  108. package/src/cli/docker-compose.js +1 -1
  109. package/src/cli/fs-selection.js +115 -0
  110. package/src/cli/fs.js +158 -342
  111. package/src/cli/index.js +22 -10
  112. package/src/cli/repository.js +28 -0
  113. package/src/cli/run.js +71 -197
  114. package/src/cli/test.js +4 -0
  115. package/src/cli/wireguard.js +32 -31
  116. package/src/client/components/core/Docs.js +77 -100
  117. package/src/client/components/core/Modal.js +56 -1
  118. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  119. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  120. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  121. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  122. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  123. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  124. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  125. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  126. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  127. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
  128. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  129. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  130. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  131. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  132. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
  133. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  134. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  135. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  136. package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
  137. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  138. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  139. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  140. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  141. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  142. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  143. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  144. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  145. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  146. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  147. package/src/client/services/object-layer/object-layer.management.js +6 -10
  148. package/src/client/services/object-layer/object-layer.service.js +3 -1
  149. package/src/client-builder/client-build-docs.js +52 -52
  150. package/src/client-builder/client-build.js +67 -44
  151. package/src/client-builder/client-bundle.js +347 -0
  152. package/src/db/mongo/MongoExpress.js +187 -0
  153. package/src/grpc/cyberia/grpc-server.js +5 -7
  154. package/src/index.js +1 -1
  155. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
  156. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  157. package/src/projects/cyberia/besu-genesis-generator.js +5 -14
  158. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  159. package/src/projects/cyberia/catalog-cyberia.js +3 -2
  160. package/src/projects/cyberia/gemini-client.js +3 -3
  161. package/src/projects/cyberia/generate-saga.js +4 -11
  162. package/src/projects/cyberia/hot-reload-trigger.js +17 -28
  163. package/src/projects/cyberia/instance-backup.js +210 -0
  164. package/src/projects/cyberia/instance-data.js +177 -257
  165. package/src/projects/cyberia/map-preview-generator.js +32 -68
  166. package/src/projects/cyberia/object-layer.js +152 -319
  167. package/src/projects/cyberia/seed-audio.js +139 -0
  168. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  169. package/src/projects/cyberia/semantic-layer-generator.js +2 -18
  170. package/src/projects/cyberia/stat-balance.js +81 -0
  171. package/src/projects/cyberia/stat-commands.js +23 -0
  172. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  173. package/src/runtime/cyberia-client/Dockerfile +8 -4
  174. package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
  175. package/src/runtime/cyberia-server/Dockerfile +9 -2
  176. package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
  177. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  178. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  179. package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
  180. package/src/runtime/engine-cyberia/compose.env +17 -5
  181. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  182. package/src/server/build/coverage.js +113 -42
  183. package/src/server/build/package.js +55 -12
  184. package/src/server/build/testing.js +59 -9
  185. package/src/server/network/middlewares.js +8 -2
  186. package/src/server/ops/logger.js +70 -35
  187. package/src/server/runtime/conf.js +4 -2
  188. package/src/server/storage/data-query.js +16 -0
  189. package/src/server/storage/downloader.js +62 -14
  190. package/src/server/storage/zip.js +153 -0
  191. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  192. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  193. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
  194. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  195. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  196. package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
  197. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  198. package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
  199. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  200. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  201. package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
  202. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  203. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  204. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  205. package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
  206. package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
  207. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  208. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  209. package/test/unit/client-build-docs.test.js +97 -70
  210. package/test/unit/client-bundle.test.js +313 -0
  211. package/test/unit/conf-resolution.test.js +17 -0
  212. package/test/unit/coverage-artifact.test.js +120 -29
  213. package/test/unit/cyberia/instance-backup.test.js +183 -0
  214. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  215. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  216. package/test/unit/cyberia/stat-balance.test.js +69 -0
  217. package/test/unit/deploy-log-table.test.js +76 -5
  218. package/test/unit/downloader-integrity.test.js +98 -0
  219. package/test/unit/file-reference-registry.test.js +141 -0
  220. package/test/unit/fs-storage-paths.test.js +596 -0
  221. package/test/unit/logger-redaction.test.js +29 -0
  222. package/test/unit/package.test.js +70 -16
  223. package/test/unit/prepare-host.test.js +84 -1
  224. package/test/unit/test-tiers.test.js +27 -0
  225. package/test/unit/zip-archive.test.js +142 -0
  226. package/vitest.config.js +11 -3
  227. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  228. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
@@ -30,10 +30,6 @@ class TranslateCyberiaPortal {
30
30
  en: 'Cyberia Entity Engine',
31
31
  es: 'Cyberia Entity Engine',
32
32
  };
33
- Translate.Data['fallback-world-engine'] = {
34
- en: 'Fallback World Engine',
35
- es: 'Fallback World Engine',
36
- };
37
33
  Translate.Data['instance-selection'] = {
38
34
  en: 'Select World',
39
35
  es: 'Seleccionar Mundo',
@@ -9,8 +9,8 @@ Underpost Platform provides the toolchain, deployment surface, PWA delivery, and
9
9
  ## Process model
10
10
 
11
11
  ```
12
- ┌─────────────────────────────────────────────────────────────────────────┐
13
- │ UNDERPOST PLATFORM (infra · toolchain · deploy · PWA/Workbox)
12
+ ┌──────────────────────────────────────────────────────────────────────────┐
13
+ │ UNDERPOST PLATFORM (infra · toolchain · deploy · PWA/Workbox)
14
14
  │ │
15
15
  │ ┌─────────────────────┐ │
16
16
  │ │ Persistent backend │ ← Cyberia content authority + asset backend │
@@ -38,9 +38,9 @@ Underpost Platform provides the toolchain, deployment surface, PWA delivery, and
38
38
  │ └─────────────────────┘ │
39
39
  │ │ │
40
40
  │ │ REST (atlas frames, asset metadata, client hints, │
41
- │ │ instance-map static presence + capability activity)
41
+ │ │ instance-map static presence + capability activity)
42
42
  │ └──→ engine-cyberia │
43
- └─────────────────────────────────────────────────────────────────────────┘
43
+ └──────────────────────────────────────────────────────────────────────────┘
44
44
  ```
45
45
 
46
46
  Three processes, strict role separation. The ecosystem is fully operational only when all three are running and healthy at the same time.
@@ -62,7 +62,7 @@ What it owns:
62
62
  - gRPC `CyberiaDataService` for world load and content streaming.
63
63
  - REST boot fallback (`/api/cyberia-instance/boot/*`): the same world-load / hot-reload payloads as the gRPC service, served over REST for deploys where the engine gRPC server is not enabled.
64
64
  - REST APIs for assets and the optional client-hints overrides.
65
- - Instance Map REST (`/api/cyberia-instance/instance-map/:code/{static,dynamic}`): static map topology plus authored presence POIs, `sumStatsLimit`-capped baseline ObjectLayer stat sums, and capability membership; the dynamic response supplies only per-player capability activity. Never live positions or simulation stats — those stay client-side.
65
+ - Instance Map REST (`/api/cyberia-instance/instance-map/:code/{static,dynamic}`): static map topology plus authored presence POIs, and capability membership; the dynamic response supplies only per-player capability activity. Never live positions or simulation stats — those stay client-side.
66
66
  - Static content distribution + Cloudinary-backed asset flow.
67
67
  - Editor and CLI integration for content workflows.
68
68
 
@@ -125,7 +125,7 @@ The three processes are supervised independently. Each service owns its own moni
125
125
 
126
126
  Dependency between services is handled by supervision and reconnect loops:
127
127
 
128
- - `cyberia-server` dials `engine-cyberia` gRPC at boot; on dial or load failure it retries over the REST boot fallback (`ENGINE_API_BASE_URL`, `/api/cyberia-instance/boot/*`) and exits only when both transports fail rather than fabricate a world. On reconnect, it reloads world configuration.
128
+ - `cyberia-server` dials `engine-cyberia` gRPC at boot; on dial or load failure it retries over the REST boot fallback (`--data-server-url`, `/api/cyberia-instance/boot/*`) and exits only when both transports fail rather than fabricate a world. On reconnect, it reloads world configuration.
129
129
  - `cyberia-client` reconnects to `cyberia-server` over WebSocket and re-fetches content from `engine-cyberia` over REST independently.
130
130
  - If any one of the three services goes unhealthy, the game moves to standby until all three recover.
131
131
 
@@ -238,18 +238,18 @@ WS frame (binary) → decode → typed InputCommand{kind, clientTick, sequen
238
238
 
239
239
  `InputCommand.Sequence` is monotonic per client. Every snapshot carries two acknowledgements of it, and they are not interchangeable:
240
240
 
241
- | Field | Meaning | Client use |
242
- | --------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
243
- | `ack` | Highest sequence **received** for this player. | Retires commands from the prediction buffer. |
244
- | `moveAck` | Highest `PlayerAction` sequence that **re-planned movement**. | Gates adoption of the authoritative `targetPos` / `path`. |
241
+ | Field | Meaning | Client use |
242
+ | --------- | ------------------------------------------------------------- | --------------------------------------------------------- |
243
+ | `ack` | Highest sequence **received** for this player. | Retires commands from the prediction buffer. |
244
+ | `moveAck` | Highest `PlayerAction` sequence that **re-planned movement**. | Gates adoption of the authoritative `targetPos` / `path`. |
245
245
 
246
- **Movement re-plans once per player per tick, and nothing else throttles it.** Handlers record the tap's destination; `phaseInput` runs one A* per player after the queue is drained. Taps that land in the same tick describe the same instant, so only the newest is planned — the rest were superseded before they could mean anything. That coalescing is the only bound on pathfinder cost.
246
+ **Movement re-plans once per player per tick, and nothing else throttles it.** Handlers record the tap's destination; `phaseInput` runs one A\* per player after the queue is drained. Taps that land in the same tick describe the same instant, so only the newest is planned — the rest were superseded before they could mean anything. That coalescing is the only bound on pathfinder cost.
247
247
 
248
248
  This is why the two acknowledgements diverge: a superseded tap is acked on arrival and never planned, so `self.path` and `self.targetPos` can still describe an earlier command. A client that adopted the route on `ack` alone would turn back toward the abandoned target — worst during rapid changes of direction, where it reads as input lag and as a walk that sets off the wrong way.
249
249
 
250
250
  Skills are not coalesced: they fire on every accepted tap, so the uplink carries every tap and the inbound rate limiter (`DefaultMessageRate`, 30/s) is what bounds the stream.
251
251
 
252
- The client predicts every tap immediately and never waits on a cadence. `self.actionCooldownMs` is the skill-trigger period only; keyboard steering paces its *refresh* by it, while a change of heading emits at once.
252
+ The client predicts every tap immediately and never waits on a cadence. `self.actionCooldownMs` is the skill-trigger period only; keyboard steering paces its _refresh_ by it, while a change of heading emits at once.
253
253
 
254
254
  ---
255
255
 
@@ -284,8 +284,8 @@ Every Cyberia document uses the same terms. Aliases are not permitted.
284
284
  | **tick rate** | Simulation Hz on `cyberia-server`. |
285
285
  | **snapshot** | AOI-filtered world view at one tick for one player. |
286
286
  | **prediction** | Optimistic local apply of input commands to the predicted self entity. |
287
- | **reconciliation** | Correct prediction by the error measured at one tick: authoritative position minus the position predicted for that tick. Input is a destination, not a per-tick impulse, so the client keeps walking toward the `targetPos` that `moveAck` confirms, rather than replaying a command log. |
288
- | **move coalescing** | One movement re-plan per player per tick, from the newest tap of that tick. The only bound on pathfinder cost, and the reason `moveAck` trails `ack`. |
287
+ | **reconciliation** | Correct prediction by the error measured at one tick: authoritative position minus the position predicted for that tick. Input is a destination, not a per-tick impulse, so the client keeps walking toward the `targetPos` that `moveAck` confirms, rather than replaying a command log. |
288
+ | **move coalescing** | One movement re-plan per player per tick, from the newest tap of that tick. The only bound on pathfinder cost, and the reason `moveAck` trails `ack`. |
289
289
  | **display smoothing** | Per-render-frame exponential lerp from the discrete predicted self position to a continuous on-screen position. Decouples the visible main player from sim-tick boundaries. |
290
290
  | **interpolation** | Render-time smoothing of remote entities, sampled from snapshot history. |
291
291
  | **authoritative server** | `cyberia-server`. Sole authority on world state. |
@@ -295,7 +295,7 @@ Every Cyberia document uses the same terms. Aliases are not permitted.
295
295
  | **presentation metadata** | Render-only data. Client-owned. |
296
296
  | **input command** | Typed client→server frame with kind, clientTick, sequence, payload. |
297
297
  | **AOI** | Area of interest — the spatial filter that defines which entities a given player receives. |
298
- | **Instance Map** | Client strategic overlay of packed map tiles and authored presence POIs. The static REST response supplies topology, presence, baseline ObjectLayer stat sums, and capability membership; the dynamic response supplies per-player capability activity. Live player presence and stats remain client-side. |
298
+ | **Instance Map** | Client strategic overlay of packed map tiles and authored presence POIs. The static REST response supplies topology, presence, and capability membership; the dynamic response supplies per-player capability activity. Live player presence and stats remain client-side. |
299
299
  | **replication** | Production and delivery of snapshots from server to clients. |
300
300
  | **simulation phase** | A named step inside one simulation tick. |
301
301
  | **healthy** | All three Cyberia services up and connected; game is playable. |
@@ -35,28 +35,38 @@ Import PNG assets, generate procedural layers, build atlas sprite sheets, push t
35
35
  cyberia ol [item-id] [options]
36
36
  ```
37
37
 
38
- | Option | Description |
39
- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ |
40
- | `--import` | Import specific item-id(s), comma-separated, from the asset directory |
41
- | `--import-types [types]` | Batch import by type (e.g. `skin,floors`) or `all` |
42
- | `--generate` | Generate procedural layers from a semantic item-id (e.g. `floor-desert`) |
43
- | `--count <n>` / `--density <0..1>` | Shape count multiplier (default `3`) / density (default `0.5`) |
44
- | `--seed <seed>` | Deterministic seed for `--generate` (e.g. `fx-42`) |
45
- | `--frame-index <n>` / `--frame-count <n>` | Start frame (default `0`) / frame count (default `1`) |
46
- | `--to-atlas-sprite-sheet [dim]` | Build a consolidated atlas PNG for the item |
47
- | `--show-frame [dir_frame]` | View one frame (e.g. `08_0`; default `08_0`) |
48
- | `--show-atlas-sprite-sheet` | Display the atlas PNG for the item |
49
- | `--drop` | Drop existing data before importing (or standalone) |
50
- | `--client-public` / `--git-clean` | With `--drop`: also remove static asset folders / run clean |
51
- | `--env-path <path>` · `--mongo-host <host>` · `--dev` · `--storage-file-path <path>` | env / DB / dev / filter overrides |
38
+ | Option | Description |
39
+ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
40
+ | `--import` | Import specific item-id(s), comma-separated; needs one source below |
41
+ | `--instance <code>` | Source `--import` from that instance backup under `engine-private` |
42
+ | `--from-directory` | Source `--import` / `--import-types` from the asset directory |
43
+ | `--import-types [types]` | Batch import by type (e.g. `skin,floors`) or `all`; needs `--from-directory` |
44
+ | `--generate` | Generate procedural layers from a semantic item-id (e.g. `floor-desert`) |
45
+ | `--count <n>` / `--density <0..1>` | Shape count multiplier (default `3`) / density (default `0.5`) |
46
+ | `--seed <seed>` | Deterministic seed for `--generate` (e.g. `fx-42`) |
47
+ | `--frame-index <n>` / `--frame-count <n>` | Start frame (default `0`) / frame count (default `1`) |
48
+ | `--to-atlas-sprite-sheet [dim]` | Rebuild both atlas renders for the selected items |
49
+ | `--minify` | Refresh the minified render and the idle still; `--instance` narrows it |
50
+ | `--upscale <px-factor>` | Pixels per cell of the human-resolution render; alone, rebuilds it |
51
+ | `--normalize-stats` | Clamp the stats of every layer the action writes to its item type's bounds |
52
+ | `--random-stats` | Regenerate the stats of every layer the action writes at random |
53
+ | `--min-stat <n>` / `--max-stat <n>` | Narrow the range the two flags above may leave (default `-100`/`100`) |
54
+ | `--show-frame [dir_frame]` | View one frame (e.g. `08_0`; default `08_0`) |
55
+ | `--show-atlas-sprite-sheet` | Display the atlas PNG for the item |
56
+ | `--drop` | Drop existing data before importing (or standalone) |
57
+ | `--client-public` / `--git-clean` | With `--drop`: also remove static asset folders / run clean |
58
+ | `--env-path <path>` · `--mongo-host <host>` · `--dev` | env / DB / dev overrides |
52
59
 
53
60
  ```bash
54
- # Import specific items
55
- cyberia ol hatchet,sword --import --env-path ./engine-private/conf/dd-cyberia/.env.development
61
+ # Restore specific items from an instance backup: the backup is the authority for the item
62
+ cyberia ol hatchet,sword --instance FOREST --import
56
63
 
57
- # Batch import by type, or everything
58
- cyberia ol --import-types skin,floors
59
- cyberia ol --import-types all
64
+ # Import specific items from the asset directory
65
+ cyberia ol hatchet,sword --from-directory --import --env-path ./engine-private/conf/dd-cyberia/.env.development
66
+
67
+ # Batch import by type, or everything, from the asset directory
68
+ cyberia ol --from-directory --import-types skin,floors
69
+ cyberia ol --from-directory --import-types all
60
70
 
61
71
  # Procedural generation
62
72
  cyberia ol floor-desert --generate --seed fx-42
@@ -66,16 +76,29 @@ cyberia ol floor-grass --generate --frame-count 4 --count 5 --density 0.7
66
76
  cyberia ol hatchet --to-atlas-sprite-sheet
67
77
  cyberia ol hatchet --show-frame 08_0
68
78
 
79
+ # Rebuild both atlas renders. The scope is an item-id, an instance, or everything.
80
+ cyberia ol hatchet --to-atlas-sprite-sheet --upscale 40
81
+ cyberia ol --instance TEST --upscale 20
82
+ cyberia ol --to-atlas-sprite-sheet
83
+
84
+ # Refresh the minified render the client downloads and the idle still every preview shows
85
+ cyberia ol hatchet --minify
86
+ cyberia ol --minify --instance FOREST
87
+
88
+ # Balance stats on every layer an action writes
89
+ cyberia ol --minify --instance FOREST --normalize-stats
90
+ cyberia ol hatchet --from-directory --import --random-stats --normalize-stats --max-stat 10
91
+
69
92
  # Drop + re-import a single item, including static folders
70
- cyberia ol hatchet --drop --client-public --import
93
+ cyberia ol hatchet --drop --client-public --from-directory --import
71
94
  ```
72
95
 
73
96
  ---
74
97
 
75
98
  ## `cyberia instance` — instance data
76
99
 
77
- Export / import / drop a game instance and its related maps, entities, actions, quests, and object
78
- layers in MongoDB.
100
+ Export / import / drop a game instance and its related maps, entities, actions, quests, object
101
+ layers and map audio in MongoDB.
79
102
 
80
103
  ```bash
81
104
  cyberia instance [instance-code] [options]
@@ -87,6 +110,7 @@ cyberia instance [instance-code] [options]
87
110
  | `--import [path]` | Import from a backup directory (upsert, preserves UUIDs) |
88
111
  | `--conf` | With `--export`/`--import`: only `cyberia-instance.json` + `-conf.json` |
89
112
  | `--drop` | Drop all documents associated with the instance code |
113
+ | `--sync-entities` | Sync the conf's entity-type default references and skill config |
90
114
  | `--export-current-fallbackworld` | Capture the in-memory procedural fallback world, then export it |
91
115
  | `--keep-fallback-codes` | Capture using the raw `fallback-map-*` / canonical action-quest codes |
92
116
  | `--fallback-url <url>` | Capture the world a running engine serves instead of regenerating it |
@@ -98,6 +122,102 @@ cyberia instance FOREST --import ./backups/FOREST
98
122
  cyberia instance FOREST --drop
99
123
  ```
100
124
 
125
+ A backup carries audio too. `cyberia-map-audio-confs/<map-code>.json` holds each map's bindings —
126
+ that configuration belongs to the map, so it travels with the instance and `--drop` removes it with
127
+ the map. The assets those bindings name travel as copies in `cyberia-audio/<code>.json` with their
128
+ WAV beside them in `files/`, because a `CyberiaAudio` document is global: the import upserts it by
129
+ code and leaves other instances' bindings alone, and `--drop` never removes one. A binding whose
130
+ asset is missing is kept as written — the code is the whole reference, so importing the asset later
131
+ is all it takes to make it play.
132
+
133
+ A backup also carries the entity-type defaults the instance's conf **references**, under
134
+ `cyberia-entity-type-defaults/<_id>.json`. `CyberiaInstanceConf.entityDefaults` holds
135
+ `CyberiaEntityTypeDefault` ids, never copies of the documents, and both directions travel by that
136
+ id: the export writes exactly the referenced documents, and the import restores them under their
137
+ original `_id` so the references keep resolving.
138
+
139
+ `--sync-entities` builds those references from the world's own content: a default belongs to the
140
+ instance when all of its `liveItemIds` appear together on some entity the maps place — the same
141
+ subset containment the runtime resolves an entity with. It is additive and idempotent, so a
142
+ hand-linked default that no map places (a player or coin default) is kept, and re-running links
143
+ nothing new. A match another instance already references is **reported, not adopted**: two worlds
144
+ built on the same art hold documents with identical live item ids, so matching alone cannot tell
145
+ them apart, and claiming one stays a deliberate act in the Entity engine.
146
+
147
+ Skills are **derived, never stored**. The `cyberia-skill` collection is deployment-wide and owns
148
+ the definitions; an instance runs the ones whose `triggerItemId` is an item id its own content
149
+ names. That content is four things and no more: what its maps place, what its entity-type defaults
150
+ wire, what its vendor and assembler catalogs trade, and what its quests ask for or pay out. The
151
+ canonical `ENTITY_TYPE_DEFAULTS` count alongside the instance's own, so a world referencing no
152
+ player default still holds `atlas_pistol_mk2` and keeps its projectile skill rather than being
153
+ disarmed.
154
+
155
+ That last source is why `hatchet` belongs to a world whose maps place no hatchet: a quest objective
156
+ or a shop shelf names one, so a player there can come to hold it and fire it. `--sync-entities`
157
+ reports the resolved list, the export writes exactly those documents to
158
+ `cyberia-skills/`, and the boot payload sends exactly those to the simulation — one rule, so the
159
+ three can no longer disagree. Nothing is written to the instance conf, which carries no
160
+ `skillConfig` field: a stored list was a second answer to the same question, and it was the one
161
+ that went stale.
162
+
163
+ The Instance engine has the same action as a button, syncing against the map codes currently
164
+ selected.
165
+
166
+ ```bash
167
+ cyberia instance FOREST --sync-entities --dev
168
+ ```
169
+
170
+ A reference cannot outlive its document. Deleting an entity-type default unlinks it from every
171
+ conf first, and both `--export` and `--import` compact the instance's references — dropping any
172
+ whose document is gone — so a backup never carries a dangling id and restoring one never recreates
173
+ it. Saving a default in the Entity engine compacts the collection too. What is exported is
174
+ therefore exactly what resolves.
175
+
176
+ That reference is what scopes a default to a world. Membership used to be inferred by matching item
177
+ ids, and two instances built on the same art therefore matched each other: exporting one dragged in
178
+ the other's wiring, and importing it overwrote the original by an `(entityType, liveItemIds)`
179
+ "natural key". An id names one document, so neither is possible. A backup written before the change
180
+ still embeds the documents in its conf; importing it converts them to references against the
181
+ documents in that same backup, creating any it cannot find.
182
+
183
+ An instance that references nothing is complete, not empty: it runs on the canonical
184
+ `ENTITY_TYPE_DEFAULTS`, and referenced documents override only the entity types they cover.
185
+
186
+ Those documents are also where a world's starting inventory comes from, and an entity has exactly
187
+ one. It carries the **union** of every id its default names — `liveItemIds`, `deadItemIds`,
188
+ `dropItemIds` and the inventory-only `inventoryItemsIds` — deduplicated. Nothing stores which slots
189
+ are worn: the three lifecycle lists are discriminators, and the runtime activates the ones the
190
+ context calls for, which is why the whole union is seeded (the server activates a slot that is
191
+ already there rather than appending one). Spawn state is alive, so the live ids are the active
192
+ ones; everything else is carried empty, a coin balance included.
193
+
194
+ `overrideItemsIdsState` is the one adjustment to that derivation, per id: `active` forces the spawn
195
+ state — a skin the equipment rules would otherwise leave inactive — `quantity` sizes a stack,
196
+ which is how a drop bundle declares how many tokens it scatters, and `dropChance` (0–1) says how
197
+ often the id actually scatters when the entity dies. It never adds an id; the union decides
198
+ membership, an override only what a member starts as.
199
+
200
+ `dropChance` is meaningful only for an id the build carries in `dropItemIds`; on any other row it is
201
+ inert, and the resolver reports 1 there so the simulation reads one field and never a missing one.
202
+ Saying nothing means 1, which is what every world did before the field existed, and each drop id is
203
+ rolled independently — a build can pair a common drop with a rare one and have both decided on their
204
+ own. A deliberate 0 survives, because the wire field is explicitly optional: absent and zero are
205
+ different answers.
206
+
207
+ `inventoryItemsIds` is therefore only for what no lifecycle state ever activates. The instance holds
208
+ no item list of its own: it names entity-type defaults, and the items follow from them, so there is
209
+ exactly one place to read or change a starting kit.
210
+
211
+ To point a world at the seeded collection, name it:
212
+
213
+ ```bash
214
+ cyberia run-workflow seed-entities --instance FOREST --dev
215
+ ```
216
+
217
+ `seed-entities` upserts `ENTITY_TYPE_DEFAULTS` into the collection; `--instance` then makes that
218
+ instance's conf reference exactly those documents, replacing whatever it referenced before, so
219
+ re-running converges rather than accumulating.
220
+
101
221
  ### Capturing the procedural fallback world
102
222
 
103
223
  The fallback world is never persisted: every engine process rebuilds it from the code defaults at
@@ -105,7 +225,9 @@ boot and serves it whenever a requested instance is absent. `--export-current-fa
105
225
  that in-memory world into MongoDB under a real instance code — maps and portal topology, the
106
226
  instance conf, and the content collections the fallback path serves from code rather than the DB
107
227
  (skills, entity-type defaults, dialogues, actions, quests) — and then exports it like any other
108
- instance.
228
+ instance. It writes each captured map's audio configuration too, under the captured map code,
229
+ binding only the codes an imported `CyberiaAudio` actually carries and reporting the rest: the
230
+ client asks for audio by map code, so a namespaced capture would otherwise play nothing.
109
231
 
110
232
  ```bash
111
233
  # Freeze the current fallback world as PROC-1 and back it up
@@ -119,7 +241,7 @@ Map, action and quest codes are namespaced under the instance code (`fallback-ma
119
241
  `PROC-1-map-0`) so successive captures never overwrite each other; `--keep-fallback-codes` writes
120
242
  the canonical codes verbatim instead. Sprites are the one thing a capture cannot synthesise: when a
121
243
  referenced item id has no `ObjectLayer` document the command aborts and names the ids to import with
122
- `cyberia ol <ids> --import`. Staged fallback default items live only in the serving engine process,
244
+ `cyberia ol <ids> --from-directory --import`. Staged fallback default items live only in the serving engine process,
123
245
  so pass `--fallback-url http://localhost:4001` to capture a live world rather than regenerating it.
124
246
 
125
247
  ---
@@ -148,6 +270,140 @@ cyberia client-hints cyberia-main --export ./client-hints-cyberia-main.json
148
270
 
149
271
  ---
150
272
 
273
+ ## `cyberia audio` — audio assets and map audio
274
+
275
+ Imports recorded [`cyberia-audio`](https://github.com/underpostnet/cyberia-audio) assets into MongoDB and binds
276
+ them to a map. Every recording is a pair — `<name>.wav` and its `<name>.json` manifest — and the pair is the unit
277
+ of import: a WAV with no manifest beside it is skipped. Produce the pair with `cyberia-audio sfx coin` or
278
+ `cyberia-audio music combat`.
279
+
280
+ ```bash
281
+ cyberia audio [audio-code] [options]
282
+ ```
283
+
284
+ | Option | Description |
285
+ | ----------------------------------------------------- | -------------------------------------------------------------------- |
286
+ | `--import` | Import WAV + manifest pairs; all of them when no code is given |
287
+ | `--records-path <path>` | Records directory to import from (default `./cyberia-audio/records`) |
288
+ | `--map <map-code>` | Target `cyberia-map` code to read or configure |
289
+ | `--set-default-music <audio-code>` | Default background music for `--map` |
290
+ | `--set-event <logic-event-id:audio-code>` | Bind an asset to a logic event (e.g. `combat`, `shoot`); repeatable |
291
+ | `--env-path <path>` · `--mongo-host <host>` · `--dev` | env / DB / dev overrides |
292
+
293
+ ```bash
294
+ # Import every recorded asset, or a single one
295
+ cyberia audio --import
296
+ cyberia audio combat --import
297
+
298
+ # Configure a map, then read back what it resolved to
299
+ cyberia audio --map FOREST --set-default-music exploration \
300
+ --set-event projectile:shoot --set-event coin_drop_or_transaction:coin
301
+ cyberia audio --map FOREST
302
+ ```
303
+
304
+ An asset is identified by its `code` alone — `cyberia-audio` stores what a sound _is_ (`code`, `fileId`,
305
+ `manifest`) and never what it is for. `manifest.bus` is the `src/audio-module/<bus-id>/` directory the module
306
+ was authored in — the asset's natural route, recorded as provenance; a map binding decides where it actually
307
+ plays. Configuration lands in `cyberia-map-audio-conf`, one document per map code,
308
+ holding `defaultMusic`, a single `events` list and volume/loop/crossfade `settings`. Each binding is the same
309
+ pattern the skill model uses — a semantic `logicEventId` resolving to an `audioCode`:
310
+
311
+ ```js
312
+ { logicEventId: 'combat', audioCode: 'combat', settings: { bus: 'music', loop: true, crossfadeMs: 800 } }
313
+ { logicEventId: 'hit', audioCode: 'hit' }
314
+ ```
315
+
316
+ `bus` is one vocabulary with one spelling — `music` and `sfx`, declared once in
317
+ `SharedDefaultsCyberia.AUDIO_BUSES`. The same two ids name the `cyberia-audio/src/audio-module/<bus-id>/`
318
+ directory an asset is authored in, the argument `cyberia-audio <bus-id> <id>` dispatches on, the `bus` a
319
+ recorded manifest carries, and the route a binding's `settings.bus` selects.
320
+
321
+ The event vocabulary is centralized the same way: `SharedDefaultsCyberia.AUDIO_LOGIC_IDS` declares the
322
+ events a binding may answer and the bus each one naturally routes to, `cyberia-server-defaults` holds the seed's
323
+ bank (`DEFAULT_AUDIO_BANK`) and its bindings (`DEFAULT_AUDIO_BINDINGS`, expanded by `buildAudioEventBindings`),
324
+ and `cyberia-client/src/audio/audio_events.h` holds the same ids for the emitting side. A binding naming an
325
+ unknown event, or an asset the bank does not carry, throws at import rather than playing silence.
326
+
327
+ The fallback bank binds `projectile`, `coin_drop_or_transaction`, `drop`, `item-pickup`, `victory`, `level-up`,
328
+ `death`, `heal`, `hit`, `portal`, `ui-click` and `footsteps` as one-shots, and `combat`, `boss`, `portal-cooldown` and
329
+ `craft` as music. `victory` is a cue rather than a bed: completing a quest is an event, and holding the
330
+ bus for it would take the map's music away for the length of a flourish. `hit` follows the server's damage events, so it sounds for any entity in view rather than
331
+ only for the player; `level-up` and `death` arrive as server `audio_event` broadcasts, so a level gained or a defeat sounds for every viewer in reach; `ui-click` follows a tap the interface accepted.
332
+
333
+ `item-pickup` fires where every route into the inventory bar converges — world loot flying in, a quest
334
+ reward, a purchase, an assembly output — so one emission covers them all. It is deliberately plainer than
335
+ `coin`, which stays the reward gesture reserved for currency.
336
+
337
+ `craft` is a held bed rather than a cue, the same shape as `portal-cooldown`: it takes over the map's music
338
+ for exactly as long as a recipe's assembly bar charges, loops seamlessly while the player waits, and releases
339
+ back to the map on completion. Both holds are arbitrated in `audio_context.c`, which is also what makes them
340
+ outlive a combat exchange instead of being cut by it — the modal owns the bar and only reports whether one is
341
+ running.
342
+
343
+ Two cues are about the crowd rather than about one event, because sounding them per entity buries the bus.
344
+ `heal` is a milestone: regeneration ticks constantly and in small amounts, so it sounds only when an entity's
345
+ life crosses back above zero, 25%, 50% or 75%, once per snapshot however many entities crossed. `footsteps`
346
+ is a cadence: one gait cycle repeats for as long as anything with feet is walking in view, at one rate for the
347
+ whole scene rather than one per walker.
348
+
349
+ Portal audio follows the authoritative teleport charge, never the map code: `onPortal` holds `portal-cooldown` as
350
+ a bed for as long as the charge runs, and completing it fires the `portal` one-shot while the departing map's
351
+ bindings are still resident. An intra-map portal moves the player without changing maps, so watching the map code
352
+ was silent for exactly that case; stepping off the pad drops the bed without sounding the jump.
353
+
354
+ Whether a binding behaves as a bed, a one-shot or a transition follows from the logic event that fires it and the
355
+ settings it carries, not from a classification stored twice. The code is checked against the imported assets, so
356
+ an unimported one is rejected rather than stored as a dangling name. Bindings merge by `logicEventId`: naming an
357
+ event replaces that binding and leaves the others in place. `--map` on its own prints the current configuration
358
+ and writes nothing.
359
+
360
+ The fallback seed is the exception, and deliberately so: it states a map's complete binding set, so re-running it
361
+ converges. A binding whose logic event the bank no longer declares — a renamed cue, for instance — is dropped and
362
+ logged, instead of surviving as a name the client would keep asking the engine to resolve.
363
+
364
+ `settings.bus` selects `music` or `sfx` on the binding. It does not classify the asset.
365
+ Omitted settings inherit client or map defaults. Supported overrides include volume, loop, crossfadeMs, pitch, pan, and priority.
366
+ Set music event routing through the map configuration API or the fallback seed workflow.
367
+ Changing only an event's audio code preserves its existing settings.
368
+
369
+ Record and seed the complete fallback bank from the engine repository root:
370
+
371
+ ```bash
372
+ cyberia run-workflow seed-audio --records-only
373
+ cyberia run-workflow seed-audio --dev --mongo-host 127.0.0.1
374
+ cyberia run-workflow seed-audio --instance my-instance --dev --mongo-host 127.0.0.1
375
+ ```
376
+
377
+ The first command records thirteen WAV and manifest pairs and touches no database.
378
+ The second also upserts generic File references, CyberiaAudio metadata, and audio configuration for the fallback maps.
379
+ The third configures one instance's maps instead: `--instance <instance-code>` reads that instance's own
380
+ `cyberiaMapCodes` and scores every one of them with the same bank, bindings and default bed the fallback world
381
+ uses, so a world built on that topology sounds the way the fallback world does. Every map falls back to the
382
+ `exploration` bed; what makes a place sound different is the event that fires there. The instance must exist and
383
+ declare at least one map, or the run fails without writing anything.
384
+
385
+ Every form states each map's whole configuration, so re-running converges rather than accumulating: a bed is
386
+ reassigned and a binding the bank no longer declares is dropped.
387
+ It reuses the existing engine environment resolution. Full seeding requires that deployment configuration and MongoDB.
388
+ Use `--records-path` to select the output directory.
389
+ An asset's generic File `_id` is derived from its code and the bytes themselves, so re-importing an unchanged
390
+ bank rewrites nothing, and a changed render lands on a new `fileId` while the blob it replaced is deleted in the
391
+ same step. That is what keeps the client honest: it fetches a WAV as `/api/file/blob/<fileId>` and caches it, so
392
+ new bytes under a reused id would go on playing the old sound. `cyberia-audio.fileId` is registered in
393
+ `src/api/file/file.ref.json`, which is the list `underpost db clean-fs` treats as the complete set of File
394
+ references — a blob no registered field points at is deleted by that sweep. Deleting an asset, through the API or
395
+ by restoring an instance over it, deletes its blob with it. Interrupted imports can be rerun safely.
396
+
397
+ Recording produces nothing for the client to ship: `cyberia-client` bundles no WAV and fetches every asset from
398
+ engine-cyberia by code, so an asset is reachable only once it is seeded.
399
+
400
+ The client resolves `logicEventId → audioCode → fileId → /api/file/blob/:fileId` and caches decoded WAVs.
401
+ Fallback maps use exploration, combat, boss, and exploration music in order.
402
+ Missing remote content uses the generated local bank. Unknown effects become silence.
403
+ Regenerate the local bank before building the client after changing audio content.
404
+
405
+ ---
406
+
151
407
  ## `cyberia chain` — Besu + ObjectLayerToken
152
408
 
153
409
  Hyperledger Besu IBFT2 network and ERC-1155 `ObjectLayerToken` (CKY) lifecycle.
@@ -217,10 +217,17 @@ Other message types — init data (0x02), FCT (0x04), ItemFCT (0x05) — carry t
217
217
 
218
218
  The client speaks REST directly to engine-cyberia for content. None of these calls go through cyberia-server.
219
219
 
220
+ The atlas blob is the minified render, at one pixel per cell, and the atlas metadata describes that
221
+ same render. The engine keeps a second, human-resolution render for viewing; the client never
222
+ downloads it. The idle still is the first down-idle frame cut out of that render, and is what the
223
+ interact overlay and every engine editor show as an item's picture. `cyberia ol --minify` refreshes
224
+ the minified render and the idle still of stored items.
225
+
220
226
  | Endpoint | Purpose |
221
227
  | -------------------------------------------------------------- | ------------------------------------------ |
222
228
  | `GET /api/atlas-sprite-sheet/metadata/:itemKey` | Frame layout JSON for a sprite atlas |
223
- | `GET /api/atlas-sprite-sheet/blob/:itemKey` | Atlas PNG |
229
+ | `GET /api/atlas-sprite-sheet/blob/:itemKey` | Minified atlas PNG, one pixel per cell |
230
+ | `GET /api/atlas-sprite-sheet/idle-preview/:itemKey` | Down-idle still PNG, the item's picture |
224
231
  | `GET /api/object-layer/:itemId` | ObjectLayer JSON metadata |
225
232
  | `GET /api/cyberia-dialogue/code/default-:itemId` | Dialogue lines for an NPC |
226
233
  | `GET /assets/ui-icons/:iconId.png` | Status-bar icons |
@@ -238,7 +245,8 @@ The Instance Map is a strategic overlay, not a minimap. It visualises the instan
238
245
 
239
246
  One integrated widget with two modes, split across two modules:
240
247
 
241
- - **Toolbar** (`ui/toolbar`) — the top HUD strip: the compact map readout left-aligned, and the right-hand toggle row `[quest][map][fullscreen]`. The quest button shows/hides the Quest Journal (hidden by default); the **Map** toggle (ui-icon `map`) morphs the container to the full screen with an eased transition and retracts it the same way on close, swapping to a close icon while expanded.
248
+ - **Toolbar** (`ui/toolbar`) — the top HUD strip: the compact map readout left-aligned, and the right-hand toggle row `[quest][map][fullscreen]`. The quest button shows/hides the Quest Journal and the map button shows/hides the minimap; both are hidden by default and stay open together. The coordinate readout opens the expanded Instance Map, which morphs to the full screen with an eased transition and retracts the same way on close.
249
+ - **Side stack** (`ui/hud_side_stack`) — the right-hand column both panels live in: the minimap on top, the Quest Journal beneath, between the toolbar and the inventory bar. The column sizes to the room the bar leaves. When both are open the minimap gives way to at most half of the column so the journal keeps room, and the journal takes only what its content needs. Each panel keeps its own state across toggles: the journal its scroll, sections and pages, the minimap its zoom.
242
250
  - **Container** (`ui/modal_map`) — compact mode is the always-on readout (map code, position, fps) hosted inside the toolbar; expanded mode is the full-screen morph target.
243
251
  - **Content** (`ui/modal_instance_map`) — renders the Instance Map inside the container: a non-blocking translucent full-screen panel (the world keeps rendering behind it).
244
252
 
@@ -253,36 +261,50 @@ Strict independence from the gameplay renderer:
253
261
 
254
262
  Data lifecycle:
255
263
 
256
- 1. Opening the overlay fetches the **static** payload once: graph nodes, portal edges, and authored `presencePois` carrying map-cell presence status, `sumStatsLimit`-capped baseline ObjectLayer stat sums, and static action/quest capability membership. The instance code arrives in the simulation server's `metadata` message.
264
+ 1. Opening the overlay fetches the **static** payload once: graph nodes, portal edges, and authored `presencePois` carrying map-cell presence status and static action/quest capability membership. The instance code arrives in the simulation server's `metadata` message.
257
265
  2. While open, the client polls the **dynamic** endpoint (~1/s, `?playerId=`) only for per-player capability activity: `acceptable` / `active` quests and active actions.
258
266
  3. Closing the overlay stops polling immediately; late responses are discarded.
259
267
 
260
268
  Live player position never travels through this API — engine-cyberia holds no simulation state. The overlay marks the player's node (and cell fraction within it) from the client's own predicted position, refreshed every frame.
261
269
 
262
- At normal zoom, POIs render only their authored presence-status icon. At inspection zoom, static action and quest capability icons appear with the authored baseline stat sum; dynamic activity only changes capability emphasis. The local player's live presence and authoritative stats sum remain client-side. Decorative map content without a presence status is not rendered as a POI.
270
+ At normal zoom, POIs render only their authored presence-status icon. At inspection zoom, static action and quest capability icons appear; dynamic activity only changes capability emphasis. The local player's live presence and authoritative stats sum remain client-side. Decorative map content without a presence status is not rendered as a POI.
263
271
 
264
272
  ---
265
273
 
274
+ ## Stats and progression
275
+
276
+ Every living entity's overhead HUD shows `[stats] lv.<level>` and `[stack] <stats sum>` above its nameplate, and a gold XP bar under its HP bar. The local player's bar fills with its progress through the current level and carries an `XP n / m` label in a smaller font; entities whose XP the client cannot read show an empty, unlabeled bar.
277
+
278
+ The Stats tab of the interact modal draws one shared stat panel per block: the stats icon, the signed sum, and a two-column grid with one icon per stat. Every entity gets its effective stats. The local player also gets the OL modifiers, base stats, and temporary modifiers from the self snapshot. The inventory modal draws the same panel for one item.
279
+
280
+ An XP award floats as gold `+N XP` combat text over the earner only. A server level increment triggers a `LEVEL UP` aura: shaded gold sparks stream from the entity's feet up past its head for 3 s under a spinning star and a label, and the whole effect follows the entity as it moves, for any entity in view. First sight, repeated snapshots, and lower levels do not trigger it.
281
+
282
+ A defeat of a player or bot triggers the death cue: shaded red sparks burst out of the body and fall while a `skull` icon rises over it for 2.6 s, following the body. It fires once, when an entity seen alive in the prior snapshot is dead in this one. An entity first seen dead, or still dead, triggers nothing. A resource is extracted, not slain: it goes straight to its extracted state with no cue.
283
+
284
+ While either cue runs, the entity's whole overhead stack, presence icon included, yields the space to it and returns when the cue ends.
285
+
286
+ The cues are visual only. Their sounds arrive as `audio_event` messages the server broadcasts to every player whose area of interest covers the event, so a defeat, an extraction, or a level gained sounds the same for everyone in reach. The client plays the id through the map's audio bindings.
287
+
288
+ Item slots badge an object layer with the `stack` icon and its signed stat sum, red when negative.
289
+
290
+ The server sends six values in canonical order. The client displays these values and uses the supplied movement speed.
291
+
266
292
  ## Build and run
267
293
 
268
294
  ```bash
269
295
  cd cyberia-client
270
296
 
271
- # Development build (defaults: BUILD_MODE=DEBUG, localhost URLs)
297
+ # Development build (BUILD_MODE=DEBUG, localhost websocket URL)
272
298
  make -f Web.mk clean && make -f Web.mk all
273
299
 
274
- # Release build — pass the production URLs explicitly (see note below)
275
- make -f Web.mk clean && make -f Web.mk all BUILD_MODE=RELEASE \
276
- WS_URL=wss://server.cyberiaonline.com/ws \
277
- API_BASE=https://www.cyberiaonline.com
278
-
279
- # Build + serve locally on dev port :8082 (DEBUG, localhost)
280
- ./dev-server.sh # or: ./dev-server.sh <port>
300
+ # Release build
301
+ make -f Web.mk clean && make -f Web.mk all BUILD_MODE=RELEASE
281
302
  ```
282
303
 
283
- `WS_URL` and `API_BASE` are passed straight through to the compiler — see
284
- [Compile-time configuration](#compile-time-configuration). When omitted they
285
- default to `localhost`, so **production build pipelines must pass real URLs**.
304
+ `BUILD_MODE` picks the websocket URL baked into the WASM — see
305
+ [Compile-time configuration](#compile-time-configuration). The Data Server URL
306
+ is not a build argument: the client reads it from its own command line at
307
+ startup.
286
308
 
287
309
  The build is part of the Underpost Platform static + PWA pipeline; production deploys go through `underpost client` and `underpost deploy`.
288
310
 
@@ -300,23 +322,23 @@ bin/
300
322
 
301
323
  ## Compile-time configuration
302
324
 
303
- ### Server URLs (build arguments)
325
+ ### Server URLs
304
326
 
305
- `WS_URL` and `API_BASE` are `make` arguments baked into the WASM as
306
- `-DWS_URL_OVERRIDE` / `-DAPI_BASE_URL_OVERRIDE`. There is **no** RELEASE/DEBUG URL
307
- switch — both default to `localhost` regardless of `BUILD_MODE`:
327
+ `WS_URL` in `src/config.h` holds both websocket endpoints. `BUILD_MODE` picks
328
+ one: DEBUG takes `ws://localhost:8081/ws`, RELEASE takes
329
+ `wss://server.cyberiaonline.com/ws`. `window.CYBERIA_WS_ORIGIN`, injected by
330
+ `wasm-driver.py`, overrides it at runtime.
308
331
 
309
- | make argument | Default | Baked macro |
310
- | ------------- | ------------------------ | ----------------------- |
311
- | `WS_URL` | `ws://localhost:8081/ws` | `WS_URL_OVERRIDE` |
312
- | `API_BASE` | `http://localhost:4005` | `API_BASE_URL_OVERRIDE` |
332
+ The Data Server URL is a command line argument, not a build argument:
333
+
334
+ ```
335
+ wasm-driver.py --data-server-url=<origin>
336
+ -> window.CYBERIA_ARGV -> Module.arguments -> main(argc, argv)
337
+ ```
313
338
 
314
- > **Production pipelines must pass the URLs.** `BUILD_MODE=RELEASE` alone still
315
- > yields `localhost`. Any release build the Docker image build, CI, manual
316
- > release must pass `WS_URL=wss://… API_BASE=https://…` as make arguments
317
- > (or Docker build-args wired through to `make`), or the client ships pointing
318
- > at localhost. If unset, `src/config.h` falls back to bare `"ws://"` /
319
- > `"https://"` stubs.
339
+ `config_init()` reads `--data-server-url=<origin>` from `argv`. The argument is
340
+ required: the client asserts and stops if it is absent. There is no environment
341
+ fallback, no config file and no runtime override.
320
342
 
321
343
  ### Constants (`src/config.h`)
322
344