@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
@@ -100,11 +100,11 @@ security presence, taxes, historical scars — never the subject.
100
100
  To make a faction the **central driver** of the saga, name one or more with
101
101
  `--faction-context` (comma-separated). Valid keys:
102
102
 
103
- | Key | Confederation |
104
- | --------- | ---------------------------------------------- |
105
- | `zenith` | the Zenith Empire (Red) |
106
- | `nova` | the Nova Republic (Blue) |
107
- | `atlas` | the Atlas Confederation (Yellow) |
103
+ | Key | Confederation |
104
+ | --------- | ------------------------------------------------ |
105
+ | `zenith` | the Zenith Empire (Red) |
106
+ | `nova` | the Nova Republic (Blue) |
107
+ | `atlas` | the Atlas Confederation (Yellow) |
108
108
  | `neutral` | unaligned independent enclaves / neutral parties |
109
109
 
110
110
  ```bash
@@ -133,23 +133,23 @@ Two optional flags steer it; **both default to random** when unset:
133
133
  from (unset → a random non-empty subset, so runs vary their cultural mix).
134
134
  Unknown keys warn and are skipped. Valid keys:
135
135
 
136
- | Key | Flavor |
137
- | --- | ------ |
138
- | `low_level_synthetics` | serialized / technical utility-frame designations |
139
- | `high_fidelity_synthetics` | refined, near-human synthetic names |
140
- | `global_latin_diaspora` | Latin / Iberian-descended |
141
- | `east_asian_pacific_diaspora` | East-Asian / Pacific-descended |
142
- | `middle_eastern_turkish_diaspora` | Middle-Eastern / Turkish-descended |
143
- | `sub_saharan_african_diaspora` | Sub-Saharan African-descended |
144
- | `classic_western_scifi` | gritty Anglo operators / enforcers |
145
- | `mutagen_clans` | organic, mutation-flavored clan names |
136
+ | Key | Flavor |
137
+ | --------------------------------- | ------------------------------------------------- |
138
+ | `low_level_synthetics` | serialized / technical utility-frame designations |
139
+ | `high_fidelity_synthetics` | refined, near-human synthetic names |
140
+ | `global_latin_diaspora` | Latin / Iberian-descended |
141
+ | `east_asian_pacific_diaspora` | East-Asian / Pacific-descended |
142
+ | `middle_eastern_turkish_diaspora` | Middle-Eastern / Turkish-descended |
143
+ | `sub_saharan_african_diaspora` | Sub-Saharan African-descended |
144
+ | `classic_western_scifi` | gritty Anglo operators / enforcers |
145
+ | `mutagen_clans` | organic, mutation-flavored clan names |
146
146
 
147
147
  - **`--cultural-exposure <mode>`** — how mixed the population is (unset → random):
148
148
 
149
- | Mode | Effect |
150
- | ---- | ------ |
151
- | `cosmopolitan` | heavy mixing, hybrid surnames, intermarriage — maximizes variety |
152
- | `local` | isolated clans/enclaves, strong local naming traditions, internally consistent |
149
+ | Mode | Effect |
150
+ | -------------- | ------------------------------------------------------------------------------ |
151
+ | `cosmopolitan` | heavy mixing, hybrid surnames, intermarriage — maximizes variety |
152
+ | `local` | isolated clans/enclaves, strong local naming traditions, internally consistent |
153
153
 
154
154
  ```bash
155
155
  # Latin + East-Asian diaspora names in a highly mixed, cosmopolitan population
@@ -166,26 +166,26 @@ node bin/cyberia.js generate-saga --character-context 'mutagen_clans' --cultural
166
166
  The theme commits to one of four broad narrative types — chosen **uniformly
167
167
  (~25% each)** unless forced with `--tone`:
168
168
 
169
- | Tone | Register |
170
- | ----------- | -------------------------------------------------------------- |
171
- | `adventure` | noir high-risk missions: covert ops, sabotage, combat, rogue AI, mystery |
169
+ | Tone | Register |
170
+ | ----------- | ----------------------------------------------------------------------------------------------------------------- |
171
+ | `adventure` | noir high-risk missions: covert ops, sabotage, combat, rogue AI, mystery |
172
172
  | `politics` | power & ideology at any scale — councils, clans, enclaves, revolutions (confederation geopolitics only sometimes) |
173
- | `tragic` | heartbreaking, intimate: family, bonds, loss, grief |
174
- | `comedy` | everyday absurdity and silliness, played for humor |
173
+ | `tragic` | heartbreaking, intimate: family, bonds, loss, grief |
174
+ | `comedy` | everyday absurdity and silliness, played for humor |
175
175
 
176
176
  #### Spatial context (physical vs. hyperspace)
177
177
 
178
178
  Cyberia has two equally important layers — the **Physical Layer** (fleets,
179
179
  colonies, logistics, force) and the **Hyperspace Layer** (persistent Instances,
180
- memory-cities, digital ecosystems). Because the lore is titled *The Frontier of
181
- Hyperspace*, an unconstrained model drifts to hyperspace-only premises. So the
180
+ memory-cities, digital ecosystems). Because the lore is titled _The Frontier of
181
+ Hyperspace_, an unconstrained model drifts to hyperspace-only premises. So the
182
182
  auto-theme picks a spatial context **explicitly and uniformly (~33.3% each)**:
183
183
 
184
- | Context | Premise lives in… |
185
- | ------------ | -------------------------------------------------------------- |
186
- | `physical` | the material frontier only — no hyperspace |
187
- | `mixed` | the porous interplay where events bleed between both layers |
188
- | `hyperspace` | inside the Instances only — not the physical frontier |
184
+ | Context | Premise lives in… |
185
+ | ------------ | ----------------------------------------------------------- |
186
+ | `physical` | the material frontier only — no hyperspace |
187
+ | `mixed` | the porous interplay where events bleed between both layers |
188
+ | `hyperspace` | inside the Instances only — not the physical frontier |
189
189
 
190
190
  Force one with `--space-context <physical\|mixed\|hyperspace>`; an invalid value
191
191
  warns and falls back to random. This applies to auto-generation only (with
@@ -265,25 +265,25 @@ overwritten and codes are never duplicated.
265
265
 
266
266
  Options:
267
267
 
268
- | Flag | Description |
269
- | -------------------------- | ---------------------------------------------------- |
270
- | `--prompt <theme>` | Theme seed. Omit to auto-generate from the lore. |
271
- | `--import <file>` | Load a generated payload file into the DB. |
272
- | `--lore-path <path>` | Override the base-lore doc (auto-generate mode). |
273
- | `--space-context <ctx>` | Force `physical` \| `mixed` \| `hyperspace` (else random). |
274
- | `--tone <tone>` | Force `adventure` \| `politics` \| `tragic` \| `comedy` (else random). |
275
- | `--faction-context <keys>` | Comma-separated `zenith` \| `nova` \| `atlas` \| `neutral` that DRIVE the theme (else background). |
276
- | `--character-context <keys>` | Comma-separated `CHARACTER_NAMES_POOL` keys for name inspiration (else random subset). |
277
- | `--cultural-exposure <mode>` | `cosmopolitan` \| `local` naming-diversity mode (else random). |
278
- | `--temperature <value>` | Sampling temperature for every model call (default `1.3`). |
279
- | `--model <id>` | Gemini model id (default `gemma-4-26b-a4b-it`). |
280
- | `--timeout <ms>` | Per-request timeout in ms (default `300000`). |
281
- | `--thinking-level <level>` | `low` \| `medium` \| `high` (default `high`). |
282
- | `--out <file>` | Payload dump path (default `./engine-private/cyberia-sagas/<saga-code>.json`). |
283
- | `--dry-run` | Normalize only; no database writes. |
284
- | `--env-path <path>` | Env file to load (`GEMINI_API_KEY`, deploy vars). |
285
- | `--mongo-host <host>` | Mongo host override. |
286
- | `--dev` | Force the development environment. |
268
+ | Flag | Description |
269
+ | ---------------------------- | -------------------------------------------------------------------------------------------------- |
270
+ | `--prompt <theme>` | Theme seed. Omit to auto-generate from the lore. |
271
+ | `--import <file>` | Load a generated payload file into the DB. |
272
+ | `--lore-path <path>` | Override the base-lore doc (auto-generate mode). |
273
+ | `--space-context <ctx>` | Force `physical` \| `mixed` \| `hyperspace` (else random). |
274
+ | `--tone <tone>` | Force `adventure` \| `politics` \| `tragic` \| `comedy` (else random). |
275
+ | `--faction-context <keys>` | Comma-separated `zenith` \| `nova` \| `atlas` \| `neutral` that DRIVE the theme (else background). |
276
+ | `--character-context <keys>` | Comma-separated `CHARACTER_NAMES_POOL` keys for name inspiration (else random subset). |
277
+ | `--cultural-exposure <mode>` | `cosmopolitan` \| `local` naming-diversity mode (else random). |
278
+ | `--temperature <value>` | Sampling temperature for every model call (default `1.3`). |
279
+ | `--model <id>` | Gemini model id (default `gemma-4-26b-a4b-it`). |
280
+ | `--timeout <ms>` | Per-request timeout in ms (default `10000`). |
281
+ | `--thinking-level <level>` | `low` \| `medium` \| `high` (default `high`). |
282
+ | `--out <file>` | Payload dump path (default `./engine-private/cyberia-sagas/<saga-code>.json`). |
283
+ | `--dry-run` | Normalize only; no database writes. |
284
+ | `--env-path <path>` | Env file to load (`GEMINI_API_KEY`, deploy vars). |
285
+ | `--mongo-host <host>` | Mongo host override. |
286
+ | `--dev` | Force the development environment. |
287
287
 
288
288
  `GEMINI_API_KEY` must be available in the environment or the `--env-path` file.
289
289
 
@@ -294,8 +294,7 @@ A single JSON object with these interrelated sections:
294
294
  - **saga** — `code`, `name`, `description`, `mapCodes[]`, `itemIds[]`, `questCodes[]`.
295
295
  - **instance** — the playable `CyberiaInstance` shell derived from the saga
296
296
  metadata: `code`, `name`, `description`, `tags[]`, `cyberiaMapCodes[]` (the
297
- saga's map codes), `itemIds[]` (`{ id, defaultPlayerInventory }`),
298
- `topologyMode`. Spatial topology (`portals`) and the tuning ref (`conf`) are
297
+ saga's map codes), `topologyMode`. Spatial topology (`portals`) and the tuning ref (`conf`) are
299
298
  left empty here and bound by downstream spatial synthesis.
300
299
  - **maps[]** — narrative zones the quest chain visits: `code`, `name`,
301
300
  `description` (text only — grid/cells/entities stay at schema defaults). Their
@@ -338,7 +337,7 @@ normalization slugifies every code/id so they line up:
338
337
  minimal `skill`-type item for any summoned id the model omitted;
339
338
  - `saga.questCodes` / `saga.itemIds` are reconciled to include every generated
340
339
  quest and item (including appended skill items), and `instance.cyberiaMapCodes`
341
- / `instance.itemIds` mirror the saga's.
340
+ mirrors the saga's.
342
341
 
343
342
  ### `talk` objectives (guaranteed fulfillable)
344
343
 
@@ -38,7 +38,7 @@ persisted maps + rules tick + AOI + snapshots render
38
38
  ```
39
39
 
40
40
  - Each service is supervised independently and owns its own monitor and reconnector.
41
- - `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.
41
+ - `cyberia-server` dials the Data Server 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.
42
42
  - On reconnect, world configuration is reloaded via `GetFullInstance(instanceCode)`.
43
43
  - If any one of the three services is unhealthy, the game moves to standby until all three recover.
44
44
 
@@ -232,7 +232,7 @@ Paths are relative to `cyberia-server/`. Gameplay logic lives under `src/`; the
232
232
  | `src/economy.go` | Fountain & Sink coin economy |
233
233
  | `src/life_regen.go` | HP regeneration |
234
234
  | `src/ai.go` | Bot AI |
235
- | `src/stats.go` | Active stat aggregation, sum-stats limit enforcement |
235
+ | `src/stats.go` | Signed stat aggregation and effective floors |
236
236
  | `src/entity_status.go` | Entity Status Indicator (ESI) numeric IDs |
237
237
  | `src/frozen_state.go` | FrozenInteractionState |
238
238
  | `src/handlers.go` | WebSocket lifecycle, binary uplink decoder, JSON-uplink back-compat adapter |
@@ -262,15 +262,20 @@ All content data (ObjectLayer metadata, asset blobs, optional client hints, the
262
262
 
263
263
  ## Environment
264
264
 
265
- | Variable | Default | Description |
266
- | --------------------------------- | ----------------- | -------------------------------------------------- |
267
- | `ENGINE_GRPC_ADDRESS` | `localhost:50051` | engine-cyberia gRPC address (**required**) |
268
- | `INSTANCE_CODE` | `default` | Instance code to load on startup |
269
- | `ENGINE_API_BASE_URL` | _(empty)_ | Internal engine-cyberia origin (server→engine only) |
270
- | `ENGINE_PUBLIC_URL` | _(empty)_ | Client-visible Content Authority origin (forwarded to clients) |
271
- | `ENGINE_GRPC_RELOAD_INTERVAL_SEC` | _(disabled)_ | ObjectLayer hot-reload polling interval |
272
- | `SERVER_PORT` | `8081` | WebSocket + HTTP listen port |
273
- | `STATIC_DIR` | `./public` | Directory for static WASM client files |
265
+ Two endpoints are command-line flags, not variables. Both are required, and
266
+ each is const for the life of the process.
267
+
268
+ | Flag | Example | Description |
269
+ | -------------------- | ------------------------------- | ------------------------- |
270
+ | `--data-server-url` | `https://www.cyberiaonline.com` | Data Server REST origin |
271
+ | `--data-server-grpc` | `localhost:50051` | Data Server gRPC endpoint |
272
+
273
+ | Variable | Default | Description |
274
+ | --------------------------------- | ------------ | --------------------------------------- |
275
+ | `INSTANCE_CODE` | `default` | Instance code to load on startup |
276
+ | `ENGINE_GRPC_RELOAD_INTERVAL_SEC` | _(disabled)_ | ObjectLayer hot-reload polling interval |
277
+ | `SERVER_PORT` | `8081` | WebSocket + HTTP listen port |
278
+ | `STATIC_DIR` | `./public` | Directory for static WASM client files |
274
279
 
275
280
  ---
276
281
 
@@ -20,7 +20,7 @@ _Stackable Rendering Layers as a Unified Tokenized Reality_
20
20
  <!-- CI/CD and health -->
21
21
  <div align="center">
22
22
 
23
- [![Node.js CI](https://img.shields.io/github/actions/workflow/status/underpostnet/engine-cyberia/docker-image.engine-cyberia.ci.yml?branch=main&label=Node.js%20CI)](https://github.com/underpostnet/engine-cyberia/actions/workflows/docker-image.engine-cyberia.ci.yml) [![Test](https://img.shields.io/github/actions/workflow/status/underpostnet/engine-cyberia/coverall.cyberia.ci.yml?branch=main&label=Test)](https://github.com/underpostnet/engine-cyberia/actions/workflows/coverall.cyberia.ci.yml) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine-cyberia/badge.svg?branch=main)](https://coveralls.io/github/underpostnet/engine-cyberia?branch=main) [![Socket Badge](https://socket.dev/api/badge/npm/package/cyberia/3.3.73)](https://socket.dev/npm/package/cyberia/overview/3.3.73)
23
+ [![Node.js CI](https://img.shields.io/github/actions/workflow/status/underpostnet/engine-cyberia/docker-image.engine-cyberia.ci.yml?branch=main&label=Node.js%20CI)](https://github.com/underpostnet/engine-cyberia/actions/workflows/docker-image.engine-cyberia.ci.yml) [![Test](https://img.shields.io/github/actions/workflow/status/underpostnet/engine-cyberia/coverall.cyberia.ci.yml?branch=main&label=Test)](https://github.com/underpostnet/engine-cyberia/actions/workflows/coverall.cyberia.ci.yml) [![Coverage Status](https://coveralls.io/repos/github/underpostnet/engine-cyberia/badge.svg?branch=main)](https://coveralls.io/github/underpostnet/engine-cyberia?branch=main) [![Socket Badge](https://socket.dev/api/badge/npm/package/cyberia/3.3.76)](https://socket.dev/npm/package/cyberia/overview/3.3.76)
24
24
 
25
25
  </div>
26
26
 
@@ -45,26 +45,26 @@ Go server and the WASM client with data — it does not run the simulation or th
45
45
 
46
46
  ```text
47
47
  ┌───────────────────────────────────────────────┐
48
- │ UNDERPOST PLATFORM
49
- │ toolchain · deploy · PWA build · static
50
- │ delivery · cluster/image/db · monitoring
51
-
52
- │ underpost CLI ──▶ builds & deploys all
48
+ │ UNDERPOST PLATFORM
49
+ │ toolchain · deploy · PWA build · static
50
+ │ delivery · cluster/image/db · monitoring
51
+
52
+ │ underpost CLI ──▶ builds & deploys all
53
53
  └───────────────────────────────────────────────┘
54
54
  │ hosts / delivers
55
55
 
56
- ╔═══════════════════════════════ CYBERIA MMO ══════════════════════════════════╗
56
+ ╔═══════════════════════════════ CYBERIA MMO ═══════════════════════════════════╗
57
57
  ║ ║
58
- ║ ┌──────────────────┐ gRPC ┌──────────────────┐ WebSocket
59
- ║ │ engine-cyberia │ ────────▶ │ cyberia-server │ ───────────┐
60
- ║ │ (Node.js) │ │ (Go) │ │
61
- ║ │ │ │ │ ▼
62
- ║ │ content │ │ authoritative │ ┌──────────────────┐ ║
63
- ║ │ validation │ │ simulation │ │ cyberia-client │ ║
64
- ║ │ persistence │ │ world tick │ │ (C / WASM) │ ║
65
- ║ │ gRPC/REST data │ │ AOI replication │ │ │ ║
66
- ║ │ asset metadata │ │ │ │ rendering │ ║
67
- ║ └──────────────────┘ └──────────────────┘ │ input │ ║
58
+ ║ ┌──────────────────┐ gRPC ┌──────────────────┐ WebSocket
59
+ ║ │ engine-cyberia │ ────────▶ │ cyberia-server │ ───────────┐
60
+ ║ │ (Node.js) │ │ (Go) │ │
61
+ ║ │ │ │ │ ▼
62
+ ║ │ content │ │ authoritative │ ┌──────────────────┐ ║
63
+ ║ │ validation │ │ simulation │ │ cyberia-client │ ║
64
+ ║ │ persistence │ │ world tick │ │ (C / WASM) │ ║
65
+ ║ │ gRPC/REST data │ │ AOI replication │ │ │ ║
66
+ ║ │ asset metadata │ │ │ │ rendering │ ║
67
+ ║ └──────────────────┘ └──────────────────┘ │ input │ ║
68
68
  ║ │ │ prediction │ ║
69
69
  ║ └──────────────── REST (content/assets) ─────▶│ presentation │ ║
70
70
  ║ └──────────────────┘ ║
@@ -157,17 +157,17 @@ Three supervised processes run in parallel, each with its own monitor and reconn
157
157
 
158
158
  ```text
159
159
  ┌──────────────────────────────────────────────────────────────┐
160
- │ SUPERVISION (parallel)
161
-
162
- │ engine-cyberia cyberia-server cyberia-client
163
- │ ┌────────────┐ ┌────────────┐ ┌────────────┐
164
- │ │ monitor │ │ monitor │ │ monitor │
165
- │ │ +reconnect │ │ +reconnect │ │ +reconnect │
166
- │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
167
- │ │ │ │
168
- │ └──────────┬──────────┴──────────┬──────────┘
169
- │ ▼ ▼
170
- │ all three up & connected?
160
+ │ SUPERVISION (parallel)
161
+
162
+ │ engine-cyberia cyberia-server cyberia-client
163
+ │ ┌────────────┐ ┌────────────┐ ┌────────────┐
164
+ │ │ monitor │ │ monitor │ │ monitor │
165
+ │ │ +reconnect │ │ +reconnect │ │ +reconnect │
166
+ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘
167
+ │ │ │ │
168
+ │ └──────────┬──────────┴──────────┬──────────┘
169
+ │ ▼ ▼
170
+ │ all three up & connected?
171
171
  └────────────────────────────┬─────────────────────────────────┘
172
172
 
173
173
  ┌─────────────────────┼─────────────────────┐
@@ -70,7 +70,7 @@ type PlayerState struct {
70
70
  FreezeReason string // "dialogue" | "inventory" | ...
71
71
  Path []PointI // A* path (for smooth movement)
72
72
  AOI Rectangle // current area-of-interest bounds
73
- SumStatsLimit int // max aggregate stat sum (equipment rules)
73
+ Progression EntityProgression // level, total XP, and progression freeze
74
74
  }
75
75
  ```
76
76
 
@@ -131,13 +131,19 @@ z-order type
131
131
 
132
132
  ## Stat Aggregation
133
133
 
134
- Stats are aggregated across all **active** Object Layers on an entity. The server enforces a `sumStatsLimit` — the maximum sum of all stat values allowed for the player. Attempting to equip items that would exceed this limit is rejected.
134
+ Players, bots, NPCs, and resources share the same progression and stat module.
135
+ Bots, NPCs, and resources have frozen XP. Their authored level selects the same base curve as players.
135
136
 
136
- **Active stats sum:**
137
+ ```text
138
+ Effective = max(floor, Base(Level) + ActiveOL + Temporary)
139
+ ```
137
140
 
138
- $$\text{activeStatsSum} = \sum_{\text{active layers}} (\text{effect} + \text{resistance} + \text{agility} + \text{range} + \text{intelligence} + \text{utility})$$
141
+ Each active layer with positive quantity contributes once. Each OL stat is an integer in `[-100, +100]`.
142
+ The server applies floors after the full sum. Equipment rules control active slots and item types.
139
143
 
140
- The `sumStatsLimit` and `activeStatsSum` are sent to the client on each AOI update so the inventory UI can show remaining equip budget.
144
+ The self snapshot carries `level`, `xp`, `levelXp`, `nextLevelXp`, and four stat arrays.
145
+ Other living entities carry `level` and `effectiveStats`. `statsSum` is an uncapped display summary.
146
+ The overhead HUD and the Stats tab display the server values. XP and level never belong to `data.stats`.
141
147
 
142
148
  ---
143
149
 
@@ -1,6 +1,6 @@
1
1
  # Cyberia Online — Development Roadmap
2
2
 
3
- **Current version:** 3.3.73 | **Target milestone:** Open Alpha
3
+ **Current version:** 3.3.76 | **Target milestone:** Open Alpha
4
4
 
5
5
  ---
6
6
 
@@ -0,0 +1,217 @@
1
+ # Cyberia stats and entity progression
2
+
3
+ The Go server owns effective stats, XP, and level changes.
4
+ The engine owns authored content, configuration, validation, and persistence.
5
+ The client displays server state. The CLI validates content and generates shared contracts.
6
+
7
+ ## Canonical contract
8
+
9
+ `src/client/components/cyberia/SharedDefaultsCyberia.js` defines the stat order, descriptions, modifier bounds, defaults, effective floors, and what one point is worth. The generated contract carries the scales to the server, so the simulation and the documentation read one table.
10
+
11
+ | Index | Stat | One point | Effective floor |
12
+ | --- | --- | --- | ---: |
13
+ | 0 | `effect` | Removes one life point per hit | 1 |
14
+ | 1 | `resistance` | Adds one point of maximum life and a tenth of a point to each regeneration | 0 |
15
+ | 2 | `agility` | Adds one percent of base movement speed | -90 |
16
+ | 3 | `range` | Adds fifty milliseconds of summon lifetime | 0 |
17
+ | 4 | `intelligence` | Adds five percentage points of summon chance | 0 |
18
+ | 5 | `utility` | Removes one percent of base cooldown and adds one percentage point of regeneration chance | 0 |
19
+
20
+ Chances are fractions of one. A tap regenerates with `lifeRegenChance` (default 0.15) plus utility; a skill summons with its `skillRules` chance (projectile 0.75, doppelganger 0.6) plus intelligence. No chance the stats raise passes `maxChance` (default 0.95), so nothing becomes a certainty. Regeneration heals the entity's own base amount plus a tenth of a point per resistance point, never past its maximum.
21
+
22
+ Each OL modifier is an integer from `-100` through `+100`. Zero is neutral.
23
+ Missing stat keys resolve to zero. Unknown keys, fractions, numeric strings, null values, and values outside the bounds fail validation.
24
+ An OL cannot store XP or level in `data.stats`.
25
+
26
+ The editor marks negative values as penalties and positive values as bonuses.
27
+ The randomizer validates its bounds and uses the full signed range by default.
28
+
29
+ ## Semantic bounds and balancing
30
+
31
+ `STAT_TYPE_BOUNDS` in the same module declares the inclusive range each stat may take per item type. A weapon deals its effect and can cost resistance; a skin and a breastplate defend; a skill reaches; world content carries nothing. A type or stat absent there keeps the contract range.
32
+
33
+ | Type | effect | resistance | agility | range | intelligence | utility |
34
+ | --- | ---: | ---: | ---: | ---: | ---: | ---: |
35
+ | `weapon` | 5..20 | -10..2 | -5..5 | 0..10 | 0..5 | 0..10 |
36
+ | `skin` | 1 | 0..20 | -5..10 | 0 | 0..2 | 0..5 |
37
+ | `breastplate` | 0 | 5..30 | -15..0 | 0 | 0..3 | 0..5 |
38
+ | `skill` | 0..15 | 0..5 | 0..5 | 5..30 | 0..10 | 0..10 |
39
+ | `resource` | 0 | 0..10 | 0 | 0 | 0 | 0 |
40
+ | world content | 0 | 0 | 0 | 0 | 0 | 0 |
41
+
42
+ `src/projects/cyberia/stat-balance.js` applies one policy to every object layer the `ol` command writes: import, batch import, generation, atlas rebuilds, and the minify refresh. `--normalize-stats` clamps the stats the content carries into its type's range. `--random-stats` draws every stat inside the range instead. `--min-stat` and `--max-stat` narrow the range; a request that leaves no overlap pins the stat to the semantic edge nearest it. The policy is a pure function of the content and the flags, so a run is deterministic and normalizing is idempotent.
43
+
44
+ ## Aggregation and levels
45
+
46
+ ```text
47
+ BaseStat(level) = baseStats[stat] + (level - 1) * perLevelStats[stat]
48
+ EffectiveStat = max(floor, BaseStat(level) + ActiveOLStat + TemporaryStat)
49
+ TotalXPForLevel(level) = xpPerLevel * (level - 1)^2
50
+ ```
51
+
52
+ Each active OL with positive quantity contributes once. Stack quantity does not multiply its modifier.
53
+ The server applies floors after the full sum. Effective values can exceed the OL bounds.
54
+ Equipment rules still control item types, active slots, and required skins.
55
+
56
+ Players, bots, NPCs, and resources share `EntityProgression`, the base curve, and stat aggregation.
57
+ Bots, NPCs, and resources have frozen progression. Their content `level` selects the base stats.
58
+ An absent content level uses `defaultBotLevel`. New players start at level 1 with zero XP.
59
+ Terrain and other passive objects carry no progression state.
60
+
61
+ Summoned entities inherit the caster's base and signed contributions, then add their own contributions.
62
+ The server applies floors after inheritance. A caster cycle stops traversal.
63
+ Temporary modifiers have an ID and expiry time. Setting the same ID replaces that modifier.
64
+ The lifecycle phase removes expired modifiers and updates maximum life.
65
+
66
+ The agility floor retains at least 10% of configured movement speed.
67
+ The existing minimum action cooldown still applies. Resistance keeps maximum life and regeneration nonnegative.
68
+ Increasing maximum life does not grant an instant heal.
69
+
70
+ ## Server configuration
71
+
72
+ `src/api/cyberia-server-defaults/cyberia-server-defaults.js` owns progression defaults and configuration limits.
73
+ Instance content can override `progressionRules`. Partial curves inherit missing defaults.
74
+ The engine resolves and validates the complete configuration before transport.
75
+
76
+ | Setting | Default |
77
+ | --- | ---: |
78
+ | `maxLevel` | 100 |
79
+ | `xpPerLevel` | 100 |
80
+ | `defaultBotLevel` | 1 |
81
+ | `killXp` | 25 |
82
+ | `objectiveXp` | 10 |
83
+ | `questXp` | 100 |
84
+ | `minAwardIntervalMs` | 500 |
85
+ | `repeatWindowMs` | 60000 |
86
+ | `maxRepeatAwards` | 4 |
87
+ | `maxAwardsPerWindow` | 30 |
88
+
89
+ The default base array is `[5, 10, 0, 0, 0, 0]`.
90
+ The default growth array is `[2, 5, 1, 10, 1, 1]`.
91
+ The first total XP thresholds are `0`, `100`, `400`, and `900`.
92
+ One reward can advance several levels. XP stops at the maximum level threshold.
93
+
94
+ Authored levels must be integers from 1 through 65535 and cannot exceed the instance maximum.
95
+ Base and growth fields must be integers from 0 through 100.
96
+ The CLI prints the current defaults with `node bin/cyberia.js stats progression`.
97
+
98
+ Configuration participates in the instance version hash.
99
+ The server validates stat bounds, curves, and authored levels before a full reload mutates state.
100
+ An invalid reload retains its pending version so a corrected payload can retry.
101
+ On a curve change, player XP selects the level under the new curve. The new maximum clamps XP.
102
+
103
+ ## XP rules
104
+
105
+ Only resolved simulation events can award XP:
106
+
107
+ - A player or bot defeat can award kill XP.
108
+ - A resource defeat can award objective XP.
109
+ - A completed quest can award quest XP once per quest code and session.
110
+
111
+ A combat contributor must supply at least 10% of recorded positive damage.
112
+ Self kills, summoned targets, item drops, providers, ghosts, and frozen players cannot produce eligible defeat rewards.
113
+ Bots and NPCs cannot gain XP.
114
+
115
+ Low-value outcomes share an award interval and a window limit.
116
+ Repeated rewards for the same event kind and target divide by `1`, `2`, `3`, then `4`.
117
+ Further repeats in that window award zero. Lower-level targets reduce the reward again.
118
+ Integer division rounds down. Zero rewards do not consume an award slot.
119
+
120
+ Unique quest completion bypasses the low-value interval and window limit.
121
+ This permits a final kill and its quest completion to award XP in the same tick.
122
+ Quest completion still rejects repeats.
123
+
124
+ Raw taps, idle duration, inventory changes, and currency transfers award no XP.
125
+ Player skills and tap regeneration share the server action cooldown. Movement input remains independent.
126
+
127
+ ## API and Object Layer specification
128
+
129
+ An Object Layer record stores its contract marker outside hashed metadata. The model stamps it on every write:
130
+
131
+ ```json
132
+ {
133
+ "statContractVersion": 2,
134
+ "data": {
135
+ "stats": {
136
+ "effect": -100,
137
+ "resistance": 100,
138
+ "agility": 0,
139
+ "range": 0,
140
+ "intelligence": 0,
141
+ "utility": 0
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ This fragment omits the existing required item, ledger, and asset fields.
148
+ The OL API, import path, generator, and boot assembler use the shared validator.
149
+ Mongo schema validation also enforces integer bounds. API updates run validators.
150
+
151
+ Entity content stores optional `level` beside `entityType` and `objectLayerItemIds`.
152
+ The map editor preserves this field and accepts an optional level. An empty input uses the instance default.
153
+ `InstanceConfig.progressionRules` carries the resolved server rules over gRPC and REST. Protobuf field 24 is reserved.
154
+
155
+ The game link uses JSON envelopes over WebSocket. AOI stat arrays use canonical order.
156
+
157
+ | Snapshot scope | Fields |
158
+ | --- | --- |
159
+ | Living entities | `level`, `effectiveStats[6]`, `statsSum` |
160
+ | Viewing player only | `xp`, `levelXp`, `nextLevelXp`, `baseStats[6]`, `layerStats[6]`, `temporaryStats[6]` |
161
+
162
+ `xp` is total XP. `levelXp` and `nextLevelXp` are the thresholds that bound the current level, so the client draws in-level progress without the curve. At maximum level, all three equal the final threshold.
163
+ `statsSum` is an uncapped display summary, not an equipment budget.
164
+ Passive objects omit `level` and `effectiveStats`. Other players do not receive private XP or base-stat breakdowns.
165
+ The instance-map REST API serves authored topology and capabilities. It does not calculate simulation stats.
166
+
167
+ An XP award sends a `combat_text` message of kind `xp` to the earner only. Damage and regen kinds stay broadcast to the area of interest.
168
+
169
+ The overhead HUD of every living entity shows its level, its stats sum, and an XP bar under the HP bar. The bar fills with the viewing player's in-level progress and stays empty for other entities.
170
+ The Stats tab draws one shared stat panel per block. Every entity gets its effective stats; the viewing player also gets OL, base, and temporary values.
171
+ The level-up effect observes authoritative level increments for an entity already in view.
172
+ First exposure, repeated snapshots, and level decreases produce no effect. Reconnect resets effects.
173
+ The server broadcasts the `level-up` audio event to every viewer in reach when a player's level rises, and the `death` audio event on every defeat.
174
+
175
+ ## Progression lifetime
176
+
177
+ The guest runtime assigns a new UUID for each WebSocket session.
178
+ Progression survives death, respawn, portal travel, and world reload within that session.
179
+ A disconnect or server restart ends that progression state.
180
+
181
+ Persistent account progression requires an authenticated, stable account binding and an engine-owned persistence contract.
182
+ The simulation server exposes no XP mutation API and stores no progression.
183
+
184
+ ## Generation and verification
185
+
186
+ Generate contracts after a shared stat or progression-default change:
187
+
188
+ ```sh
189
+ node bin/cyberia.js stat-contract
190
+ node bin/cyberia.js stat-contract --check
191
+ ```
192
+
193
+ The generator writes the Go contract, C header, and REST test fixtures. Do not edit these outputs by hand.
194
+ Regenerate protobuf output with the existing protobuf toolchain when `gen/proto/cyberia.proto` changes.
195
+
196
+ Inspect and validate content with:
197
+
198
+ ```sh
199
+ node bin/cyberia.js stats progression
200
+ node bin/cyberia.js stats validate /tmp/object-layers.json
201
+ node bin/cyberia.js stats random --min=-100 --max=100
202
+ ```
203
+
204
+ Focused engine tests:
205
+
206
+ ```sh
207
+ node node_modules/vitest/vitest.mjs run test/integration/app/cyberia/cyberia-stats.test.js
208
+ ```
209
+
210
+ Run `go test ./...` from `cyberia-server`. The REST and WebSocket tests need local loopback socket access.
211
+ The tests cover signed bounds, floors, inheritance, expiry, thresholds, static levels, repeat limits, tap spam, and reload rejection.
212
+ Generated REST fixtures and protobuf round trips check the engine/server contract.
213
+
214
+ Run the native client tests, including the level-up effect, with `cyberia-client/tests/run.sh`.
215
+
216
+ Build the client with its existing `Web.mk` target and Emscripten SDK.
217
+ The SDK needs Python 3.10 or later and a writable cache.
@@ -18,7 +18,7 @@ _Stackable Rendering Layers as a Unified Tokenized Reality_
18
18
 
19
19
  ---
20
20
 
21
- **Version:** 3.3.73 | **Status:** Draft | **Authors:** Underpost Engineering
21
+ **Version:** 3.3.76 | **Status:** Draft | **Authors:** Underpost Engineering
22
22
 
23
23
  ---
24
24
 
@@ -208,18 +208,26 @@ The `ObjectLayerToken` contract maps each `tokenId` → canonical `cid` on-chain
208
208
 
209
209
  ### 4.4 Canonical Stats Schema
210
210
 
211
- Stats govern mechanical behavior across all game systems (combat, movement, skills, drops):
211
+ Each Object Layer contributes six signed integer modifiers in `[-100, +100]`. Zero is neutral.
212
+ The canonical source is `SharedDefaultsCyberia.js`.
212
213
 
213
- ```
214
- Stats {
215
- effect: int // damage output / skill power
216
- resistance: int // damage reduction
217
- agility: int // movement speed modifier
218
- range: int // attack / interaction range
219
- intelligence: int // skill cooldown / mana modifier
220
- utility: int // general-purpose utility bonus
221
- }
222
- ```
214
+ | Stat | Runtime meaning |
215
+ | -------------- | --------------------------------------------------------------------- |
216
+ | `effect` | Life removed by an impact |
217
+ | `resistance` | Maximum life bonus and life restored by regeneration |
218
+ | `agility` | Movement speed percentage modifier |
219
+ | `range` | Summoned entity lifetime in milliseconds |
220
+ | `intelligence` | Summon success chance modifier |
221
+ | `utility` | Action cooldown percentage reduction and regeneration chance modifier |
222
+
223
+ The server adds base stats at the entity level, active OL contributions, and temporary modifiers.
224
+ It applies playable floors after this sum. Players, bots, NPCs, and resources use the same calculation.
225
+ Bots and NPCs use assigned levels with frozen XP.
226
+
227
+ XP belongs to entity progression. Resolved kills, resource defeats, and quest completions can award XP.
228
+ Cooldowns, contribution checks, repeat limits, and rate limits restrict rewards. Raw taps and idle time award no XP.
229
+
230
+ The current guest runtime keeps progression for one connected session.
223
231
 
224
232
  ### 4.5 Item Type Registry
225
233