@underpostnet/cyberia 3.3.0 → 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 (568) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +17 -5
  3. package/.github/workflows/coverall.cyberia.ci.yml +39 -14
  4. package/.github/workflows/cyberia-client.cd.yml +5 -0
  5. package/.github/workflows/cyberia-server.cd.yml +5 -0
  6. package/.github/workflows/docker-image.cyberia-client.ci.yml +8 -5
  7. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +8 -3
  8. package/.github/workflows/docker-image.cyberia-server.ci.yml +8 -5
  9. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +8 -5
  10. package/.github/workflows/docker-image.engine-cyberia.ci.yml +15 -1
  11. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +15 -1
  12. package/.github/workflows/engine-cyberia.cd.yml +3 -0
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +35 -20
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/publish.cyberia.ci.yml +44 -0
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +1 -0
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +96 -0
  22. package/CHANGELOG.md +653 -2513
  23. package/CLI-HELP.md +323 -157
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +5 -5
  27. package/README.md +48 -28
  28. package/bin/build.js +86 -41
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +1494 -1403
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +1494 -1403
  33. package/compose.env +17 -5
  34. package/conf.js +98 -7
  35. package/deploy/cyberia-client/deploy.sh +46 -0
  36. package/deploy/cyberia-client/package.sh +17 -0
  37. package/deploy/cyberia-client/state.sh +15 -0
  38. package/deploy/cyberia-server/deploy.sh +46 -0
  39. package/deploy/cyberia-server/package.sh +17 -0
  40. package/deploy/cyberia-server/state.sh +15 -0
  41. package/deploy/dd-cyberia/deploy.sh +9 -29
  42. package/deploy/dd-cyberia/init.sh +38 -45
  43. package/deploy/dd-cyberia/package.sh +16 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +133 -147
  46. package/deploy/lib/config.sh +29 -0
  47. package/deploy/lib/github-actions-logging.sh +610 -0
  48. package/deploy/lib/host.sh +194 -0
  49. package/deploy/lib/state.sh +65 -0
  50. package/deploy/pwa-microservices-template/deploy.sh +17 -42
  51. package/deploy/release/deploy.sh +9 -41
  52. package/deployment.yaml +7 -2
  53. package/docker-compose.yml +23 -17
  54. package/examples/static-page/README.md +19 -19
  55. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  56. package/hardhat/hardhat.config.js +1 -6
  57. package/hardhat/package-lock.json +101 -40
  58. package/hardhat/package.json +2 -2
  59. package/manifests/alertmanager/deployment.yaml +81 -0
  60. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  61. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +32 -7
  62. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +32 -7
  63. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  64. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  65. package/manifests/deployment/mongo-express/deployment.yaml +46 -27
  66. package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
  67. package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
  68. package/manifests/deployment/mongo-express/service.yaml +16 -0
  69. package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
  70. package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
  71. package/manifests/grafana/deployment.yaml +26 -7
  72. package/manifests/grafana/kustomization.yaml +1 -0
  73. package/manifests/grafana/service.yaml +6 -1
  74. package/manifests/mariadb/pv.yaml +1 -0
  75. package/manifests/mongodb/statefulset.yaml +9 -2
  76. package/manifests/mysql/pv-pvc.yaml +14 -1
  77. package/manifests/postgresql/pv.yaml +12 -1
  78. package/manifests/postgresql/pvc.yaml +1 -0
  79. package/manifests/prometheus/deployment.yaml +18 -22
  80. package/package.json +87 -12
  81. package/scripts/event-monitor.sh +56 -0
  82. package/scripts/k3s-node-setup.sh +27 -14
  83. package/scripts/kubeadm-node-setup.sh +31 -11
  84. package/scripts/maas-setup.sh +8 -8
  85. package/scripts/selinux-normalize.sh +907 -0
  86. package/scripts/shutdown-machine.sh +843 -0
  87. package/scripts/test-monitor.sh +5 -5
  88. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +15 -10
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +7 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +90 -186
  92. package/src/api/core/core.controller.js +1 -1
  93. package/src/api/core/core.router.js +2 -2
  94. package/src/api/core/core.service.js +2 -2
  95. package/src/api/crypto/crypto.controller.js +1 -1
  96. package/src/api/crypto/crypto.service.js +1 -1
  97. package/src/api/cyberia-action/cyberia-action.controller.js +1 -1
  98. package/src/api/cyberia-action/cyberia-action.router.js +3 -3
  99. package/src/api/cyberia-action/cyberia-action.service.js +5 -6
  100. package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
  101. package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
  102. package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
  103. package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
  104. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +2 -4
  105. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
  106. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +13 -35
  107. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +17 -34
  108. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  109. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
  110. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  111. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  112. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  113. package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
  114. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  115. package/src/api/cyberia-entity/cyberia-entity.service.js +3 -3
  116. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
  117. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
  118. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +17 -2
  119. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +468 -5
  120. package/src/api/cyberia-instance/cyberia-fallback-capture.js +502 -0
  121. package/src/api/cyberia-instance/cyberia-fallback-world.js +29 -40
  122. package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
  123. package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
  124. package/src/api/cyberia-instance/cyberia-instance-map.service.js +35 -142
  125. package/src/api/cyberia-instance/cyberia-instance.controller.js +2 -5
  126. package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
  127. package/src/api/cyberia-instance/cyberia-instance.router.js +5 -18
  128. package/src/api/cyberia-instance/cyberia-instance.service.js +5 -45
  129. package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
  130. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  131. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
  132. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  133. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +76 -3
  134. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  135. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  136. package/src/api/cyberia-map/cyberia-map.service.js +8 -4
  137. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
  138. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
  139. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
  140. package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
  141. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  142. package/src/api/cyberia-quest/cyberia-quest.router.js +4 -4
  143. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  144. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  145. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
  146. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  147. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  148. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  149. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  150. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  151. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -214
  152. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  153. package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
  154. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  155. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  156. package/src/api/default/default.controller.js +1 -1
  157. package/src/api/default/default.router.js +1 -1
  158. package/src/api/default/default.service.js +2 -2
  159. package/src/api/document/document.controller.js +1 -1
  160. package/src/api/document/document.router.js +1 -1
  161. package/src/api/document/document.service.js +2 -2
  162. package/src/api/file/file.controller.js +1 -1
  163. package/src/api/file/file.ref.js +153 -0
  164. package/src/api/file/file.ref.json +11 -2
  165. package/src/api/file/file.router.js +1 -1
  166. package/src/api/file/file.service.js +2 -2
  167. package/src/api/instance/instance.controller.js +1 -1
  168. package/src/api/instance/instance.router.js +1 -1
  169. package/src/api/instance/instance.service.js +2 -2
  170. package/src/api/ipfs/ipfs.controller.js +1 -1
  171. package/src/api/ipfs/ipfs.router.js +2 -2
  172. package/src/api/ipfs/ipfs.service.js +2 -2
  173. package/src/api/object-layer/object-layer.controller.js +1 -1
  174. package/src/api/object-layer/object-layer.model.js +47 -12
  175. package/src/api/object-layer/object-layer.router.js +12 -5
  176. package/src/api/object-layer/object-layer.service.js +116 -123
  177. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  178. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  179. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  180. package/src/api/test/test.controller.js +1 -1
  181. package/src/api/test/test.service.js +1 -1
  182. package/src/api/user/guest.service.js +3 -3
  183. package/src/api/user/user.controller.js +1 -1
  184. package/src/api/user/user.router.js +2 -2
  185. package/src/api/user/user.service.js +4 -4
  186. package/src/api.js +5 -9
  187. package/src/cli/app.js +402 -0
  188. package/src/cli/baremetal.js +30 -35
  189. package/src/cli/client.js +306 -0
  190. package/src/cli/cloud-init.js +3 -3
  191. package/src/cli/cluster.js +191 -82
  192. package/src/cli/db.js +120 -68
  193. package/src/cli/deploy.js +157 -75
  194. package/src/cli/docker-compose.js +5 -5
  195. package/src/cli/domains.js +184 -0
  196. package/src/cli/dotenv-store.js +143 -0
  197. package/src/cli/event.js +2281 -0
  198. package/src/cli/fs-selection.js +115 -0
  199. package/src/cli/fs.js +159 -343
  200. package/src/cli/host.js +672 -0
  201. package/src/cli/image.js +4 -6
  202. package/src/cli/index.js +343 -151
  203. package/src/cli/ipfs.js +34 -30
  204. package/src/cli/kickstart.js +1 -1
  205. package/src/cli/kubectl.js +218 -41
  206. package/src/cli/lxd.js +3 -3
  207. package/src/cli/monitor.js +1359 -26
  208. package/src/cli/package.js +90 -0
  209. package/src/cli/release.js +16 -12
  210. package/src/cli/repository.js +586 -363
  211. package/src/cli/run.js +398 -843
  212. package/src/cli/secrets.js +2158 -964
  213. package/src/cli/ssh.js +478 -279
  214. package/src/cli/state.js +394 -0
  215. package/src/cli/static.js +2 -2
  216. package/src/cli/system.js +1 -1
  217. package/src/cli/test.js +203 -125
  218. package/src/cli/vultr.js +61 -21
  219. package/src/cli/wireguard.js +1112 -306
  220. package/src/client/components/core/CalendarCore.js +53 -96
  221. package/src/client/components/core/Docs.js +327 -303
  222. package/src/client/components/core/FileExplorer.js +0 -21
  223. package/src/client/components/core/Modal.js +56 -1
  224. package/src/client/components/core/PanelForm.js +4 -4
  225. package/src/client/components/core/Repository.js +56 -0
  226. package/src/client/components/core/Translate.js +7 -0
  227. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  228. package/src/client/components/core/Wallet.js +0 -11
  229. package/src/client/components/core/Worker.js +7 -1
  230. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  231. package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
  232. package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
  233. package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
  234. package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
  235. package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
  236. package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
  237. package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
  238. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
  239. package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
  240. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +2 -35
  241. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
  242. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
  243. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  244. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
  245. package/src/client/components/default/AppShellDefault.js +1 -1
  246. package/src/client/components/default/SettingsDefault.js +2 -2
  247. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  248. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  249. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  250. package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
  251. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +313 -30
  252. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
  253. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
  254. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
  255. package/src/client/public/cyberia-docs/CYBERIA.md +48 -28
  256. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
  257. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  258. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  259. package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
  260. package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
  261. package/src/client/public/default/android-chrome-144x144.png +0 -0
  262. package/src/client/public/default/android-chrome-192x192.png +0 -0
  263. package/src/client/public/default/android-chrome-256x256.png +0 -0
  264. package/src/client/public/default/android-chrome-36x36.png +0 -0
  265. package/src/client/public/default/android-chrome-384x384.png +0 -0
  266. package/src/client/public/default/android-chrome-48x48.png +0 -0
  267. package/src/client/public/default/android-chrome-512x512.png +0 -0
  268. package/src/client/public/default/android-chrome-72x72.png +0 -0
  269. package/src/client/public/default/android-chrome-96x96.png +0 -0
  270. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  271. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  272. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  273. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  274. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  275. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  276. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  277. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  278. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  279. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  280. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  281. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  282. package/src/client/public/default/apple-touch-icon.png +0 -0
  283. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  284. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  285. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  286. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  287. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  288. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  289. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  290. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  291. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  292. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  293. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  294. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  295. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  296. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  297. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  298. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  299. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  300. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  301. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  302. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  303. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  304. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  305. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  306. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  307. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  308. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  309. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  310. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  311. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  312. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  313. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  314. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  315. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  316. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  317. package/src/client/public/default/assets/background/white.jpg +0 -0
  318. package/src/client/public/default/browserconfig.xml +1 -1
  319. package/src/client/public/default/favicon-16x16.png +0 -0
  320. package/src/client/public/default/favicon-32x32.png +0 -0
  321. package/src/client/public/default/favicon-48x48.png +0 -0
  322. package/src/client/public/default/favicon.ico +0 -0
  323. package/src/client/public/default/manifest.webmanifest +5 -5
  324. package/src/client/public/default/mstile-144x144.png +0 -0
  325. package/src/client/public/default/mstile-150x150.png +0 -0
  326. package/src/client/public/default/mstile-310x150.png +0 -0
  327. package/src/client/public/default/mstile-310x310.png +0 -0
  328. package/src/client/public/default/mstile-70x70.png +0 -0
  329. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  330. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  331. package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
  332. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
  333. package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
  334. package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
  335. package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
  336. package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
  337. package/src/client/services/object-layer/object-layer.management.js +6 -10
  338. package/src/client/services/object-layer/object-layer.service.js +3 -1
  339. package/src/client/ssr/RootDocument.js +1 -1
  340. package/src/client/ssr/body/404.js +1 -1
  341. package/src/client/ssr/body/500.js +1 -1
  342. package/src/client/ssr/body/CacheControl.js +1 -1
  343. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  344. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  345. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  346. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  347. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  348. package/src/client/ssr/head/Css.js +1 -1
  349. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  350. package/src/client/ssr/head/DefaultScripts.js +1 -1
  351. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  352. package/src/client/ssr/head/Microdata.js +1 -1
  353. package/src/client/ssr/head/Production.js +1 -1
  354. package/src/client/ssr/head/Pwa.js +1 -1
  355. package/src/client/ssr/head/PwaItemledger.js +1 -1
  356. package/src/client/ssr/head/Seo.js +1 -1
  357. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  358. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  359. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  360. package/src/client/ssr/views/Cyberia404.js +7 -5
  361. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  362. package/src/client/ssr/views/Maintenance.js +1 -1
  363. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  364. package/src/client/ssr/views/Test.js +1 -1
  365. package/src/client/sw/core.sw.js +20 -14
  366. package/src/client-builder/client-build-docs.js +92 -77
  367. package/src/client-builder/client-build-live.js +2 -2
  368. package/src/client-builder/client-build.js +105 -74
  369. package/src/client-builder/client-bundle.js +347 -0
  370. package/src/client-builder/client-dev-server.js +3 -3
  371. package/src/client-builder/client-icons.js +2 -2
  372. package/src/client-builder/ssr.js +3 -3
  373. package/src/client.build.js +3 -6
  374. package/src/client.dev.js +3 -6
  375. package/src/db/DataBaseProvider.js +186 -3
  376. package/src/db/mariadb/MariaDB.js +3 -2
  377. package/src/db/mongo/MongoBootstrap.js +273 -101
  378. package/src/db/mongo/MongoExpress.js +187 -0
  379. package/src/db/mongo/MongooseDB.js +7 -8
  380. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  381. package/src/grpc/cyberia/grpc-server.js +6 -8
  382. package/src/index.js +79 -23
  383. package/src/mailer/EmailRender.js +2 -2
  384. package/src/mailer/MailerInterceptor.js +87 -0
  385. package/src/mailer/MailerProvider.js +50 -6
  386. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +144 -147
  387. package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
  388. package/src/projects/cyberia/besu-genesis-generator.js +8 -17
  389. package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
  390. package/src/projects/cyberia/catalog-cyberia.js +54 -10
  391. package/src/projects/cyberia/gemini-client.js +4 -4
  392. package/src/projects/cyberia/generate-saga.js +5 -12
  393. package/src/projects/cyberia/hot-reload-trigger.js +18 -29
  394. package/src/projects/cyberia/instance-backup.js +210 -0
  395. package/src/projects/cyberia/instance-data.js +178 -258
  396. package/src/projects/cyberia/ipfs-client.js +4 -4
  397. package/src/projects/cyberia/map-preview-generator.js +33 -69
  398. package/src/projects/cyberia/object-layer.js +153 -320
  399. package/src/projects/cyberia/seed-audio.js +139 -0
  400. package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
  401. package/src/projects/cyberia/semantic-layer-generator.js +3 -19
  402. package/src/projects/cyberia/stat-balance.js +81 -0
  403. package/src/projects/cyberia/stat-commands.js +23 -0
  404. package/src/projects/cyberia/stat-contract-generator.js +101 -0
  405. package/src/projects/underpost/catalog-underpost.js +9 -1
  406. package/src/proxy.js +4 -7
  407. package/src/runtime/cyberia-client/Dockerfile +17 -8
  408. package/src/runtime/cyberia-client/Dockerfile.dev +13 -4
  409. package/src/runtime/cyberia-server/Dockerfile +18 -6
  410. package/src/runtime/cyberia-server/Dockerfile.dev +16 -3
  411. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  412. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  413. package/src/runtime/engine-cyberia/Dockerfile.test +5 -5
  414. package/src/runtime/engine-cyberia/compose.env +17 -5
  415. package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
  416. package/src/runtime/express/Express.js +32 -27
  417. package/src/runtime/lampp/Lampp.js +22 -70
  418. package/src/runtime/nginx/Nginx.js +1 -1
  419. package/src/runtime/wp/Wp.js +16 -48
  420. package/src/server/{catalog.js → build/catalog.js} +15 -5
  421. package/src/server/build/coverage.js +188 -0
  422. package/src/server/build/execution.js +402 -0
  423. package/src/server/build/package.js +508 -0
  424. package/src/server/build/testing.js +578 -0
  425. package/src/server/{dns.js → network/dns.js} +144 -11
  426. package/src/server/{forward-proxy.js → network/forward-proxy.js} +10 -18
  427. package/src/server/{middlewares.js → network/middlewares.js} +11 -5
  428. package/src/server/network/node-capability.js +98 -0
  429. package/src/server/{peer.js → network/peer.js} +3 -3
  430. package/src/server/{proxy.js → network/proxy.js} +7 -13
  431. package/src/server/network/router.js +356 -0
  432. package/src/server/{tls.js → network/tls.js} +2 -2
  433. package/src/server/{underpost-compression.js → network/underpost-compression.js} +1 -1
  434. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +7 -7
  435. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +2 -2
  436. package/src/server/{cri.js → ops/cri.js} +2 -2
  437. package/src/server/{cron.js → ops/cron.js} +349 -74
  438. package/src/server/ops/event-notification.js +284 -0
  439. package/src/server/{logger.js → ops/logger.js} +143 -23
  440. package/src/server/ops/monitoring.js +1724 -0
  441. package/src/server/{systemd.js → ops/systemd.js} +55 -1
  442. package/src/server/{conf.js → runtime/conf.js} +516 -423
  443. package/src/server/runtime/config-scope.js +215 -0
  444. package/src/server/runtime/environment.js +149 -0
  445. package/src/server/{process.js → runtime/process.js} +153 -12
  446. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  447. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  448. package/src/server/{start.js → runtime/start.js} +128 -39
  449. package/src/server/{auth.js → security/auth.js} +7 -8
  450. package/src/server/security/container-storage.js +382 -0
  451. package/src/server/{crypto.js → security/crypto.js} +1 -1
  452. package/src/server/{selinux.js → security/selinux.js} +1 -1
  453. package/src/server/{backup.js → storage/backup.js} +17 -8
  454. package/src/server/{data-query.js → storage/data-query.js} +17 -1
  455. package/src/server/storage/downloader.js +121 -0
  456. package/src/server/storage/repository.js +68 -0
  457. package/src/server/storage/zip.js +153 -0
  458. package/src/server.js +4 -7
  459. package/src/ws/IoInterface.js +1 -1
  460. package/src/ws/IoServer.js +1 -1
  461. package/src/ws/core/core.ws.connection.js +1 -1
  462. package/src/ws/core/core.ws.emit.js +1 -1
  463. package/src/ws/core/core.ws.server.js +1 -1
  464. package/src/ws/default/default.ws.connection.js +1 -1
  465. package/src/ws/default/default.ws.emit.js +1 -1
  466. package/src/ws/default/default.ws.server.js +1 -1
  467. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  468. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  469. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  470. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  471. package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
  472. package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
  473. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +64 -0
  474. package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
  475. package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
  476. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +17 -23
  477. package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
  478. package/test/integration/app/cyberia/cyberia-load.test.js +466 -0
  479. package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
  480. package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
  481. package/test/integration/app/cyberia/fallback-world-capture.test.js +338 -0
  482. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  483. package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
  484. package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
  485. package/test/integration/app/cyberia/seed-audio.test.js +275 -0
  486. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  487. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  488. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  489. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  490. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  491. package/test/{selinux.test.js → integration/infra/1-security/selinux.test.js} +2 -4
  492. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  493. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  494. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  495. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  496. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  497. package/test/integration/infra/2-network/wireguard-cli.test.js +1187 -0
  498. package/test/{wireguard-edge.test.js → integration/infra/2-network/wireguard-edge.test.js} +649 -55
  499. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  500. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  501. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  502. package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
  503. package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
  504. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  505. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  506. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  507. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +6 -6
  508. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +4 -4
  509. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  510. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  511. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  512. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  513. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  514. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  515. package/test/support/shell-harness.js +73 -0
  516. package/test/unit/build-template.test.js +97 -0
  517. package/test/unit/build-workflow-offline.test.js +132 -0
  518. package/test/unit/catalog.test.js +71 -0
  519. package/test/unit/client-build-docs.test.js +361 -0
  520. package/test/unit/client-bundle.test.js +313 -0
  521. package/test/unit/conf-loading.test.js +303 -0
  522. package/test/unit/conf-resolution.test.js +996 -0
  523. package/test/unit/coverage-artifact.test.js +221 -0
  524. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  525. package/test/unit/cyberia/instance-backup.test.js +183 -0
  526. package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
  527. package/test/unit/cyberia/publish-workflow.test.js +22 -0
  528. package/test/unit/cyberia/stat-balance.test.js +69 -0
  529. package/test/unit/deploy-env-resolution.test.js +38 -0
  530. package/test/unit/deploy-log-table.test.js +518 -0
  531. package/test/unit/downloader-integrity.test.js +98 -0
  532. package/test/unit/execution-profiles.test.js +219 -0
  533. package/test/unit/file-reference-registry.test.js +141 -0
  534. package/test/unit/fleet-sync-source.test.js +149 -0
  535. package/test/unit/fs-storage-paths.test.js +596 -0
  536. package/test/unit/logger-redaction.test.js +141 -0
  537. package/test/unit/package.test.js +457 -0
  538. package/test/unit/prepare-host.test.js +382 -0
  539. package/test/unit/process-environment.test.js +18 -0
  540. package/test/unit/propagation-message.test.js +78 -0
  541. package/test/unit/redeploy-plan.test.js +24 -0
  542. package/test/unit/release-bump.test.js +474 -0
  543. package/test/unit/ssh-output-redaction.test.js +28 -0
  544. package/test/unit/start-options.test.js +46 -0
  545. package/test/unit/test-tiers.test.js +220 -0
  546. package/test/unit/zip-archive.test.js +142 -0
  547. package/vitest.config.js +66 -0
  548. package/.nycrc +0 -9
  549. package/deploy/lib/logging.sh +0 -96
  550. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  551. package/manifests/mongodb/pv-pvc.yaml +0 -59
  552. package/scripts/coverall-test-cyberia.sh +0 -24
  553. package/scripts/coverall-test.sh +0 -24
  554. package/scripts/link-local-underpost-cli.sh +0 -6
  555. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
  556. package/src/api/user/user.build.js +0 -16
  557. package/src/cli/env.js +0 -177
  558. package/src/client/components/core/Blockchain.js +0 -41
  559. package/src/client/components/core/Blog.js +0 -9
  560. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  561. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
  562. package/src/client/public/default/assets/background/dark.jpg +0 -0
  563. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  564. package/src/client/public/default/assets/background/white0.jpg +0 -0
  565. package/src/client/public/doc/favicon.ico +0 -0
  566. package/src/client/public/doc/sitemap +0 -148
  567. package/src/server/downloader.js +0 -73
  568. package/src/server/environment.js +0 -98
@@ -1,78 +1,41 @@
1
1
  /**
2
- * Canonical server-owned defaults for the Cyberia runtime.
2
+ * Canonical simulation defaults for the Cyberia runtime: per-entity-type item
3
+ * sets, simulation / AOI / combat / economy / skill rules, equipment rules,
4
+ * status-icon numeric IDs, and the seed content (dialogues, actions, quests)
5
+ * the CLI writes to Mongo.
3
6
  *
4
- * Single source of truth for everything the **authoritative simulation
5
- * server** (cyberia-server, Go) needs to build and run a world:
7
+ * STRICT BOUNDARY: never import this from `src/client/`. The browser bundler
8
+ * resolves imports recursively, so one browser-side import ships the whole
9
+ * simulation defaults in the public JS payload.
6
10
  *
7
- * - per-entity-type live / dead / drop item configuration
8
- * - simulation, AOI, combat, economy, and skill rules
9
- * - equipment rules
10
- * - status-icon **numeric IDs** (visuals live in client defaults)
11
- * - seed content (dialogues, actions, quests) consumed by the
12
- * persistence/CLI tooling that bootstraps the world
13
- * - native-dependency pin list for chain-bridge tooling
14
- *
15
- * STRICT BOUNDARY
16
- * ---------------
17
- * NEVER imported from any file under `src/client/`. The browser bundler
18
- * resolves imports recursively, so a single browser-side import would drag
19
- * the entire simulation defaults into the public JS payload.
20
- *
21
- * Shared content **vocabulary** (item/entity type enums, the
22
- * `DefaultCyberiaItems` registry, `ENTITY_TYPE_TO_ITEM_TYPES`, the quest
23
- * step objective enum) lives in `SharedDefaultsCyberia.js`. This file
24
- * re-imports those so the browser editor never needs to reach into
25
- * server-defaults to learn the schema.
26
- *
27
- * Consumers:
28
- * - cyberia-instance-conf model + grpc-server (gameplay config)
29
- * - cyberia-world-generator / cyberia-fallback-world (world build)
30
- * - cyberia-quest / cyberia-action / Mongo seed scripts (content)
31
- * - bin/cyberia + bin/build + bin/deploy (CLI + chain bridge)
11
+ * Shared content vocabulary (item / entity type enums, `DefaultCyberiaItems`,
12
+ * `ENTITY_TYPE_TO_ITEM_TYPES`, quest step objectives) lives in
13
+ * `SharedDefaultsCyberia.js`.
32
14
  *
33
15
  * @module src/api/cyberia-server-defaults/cyberia-server-defaults.js
34
16
  */
35
17
 
36
- // The canonical client-defaults module lives under src/client/ so the
37
- // browser bundler can resolve the URL inside the client tree. Engine-side
38
- // Node imports work from any path, so we reach into it from here.
18
+ // Shared vocabulary lives under src/client/ so the browser bundler resolves it.
39
19
  import {
40
20
  ITEM_TYPES,
21
+ STAT_TYPES,
22
+ STAT_MODIFIER_MAX,
23
+ ENTITY_LEVEL_MAX,
41
24
  ENTITY_TYPES,
42
25
  SKILL_LOGIC_ID_VALUES,
43
26
  isCanonicalSkillLogicId,
27
+ AUDIO_BUSES,
28
+ AUDIO_BUS_MUSIC,
29
+ AUDIO_BUS_SFX,
30
+ AUDIO_LOGIC_ID_BUSES,
31
+ AUDIO_LOGIC_ID_VALUES,
32
+ isCanonicalAudioLogicId,
44
33
  } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
45
34
 
46
35
  export const DOCKER_COMPOSE_ID = 'cyberia';
47
36
 
48
37
  export const DEPLOY_ID = 'dd-cyberia';
49
38
 
50
- /**
51
- * Native-dependency pin list. Consumed by `bin/build.js` and `bin/deploy.js`
52
- * when materialising the Cyberia subtree so versions stay reproducible
53
- * across CI / production deploys.
54
- */
55
- export class CyberiaDependencies {
56
- static 'maxrects-packer' = '^2.7.3';
57
- static pngjs = '^7.0.0';
58
- static jimp = '^1.6.0';
59
- static sharp = '^0.34.5';
60
- static ethers = '~6.16.0';
61
- }
62
-
63
- export const DOCKER_SCRIPTS = {
64
- 'docker:generate': `node bin docker-compose --generate --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
65
- 'docker:up': `node bin docker-compose --up --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
66
- 'docker:up:build': `node bin docker-compose --up --build --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
67
- 'docker:down': `node bin docker-compose --down --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
68
- 'docker:down:volumes': `node bin docker-compose --down --volumes --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
69
- 'docker:restart': `node bin docker-compose --restart --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
70
- 'docker:pull': `node bin docker-compose --pull --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
71
- 'docker:logs': `node bin docker-compose --logs --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
72
- 'docker:status': `node bin docker-compose --status --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
73
- 'docker:reset': `node bin docker-compose --reset --deploy-id ${DEPLOY_ID} --docker-compose-id ${DOCKER_COMPOSE_ID}`,
74
- };
75
-
76
39
  // ─────────────────────────────────────────────────────────────────────────────
77
40
  // Skill / action / quest seed content
78
41
  // ─────────────────────────────────────────────────────────────────────────────
@@ -86,10 +49,9 @@ export const DOCKER_SCRIPTS = {
86
49
  * - `skills` (expanded metadata with name, description, summoned entity)
87
50
  *
88
51
  * Consumers:
89
- * - `CYBERIA_INSTANCE_CONF_DEFAULTS.skillConfig` (gRPC fallback defaults)
90
- * - `bin/cyberia.js seed-skills` (upsert into the cyberia-skill collection
91
- * the authoritative CyberiaSkill model keeps the full record, including the
92
- * `skills` metadata the instance-conf skillConfig schema drops)
52
+ * - `bin/cyberia.js seed-skills` (upsert into the cyberia-skill collection, which is the
53
+ * authoritative store an instance then runs the subset its own content triggers)
54
+ * - the fallback world, which has no collection to read
93
55
  */
94
56
  export const DefaultSkillConfig = [
95
57
  {
@@ -118,18 +80,6 @@ export const DefaultSkillConfig = [
118
80
  },
119
81
  ],
120
82
  },
121
- // {
122
- // triggerItemId: 'anon',
123
- // logicEventIds: ['doppelganger'],
124
- // skills: [
125
- // {
126
- // logicEventId: 'doppelganger',
127
- // name: 'Doppelganger',
128
- // description: 'Summons a passive clone of yourself that wanders nearby. Spawn chance scales with Intelligence.',
129
- // summonedEntityItemId: '$active_skin',
130
- // },
131
- // ],
132
- // },
133
83
  {
134
84
  triggerItemId: 'hatchet',
135
85
  logicEventIds: ['projectile'],
@@ -145,9 +95,7 @@ export const DefaultSkillConfig = [
145
95
  },
146
96
  ];
147
97
 
148
- // Fail fast on a non-canonical logicEventId: the canonical LogicId registry in
149
- // SharedDefaultsCyberia.js is the single source of truth, so a typo or a handler
150
- // the dispatcher does not know must surface at boot, not as a silent no-op skill.
98
+ // A logicEventId absent from the SharedDefaultsCyberia registry fails at boot.
151
99
  for (const cfg of DefaultSkillConfig) {
152
100
  for (const logicEventId of [...(cfg.logicEventIds || []), ...(cfg.skills || []).map((sk) => sk.logicEventId)]) {
153
101
  if (!isCanonicalSkillLogicId(logicEventId)) {
@@ -163,18 +111,11 @@ for (const cfg of DefaultSkillConfig) {
163
111
  // Lain demo quests (testing only) — single source of truth
164
112
  // ─────────────────────────────────────────────────────────────────────────────
165
113
  //
166
- // Offered by Lain (15,22): a batch of simple no-prerequisite quests so the
167
- // Quest Journal can exercise pagination with 4+ concurrently active quests.
168
- //
169
- // These specs drive THREE derived structures so they can never drift apart:
170
- // - DefaultCyberiaQuests — the quest definitions
171
- // - DefaultCyberiaDialogues — one quest-talk dialogue per talk objective
172
- // - DefaultCyberiaActions — Lain's questDialogueCodes mapping
114
+ // No-prerequisite quests offered by Lain (15,22). These specs derive the quest
115
+ // definitions, their quest-talk dialogues, and Lain's questDialogueCodes map.
173
116
  //
174
- // Every `talk` objective MUST have its own quest-talk dialogue mapped on the
175
- // provider's action: the server validates a talk objective only when the
176
- // completed dialogue matches that mapping, so a quest without one could be
177
- // satisfied by reading the NPC's default greeting.
117
+ // Each `talk` objective needs its own quest-talk dialogue on the provider's
118
+ // action. The server validates a talk only against that mapping.
178
119
 
179
120
  const LAIN_DEMO_QUEST_SPECS = [
180
121
  {
@@ -288,12 +229,7 @@ const LAIN_DEMO_TALK_SPECS = LAIN_DEMO_QUEST_SPECS.filter((q) =>
288
229
  (q.steps || []).some((s) => (s.objectives || []).some((o) => o.type === 'talk' && o.itemId === 'lain')),
289
230
  );
290
231
 
291
- /**
292
- * Default dialogue seeds. Mirrors `CyberiaDialogue` model schema:
293
- * { code, order, speaker, text, mood }
294
- *
295
- * Used by the seed/migration script to populate Mongo on first boot.
296
- */
232
+ /** Dialogue seeds ({ code, order, speaker, text, mood }) the CLI writes to Mongo. */
297
233
  export const DefaultCyberiaDialogues = [
298
234
  {
299
235
  code: 'default-coin',
@@ -553,12 +489,10 @@ export const DefaultCyberiaDialogues = [
553
489
  * Each entry follows the `CyberiaAction` model schema.
554
490
  */
555
491
  export const DefaultCyberiaActions = [
556
- // An action has no `type`: it declares the capabilities available at a cell.
557
- // `code` is a generic location slug; `label` is the bot's overhead name (the
558
- // client fetches it by code via REST). The NPC skin is derived from
559
- // `dialogCode` (default-<skin>). `questDialogueCodes` maps each quest the NPC
560
- // handles to the dialogue shown for it (offer + talk-objective validation).
561
- // The quests an NPC OFFERS are those whose source cell matches the action's.
492
+ // An action declares the capabilities available at a cell. `code` is a
493
+ // location slug, `label` the bot's overhead name, and the NPC skin comes from
494
+ // `dialogCode` (default-<skin>). `questDialogueCodes` maps a quest to its
495
+ // dialogue. An NPC offers the quests whose source cell matches the action's.
562
496
  {
563
497
  code: 'loc-fallback-map-0-12-10',
564
498
  label: 'Wason',
@@ -592,9 +526,8 @@ export const DefaultCyberiaActions = [
592
526
  questDialogueCodes: [{ questCode: 'bounty-quest-alpha', dialogCode: 'quest-talk-agent' }],
593
527
  },
594
528
  {
595
- // Shop capability: an action carrying shopItems is a vendor. The client
596
- // surfaces a Shop tab for it and the simulation validates every purchase
597
- // against this catalog (price item + quantity).
529
+ // Shop capability: an action with shopItems is a vendor. The simulation
530
+ // validates every purchase against this catalog (price item + quantity).
598
531
  code: 'loc-fallback-map-0-18-16',
599
532
  label: 'Punk',
600
533
  sourceMapCode: 'fallback-map-0',
@@ -604,12 +537,9 @@ export const DefaultCyberiaActions = [
604
537
  shopItems: [{ itemId: 'tim-knife', priceItemId: 'coin', priceQty: 10 }],
605
538
  },
606
539
  {
607
- // Assembler capability: an action carrying craftRecipes is a fabrication
608
- // terminal. The client surfaces an Assembly tab for it and the simulation
609
- // validates every synthesis against these recipes (ingredients outputs).
610
- // Every id here is obtainable inside the fallback world — wood from the
611
- // resource nodes, coin from field drops — so the loop is playable on a
612
- // fresh spawn with no seeding.
540
+ // Assembler capability: an action with craftRecipes is a fabrication
541
+ // terminal. The simulation validates every synthesis against these recipes.
542
+ // Every id here is obtainable in the fallback world with no seeding.
613
543
  code: 'loc-fallback-map-0-15-16',
614
544
  label: 'Eiri',
615
545
  sourceMapCode: 'fallback-map-0',
@@ -635,10 +565,7 @@ export const DefaultCyberiaActions = [
635
565
  craftTimeMs: 5000,
636
566
  },
637
567
  {
638
- // Multi-output: one blade breaks down into three component stacks, so
639
- // the assembly UI and the arrival flights are exercised with more than
640
- // a single result slot. Closes the loop with Punk's shop — buy a knife,
641
- // salvage it back into parts.
568
+ // Multi-output salvage recipe: one blade breaks down into three stacks.
642
569
  outputItems: [
643
570
  { itemId: 'wood-drop-1', qty: 2 },
644
571
  { itemId: 'wood-drop-2', qty: 1 },
@@ -650,9 +577,8 @@ export const DefaultCyberiaActions = [
650
577
  ],
651
578
  },
652
579
  {
653
- // Storage capability: an action carrying storageSlots is a personal vault.
654
- // The client surfaces a Storage tab whose square grid is sized from the
655
- // capacity (25 → 5x5) and the simulation owns the contents.
580
+ // Storage capability: an action with storageSlots is a personal vault. The
581
+ // simulation owns the contents; the slot count sizes the grid (25 5x5).
656
582
  code: 'loc-fallback-map-0-12-22',
657
583
  label: 'Kaneki',
658
584
  sourceMapCode: 'fallback-map-0',
@@ -668,9 +594,7 @@ export const DefaultCyberiaActions = [
668
594
  sourceCellX: 15,
669
595
  sourceCellY: 22,
670
596
  dialogCode: 'default-lain',
671
- // Every demo quest with a `talk` objective gets its own quest-talk
672
- // dialogue here — without the mapping the server would accept the
673
- // default greeting as the talk, and no quest-talk button would show.
597
+ // One quest-talk dialogue per demo quest that carries a `talk` objective.
674
598
  questDialogueCodes: LAIN_DEMO_TALK_SPECS.map((q) => ({
675
599
  questCode: lainDemoQuestCode(q.suffix),
676
600
  dialogCode: lainDemoTalkDialogCode(q.suffix),
@@ -713,8 +637,7 @@ export const DefaultCyberiaQuests = [
713
637
  rewards: [{ itemId: 'coin', quantity: 50 }],
714
638
  },
715
639
  {
716
- // Parallel initial mission same source cell as Wason (12,10), no
717
- // prerequisites, so the player can accept it alongside the intro quest.
640
+ // Parallel initial mission: same source cell as the intro quest, no prerequisites.
718
641
  code: 'wason-errand',
719
642
  title: "Wason's Errand",
720
643
  description: 'Gather coins from the field and bring them back to Wason.',
@@ -767,8 +690,7 @@ export const DefaultCyberiaQuests = [
767
690
  rewards: [{ itemId: 'hatchet', quantity: 1 }],
768
691
  },
769
692
  // ── Demo quests (testing only) ─────────────────────────────────────────────
770
- // Built from LAIN_DEMO_QUEST_SPECS so the quest definitions, their
771
- // quest-talk dialogues, and Lain's questDialogueCodes mapping stay in sync.
693
+ // Derived from LAIN_DEMO_QUEST_SPECS.
772
694
  ...LAIN_DEMO_QUEST_SPECS.map((q) => ({
773
695
  code: lainDemoQuestCode(q.suffix),
774
696
  title: q.title,
@@ -788,11 +710,9 @@ export const DefaultCyberiaQuests = [
788
710
  // ─────────────────────────────────────────────────────────────────────────────
789
711
 
790
712
  /**
791
- * Governs which ObjectLayer item types may be simultaneously active on an
792
- * entity and enforces the one-per-type constraint. The server validates
793
- * every item_activation request against these rules, scoped to the item set
794
- * of the entity's current state: liveItemIds while alive, deadItemIds while
795
- * in the Fragmented State (see DEFAULT_DEAD_ITEM_ID).
713
+ * Which ObjectLayer item types may be active together on an entity. The server
714
+ * validates every item_activation against these rules, scoped to the item set
715
+ * of the entity state: liveItemIds while alive, deadItemIds while dead.
796
716
  */
797
717
  export const EQUIPMENT_RULES_DEFAULTS = Object.freeze({
798
718
  activeItemTypes: [ITEM_TYPES.skin, ITEM_TYPES.breastplate, ITEM_TYPES.weapon],
@@ -805,13 +725,10 @@ export const EQUIPMENT_RULES_DEFAULTS = Object.freeze({
805
725
  // ─────────────────────────────────────────────────────────────────────────────
806
726
 
807
727
  /**
808
- * Numeric Entity Status Indicator (ESI) IDs. The Go server stamps one of
809
- * these u8 IDs on every entity in the AOI binary wire format; the client
810
- * resolves the icon stem + border colour from its own presentation
811
- * defaults table (see `SharedDefaultsCyberia.js#STATUS_ICONS_PRESENTATION`).
812
- *
813
- * IDs MUST stay in sync with:
814
- * cyberia-server/src/entity_status.go (StatusNone … StatusResourceExtracted)
728
+ * Numeric Entity Status Indicator (ESI) IDs. The server stamps one u8 ID on
729
+ * every entity in the AOI wire format; the client resolves the icon from
730
+ * `SharedDefaultsCyberia.js#STATUS_ICONS_PRESENTATION`. These IDs are wire
731
+ * contract: append only, never renumber.
815
732
  */
816
733
  export const STATUS_ICONS = Object.freeze([
817
734
  { id: 0, name: 'none', description: 'No icon (skill/coin bots, world objects)' },
@@ -862,14 +779,14 @@ export const RESOURCE_ENTITY_TYPE_DEFAULTS = Object.freeze([
862
779
  liveItemIds: ['wood-1'],
863
780
  deadItemIds: ['wood-extracted-1'],
864
781
  dropItemIds: ['wood-drop-1'],
865
- defaultObjectLayers: [],
782
+ inventoryItemsIds: [],
866
783
  }),
867
784
  Object.freeze({
868
785
  entityType: ENTITY_TYPES.resource,
869
786
  liveItemIds: ['wood-2'],
870
787
  deadItemIds: ['wood-extracted-2'],
871
788
  dropItemIds: ['wood-drop-2'],
872
- defaultObjectLayers: [],
789
+ inventoryItemsIds: [],
873
790
  }),
874
791
  ]);
875
792
 
@@ -877,63 +794,51 @@ export const RESOURCE_ENTITY_TYPE_DEFAULTS = Object.freeze([
877
794
  export const RESOURCE_ENTITY_TYPE_DEFAULT = RESOURCE_ENTITY_TYPE_DEFAULTS[0];
878
795
 
879
796
  /**
880
- * Canonical dead-state visual (Fragmentation): applied by cyberia-server when
881
- * an entity-type default declares no deadItemIds. Dead-state items this
882
- * default and any deadItemIds configured via MongoDB form the Fragmented
883
- * State's aesthetic loadout: equippable only while the entity is dead, under
884
- * EQUIPMENT_RULES_DEFAULTS scoped to the dead-item set, and remembered across
885
- * deaths. Only this default id is excluded from the player inventory wire;
886
- * the rest appear (lock-badged while alive). No defaultObjectLayers row is
887
- * needed — the death handler appends missing dead items on demand.
888
- * Mirrored by `defaultDeadItemID` in cyberia-server/game/dead_items.go.
797
+ * Canonical dead-state visual (Fragmentation). The server applies it when an
798
+ * entity-type default declares no deadItemIds. Dead items are equippable only
799
+ * while the entity is dead and are kept across deaths. This id stays out of
800
+ * the player inventory wire; other dead items appear in it.
889
801
  */
890
802
  export const DEFAULT_DEAD_ITEM_ID = 'fragmentation';
891
803
 
892
804
  /**
893
- * Per-entity-type defaults consumed by the Go server (live / dead / drop
894
- * item IDs and the seed inventory for newly spawned entities).
805
+ * Per-entity-type defaults the simulation consumes: live / dead / drop item IDs
806
+ * and the seed inventory of a new entity.
895
807
  *
896
808
  * Field reference:
897
- * entityType — server-side category string.
898
- * liveItemIds — ObjectLayer item IDs while the entity is alive.
899
- * deadItemIds — IDs swapped in on death / ghost state.
900
- * dropItemIds — IDs granted to the killer on resource depletion.
901
- * defaultObjectLayers initial inventory rows ({itemId, active, qty}).
809
+ * entityType — server-side category string.
810
+ * liveItemIds — ObjectLayer item IDs while the entity is alive.
811
+ * deadItemIDs — IDs swapped in on death / ghost state.
812
+ * dropItemIds — IDs granted to the killer on resource depletion.
813
+ * inventoryItemsIds IDs the entity carries but never activates by lifecycle.
814
+ * overrideItemsIdsState — per-id overrides of what those lists derive.
815
+ *
816
+ * The three lifecycle lists are discriminators, not separate inventories: an entity holds one
817
+ * inventory and the runtime activates the slots the context calls for. `inventoryItemsIds` is only
818
+ * for what no lifecycle state ever activates — a coin balance, say. See
819
+ * {@link resolveEntityInventory}, which is where that single inventory is derived.
902
820
  */
903
821
  export const ENTITY_TYPE_DEFAULTS = Object.freeze([
904
822
  {
905
823
  entityType: ENTITY_TYPES.player,
906
824
  liveItemIds: ['anon', 'atlas_pistol_mk2'],
907
825
  deadItemIds: [DEFAULT_DEAD_ITEM_ID],
908
- defaultObjectLayers: [
909
- { itemId: 'anon', active: true, quantity: 1 },
910
- { itemId: 'atlas_pistol_mk2', active: true, quantity: 1 },
911
- { itemId: 'coin', active: false, quantity: 0 },
912
- ],
826
+ inventoryItemsIds: ['coin'],
913
827
  },
914
828
  {
915
829
  entityType: ENTITY_TYPES.other_player,
916
830
  liveItemIds: ['anon', 'atlas_pistol_mk2'],
917
831
  deadItemIds: [DEFAULT_DEAD_ITEM_ID],
918
- defaultObjectLayers: [
919
- { itemId: 'anon', active: true, quantity: 1 },
920
- { itemId: 'atlas_pistol_mk2', active: true, quantity: 1 },
921
- { itemId: 'coin', active: false, quantity: 0 },
922
- ],
832
+ inventoryItemsIds: ['coin'],
923
833
  },
924
834
  {
925
835
  entityType: ENTITY_TYPES.bot,
926
836
  liveItemIds: ['purple'],
927
837
  deadItemIds: [DEFAULT_DEAD_ITEM_ID],
928
- defaultObjectLayers: [
929
- { itemId: 'purple', active: true, quantity: 1 },
930
- { itemId: 'coin', active: false, quantity: 0 },
931
- ],
838
+ inventoryItemsIds: ['coin'],
932
839
  },
933
- // Fallback-world mission/action givers. Resolved by their active skin (the
934
- // liveItemIds key), these bots take the canonical `provider` behavior: they
935
- // barely move from their spawn and are immortal. Lain only talks in place, so
936
- // she is fully static. Authors can retarget any of these via EntityEngineCyberia.
840
+ // Fallback-world mission givers, keyed by their active skin. `provider` bots
841
+ // stay near their spawn and are immortal; `provider-static` bots do not move.
937
842
  { entityType: ENTITY_TYPES.bot, liveItemIds: ['wason'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
938
843
  { entityType: ENTITY_TYPES.bot, liveItemIds: ['alex'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
939
844
  { entityType: ENTITY_TYPES.bot, liveItemIds: ['agent'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
@@ -960,37 +865,277 @@ export const ENTITY_TYPE_DEFAULTS = Object.freeze([
960
865
  entityType: ENTITY_TYPES.skill,
961
866
  liveItemIds: ['atlas_pistol_mk2_bullet'],
962
867
  deadItemIds: [],
963
- defaultObjectLayers: [{ itemId: 'atlas_pistol_mk2_bullet', active: true, quantity: 1 }],
868
+ inventoryItemsIds: [],
964
869
  },
965
870
  {
966
871
  entityType: ENTITY_TYPES.coin,
967
872
  liveItemIds: ['coin'],
968
873
  deadItemIds: [],
969
- defaultObjectLayers: [{ itemId: 'coin', active: true, quantity: 1 }],
874
+ inventoryItemsIds: [],
970
875
  },
971
- { entityType: ENTITY_TYPES.floor, liveItemIds: ['grass'], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
972
- { entityType: ENTITY_TYPES.obstacle, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
973
- { entityType: ENTITY_TYPES.portal, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
974
- { entityType: ENTITY_TYPES.foreground, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
876
+ { entityType: ENTITY_TYPES.floor, liveItemIds: ['grass'], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
877
+ { entityType: ENTITY_TYPES.obstacle, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
878
+ { entityType: ENTITY_TYPES.portal, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
879
+ { entityType: ENTITY_TYPES.foreground, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
975
880
  // Static decorator — non-moving, passable, depth-sorted. Visuals come from the
976
881
  // map definition / world generator; no live/dead/drop rotation.
977
- { entityType: ENTITY_TYPES.static, liveItemIds: [], deadItemIds: [], dropItemIds: [], defaultObjectLayers: [] },
882
+ { entityType: ENTITY_TYPES.static, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
978
883
  ...RESOURCE_ENTITY_TYPE_DEFAULTS,
979
884
  ]);
980
885
 
886
+ /**
887
+ * The default a placed entity resolves to, by the item ids it already carries.
888
+ *
889
+ * Mirrors `resolveEntityDefaultBuild` in cyberia-server/game/entity_defaults.go, which is the rule
890
+ * the simulation applies at spawn: a default matches only when the entity carries ALL of its live
891
+ * ids, and the most specific match wins — the one requiring the largest set, so `{purple, pistol}`
892
+ * beats `{purple}`. Ties keep the earlier default, which makes the order a world declares them in
893
+ * significant. With no containing match, the first default of that type answers.
894
+ *
895
+ * @param {{entityType: string, itemIds?: string[]}} entity - Placed entity type and its item ids.
896
+ * @param {Array<{entityType: string, liveItemIds?: string[]}>} defaults - Candidate defaults, in order.
897
+ * @returns {object|undefined} The matched default, or undefined when the type has none.
898
+ */
899
+ export function resolveEntityDefaultBuild({ entityType, itemIds = [] }, defaults = []) {
900
+ const carried = new Set(itemIds.filter(Boolean));
901
+ let firstOfType;
902
+ let matched;
903
+ let matchedSize = 0;
904
+ for (const candidate of defaults) {
905
+ if (candidate?.entityType !== entityType) continue;
906
+ firstOfType ??= candidate;
907
+ const liveItemIds = candidate.liveItemIds || [];
908
+ if (carried.size === 0 || liveItemIds.length === 0) continue;
909
+ if (!liveItemIds.every((itemId) => carried.has(itemId))) continue;
910
+ if (liveItemIds.length > matchedSize) {
911
+ matched = candidate;
912
+ matchedSize = liveItemIds.length;
913
+ }
914
+ }
915
+ return matched ?? firstOfType;
916
+ }
917
+
918
+ /**
919
+ * The canonical inventory contract: one entity, one inventory, derived.
920
+ *
921
+ * An entity carries the union of every item id its default names — the lifecycle discriminators
922
+ * (live / dead / drop) and the inventory-only extras — deduplicated, in that order. Which slots
923
+ * are *active* is not stored anywhere: it follows from the discriminator the id belongs to, and
924
+ * the runtime flips them as context changes (`activateOrAppendLayer` in
925
+ * cyberia-server/game/dead_items.go activates the slot that is already there). Seeding the whole
926
+ * union is what lets it activate rather than append.
927
+ *
928
+ * Spawn state is alive, so the live ids are the active ones. A worn item and an inventory-only
929
+ * item are stock the entity holds, so each starts as one unit. A dead or drop id is a lifecycle
930
+ * slot the runtime activates or scatters later, so it starts empty. The server owns the coin
931
+ * quantity from spawn on, whatever the seed says.
932
+ *
933
+ * `overrideItemsIdsState` adjusts that derivation for one carried id: `active` forces the spawn
934
+ * state — a skin the equipment rules would otherwise leave inactive — and `quantity` sizes a
935
+ * stack, which is how a drop bundle declares how many it scatters. It never adds an id: the union
936
+ * decides membership, an override only what a member starts as, so a rule naming an id no list
937
+ * carries does nothing at all.
938
+ *
939
+ * Activating one item can deactivate another. `EQUIPMENT_RULES_DEFAULTS` allows one active item
940
+ * per type, so an override that activates a skin is a decision about which skin the entity wears:
941
+ * the one the lists derived gives way to the one the author named. Only the governed types are
942
+ * constrained, and only where the item's type is known — a resource visual, a coin, anything the
943
+ * rules do not name, passes through untouched.
944
+ *
945
+ * @param {{liveItemIds?:string[],deadItemIds?:string[],dropItemIds?:string[],inventoryItemsIds?:string[],
946
+ * overrideItemsIdsState?:Array<{itemId:string,active?:boolean,quantity?:number}>}} entityDefault
947
+ * @param {{itemTypes?:Object<string,string>|Map<string,string>}} [context] - itemId → item type, where known.
948
+ * @returns {Array<{itemId:string,active:boolean,quantity:number}>} Inventory rows, as the wire carries them.
949
+ */
950
+ export function resolveEntityInventory(entityDefault = {}, { itemTypes } = {}) {
951
+ const live = new Set(entityDefault.liveItemIds || []);
952
+ const overrides = new Map(
953
+ (entityDefault.overrideItemsIdsState || []).filter((rule) => rule?.itemId).map((rule) => [rule.itemId, rule]),
954
+ );
955
+ const itemIds = [
956
+ ...new Set(
957
+ [
958
+ ...(entityDefault.liveItemIds || []),
959
+ ...(entityDefault.deadItemIds || []),
960
+ ...(entityDefault.dropItemIds || []),
961
+ ...(entityDefault.inventoryItemsIds || []),
962
+ ].filter(Boolean),
963
+ ),
964
+ ];
965
+ const drops = new Set(entityDefault.dropItemIds || []);
966
+ const stock = new Set(entityDefault.inventoryItemsIds || []);
967
+ const rows = itemIds.map((itemId) => {
968
+ const override = overrides.get(itemId);
969
+ const active = 'boolean' === typeof override?.active ? override.active : live.has(itemId);
970
+ const held = active || stock.has(itemId);
971
+ const quantity = Number.isFinite(override?.quantity) ? override.quantity : held ? 1 : 0;
972
+ // A drop id scatters on death unless an override says how often. Rows that are not drops
973
+ // carry the same 1, so every consumer reads one field and never a missing one.
974
+ const dropChance =
975
+ drops.has(itemId) && Number.isFinite(override?.dropChance)
976
+ ? Math.min(1, Math.max(0, override.dropChance))
977
+ : 1;
978
+ return { itemId, active, quantity, dropChance };
979
+ });
980
+ return applyEquipmentRules(rows, { itemTypes, overrides });
981
+ }
982
+
983
+ /**
984
+ * One active item per governed type, with the authored choice ahead of the derived one.
985
+ *
986
+ * An override that activates an item states which item of its type the entity wears; a row the
987
+ * lists merely derived yields to it. Two overrides of the same type resolve in list order, first
988
+ * kept — the same first-wins the runtime applies when it normalizes a loadout.
989
+ *
990
+ * @param {Array<{itemId:string,active:boolean,quantity:number}>} rows - Resolved inventory rows.
991
+ * @param {{itemTypes?:Object<string,string>|Map<string,string>, overrides?:Map<string,object>}} context
992
+ * @returns {Array<{itemId:string,active:boolean,quantity:number}>} The same rows, contested types settled.
993
+ */
994
+ function applyEquipmentRules(rows, { itemTypes, overrides = new Map() } = {}) {
995
+ if (!itemTypes || !EQUIPMENT_RULES_DEFAULTS.onePerType) return rows;
996
+ const typeOf = (itemId) => (itemTypes instanceof Map ? itemTypes.get(itemId) : itemTypes[itemId]) || '';
997
+ const governed = new Set(EQUIPMENT_RULES_DEFAULTS.activeItemTypes);
998
+ const claimed = new Set();
999
+ // Authored first, so an override wins the slot it asks for; the derived rows fill what is left.
1000
+ const contenders = [
1001
+ ...rows.filter((row) => row.active && overrides.get(row.itemId)?.active === true),
1002
+ ...rows.filter((row) => row.active && overrides.get(row.itemId)?.active !== true),
1003
+ ];
1004
+ const deactivated = new Set();
1005
+ for (const row of contenders) {
1006
+ const type = typeOf(row.itemId);
1007
+ if (!governed.has(type)) continue;
1008
+ if (claimed.has(type)) deactivated.add(row.itemId);
1009
+ else claimed.add(type);
1010
+ }
1011
+ // Taking something off does not take it away: the entity still carries the stack, so only the
1012
+ // worn flag changes. A row emptied here would leave the inventory holding nothing.
1013
+ return rows.map((row) => (deactivated.has(row.itemId) ? { ...row, active: false } : row));
1014
+ }
1015
+
981
1016
  // ─────────────────────────────────────────────────────────────────────────────
982
- // Player spawn
1017
+ // Audio seed content
983
1018
  // ─────────────────────────────────────────────────────────────────────────────
984
1019
 
985
1020
  /**
986
- * Canonical default player spawn policy. Mirrors PlayerSpawnSchema in
987
- * cyberia-instance.model.js and the Go PlayerSpawnConfig: when `random` is false
988
- * and `sourceMapCode` names a loaded map, new players spawn at
989
- * (sourceCellX, sourceCellY) on it; otherwise (random, or no/unknown map) they
990
- * spawn at a random walkable cell on a random map.
1021
+ * The audio bank the seeded world needs: one `cyberia-audio` module per asset code.
1022
+ *
1023
+ * `bus` names the module directory (`cyberia-audio/src/audio-module/<bus-id>/<code>`) and is the
1024
+ * asset's natural route; `options` are that module's render parameters. The code is also the
1025
+ * identity the client fetches by, so a code here must exist as a module there.
1026
+ *
1027
+ * @type {ReadonlyArray<{code:string,bus:'music'|'sfx',options?:object}>}
1028
+ */
1029
+ export const DEFAULT_AUDIO_BANK = Object.freeze([
1030
+ ...['shoot', 'coin', 'drop', 'item-pickup', 'victory', 'level-up', 'death', 'heal', 'hit', 'portal', 'ui-click', 'footsteps'].map(
1031
+ (code) => Object.freeze({ code, bus: AUDIO_BUS_SFX }),
1032
+ ),
1033
+ Object.freeze({ code: 'exploration', bus: AUDIO_BUS_MUSIC, options: { cycles: 1 } }),
1034
+ Object.freeze({ code: 'combat', bus: AUDIO_BUS_MUSIC, options: { rounds: 1 } }),
1035
+ Object.freeze({ code: 'boss', bus: AUDIO_BUS_MUSIC }),
1036
+ Object.freeze({ code: 'portal-cooldown', bus: AUDIO_BUS_MUSIC }),
1037
+ Object.freeze({ code: 'craft', bus: AUDIO_BUS_MUSIC }),
1038
+ ]);
1039
+
1040
+ /** Map-level audio settings the seed writes onto every seeded map. */
1041
+ export const DEFAULT_AUDIO_SETTINGS = Object.freeze({ volume: 0.8, crossfadeMs: 800 });
1042
+
1043
+ /**
1044
+ * The music bed a seeded map falls back to when no event is holding the bus.
1045
+ *
1046
+ * One bed for every map of a world: what a map sounds like when nothing is happening is the
1047
+ * world's ambient identity, and the events are what make a place sound different — a combat or a
1048
+ * boss bed is bound to the moment it belongs to, not to a map index.
1049
+ */
1050
+ export const DEFAULT_MAP_MUSIC = 'exploration';
1051
+
1052
+ /**
1053
+ * Canonical runtime-event → asset bindings.
1054
+ *
1055
+ * `logicEventId` is the semantic event the client emits (an audio LogicId, or a skill LogicId the
1056
+ * dispatcher runs); `audioCode` is the bank entry that answers it.
1057
+ *
1058
+ * @type {ReadonlyArray<{logicEventId:string,audioCode:string}>}
1059
+ */
1060
+ export const DEFAULT_AUDIO_BINDINGS = Object.freeze([
1061
+ Object.freeze({ logicEventId: 'projectile', audioCode: 'shoot' }),
1062
+ Object.freeze({ logicEventId: 'coin_drop_or_transaction', audioCode: 'coin' }),
1063
+ Object.freeze({ logicEventId: 'drop', audioCode: 'drop' }),
1064
+ Object.freeze({ logicEventId: 'item-pickup', audioCode: 'item-pickup' }),
1065
+ Object.freeze({ logicEventId: 'craft', audioCode: 'craft' }),
1066
+ Object.freeze({ logicEventId: 'heal', audioCode: 'heal' }),
1067
+ Object.freeze({ logicEventId: 'hit', audioCode: 'hit' }),
1068
+ Object.freeze({ logicEventId: 'portal', audioCode: 'portal' }),
1069
+ Object.freeze({ logicEventId: 'ui-click', audioCode: 'ui-click' }),
1070
+ Object.freeze({ logicEventId: 'footsteps', audioCode: 'footsteps' }),
1071
+ Object.freeze({ logicEventId: 'combat', audioCode: 'combat' }),
1072
+ Object.freeze({ logicEventId: 'boss', audioCode: 'boss' }),
1073
+ Object.freeze({ logicEventId: 'victory', audioCode: 'victory' }),
1074
+ Object.freeze({ logicEventId: 'level-up', audioCode: 'level-up' }),
1075
+ Object.freeze({ logicEventId: 'death', audioCode: 'death' }),
1076
+ Object.freeze({ logicEventId: 'portal-cooldown', audioCode: 'portal-cooldown' }),
1077
+ ]);
1078
+
1079
+ const AUDIO_BANK_CODES = DEFAULT_AUDIO_BANK.map(({ code }) => code);
1080
+
1081
+ for (const { code, bus } of DEFAULT_AUDIO_BANK) {
1082
+ if (!AUDIO_BUSES.includes(bus)) {
1083
+ throw new Error(`DEFAULT_AUDIO_BANK: "${code}" names bus "${bus}". Allowed: ${AUDIO_BUSES.join(', ')}`);
1084
+ }
1085
+ }
1086
+
1087
+ // Fail fast on an audio binding the runtime cannot honour: an unknown event would never fire, and
1088
+ // an unbanked code would leave the client requesting an asset the seed never imported. Both read
1089
+ // as silence at play time, so they must surface at boot instead.
1090
+ for (const { logicEventId, audioCode } of DEFAULT_AUDIO_BINDINGS) {
1091
+ if (!isCanonicalAudioLogicId(logicEventId)) {
1092
+ throw new Error(
1093
+ `DEFAULT_AUDIO_BINDINGS: unknown logicEventId "${logicEventId}". ` +
1094
+ `Allowed (SharedDefaultsCyberia): ${[...AUDIO_LOGIC_ID_VALUES, ...SKILL_LOGIC_ID_VALUES].join(', ')}`,
1095
+ );
1096
+ }
1097
+ if (!AUDIO_BANK_CODES.includes(audioCode)) {
1098
+ throw new Error(
1099
+ `DEFAULT_AUDIO_BINDINGS: "${logicEventId}" binds audioCode "${audioCode}", ` +
1100
+ `absent from DEFAULT_AUDIO_BANK: ${AUDIO_BANK_CODES.join(', ')}`,
1101
+ );
1102
+ }
1103
+ }
1104
+ if (!AUDIO_BANK_CODES.includes(DEFAULT_MAP_MUSIC)) {
1105
+ throw new Error(`Default map music "${DEFAULT_MAP_MUSIC}" is absent from DEFAULT_AUDIO_BANK`);
1106
+ }
1107
+
1108
+ /**
1109
+ * Expands {@link DEFAULT_AUDIO_BINDINGS} into `cyberia-map-audio-conf` event records.
1110
+ *
1111
+ * Routing is derived, not restated: an event's bus comes from the canonical audio registry
1112
+ * (a skill LogicId is a one-shot, so it falls back to sfx), music holds the bed in a loop unless
1113
+ * the binding is a one-shot cue, and only music crossfades.
991
1114
  *
992
- * The canonical default is a random spawn so a fresh or procedural world never
993
- * piles every new player onto a single cell.
1115
+ * @param {{volume?:number,crossfadeMs?:number}} [settings=DEFAULT_AUDIO_SETTINGS]
1116
+ * @returns {Array<{logicEventId:string,audioCode:string,settings:object}>}
1117
+ */
1118
+ export function buildAudioEventBindings(settings = DEFAULT_AUDIO_SETTINGS) {
1119
+ const { volume, crossfadeMs } = { ...DEFAULT_AUDIO_SETTINGS, ...settings };
1120
+ return DEFAULT_AUDIO_BINDINGS.map(({ logicEventId, audioCode }) => {
1121
+ const bus = AUDIO_LOGIC_ID_BUSES[logicEventId] ?? AUDIO_BUS_SFX;
1122
+ const music = AUDIO_BUS_MUSIC === bus;
1123
+ return {
1124
+ logicEventId,
1125
+ audioCode,
1126
+ settings: { bus, volume, loop: music, crossfadeMs: music ? crossfadeMs : 0 },
1127
+ };
1128
+ });
1129
+ }
1130
+
1131
+ // ─────────────────────────────────────────────────────────────────────────────
1132
+ // Player spawn
1133
+ // ─────────────────────────────────────────────────────────────────────────────
1134
+
1135
+ /**
1136
+ * Default player spawn policy, the shape of PlayerSpawnSchema. With `random`
1137
+ * false and `sourceMapCode` naming a loaded map, players spawn at
1138
+ * (sourceCellX, sourceCellY). Otherwise they spawn on a random walkable cell.
994
1139
  */
995
1140
  export const DEFAULT_PLAYER_SPAWN = Object.freeze({
996
1141
  sourceMapCode: 'fallback-map-0',
@@ -1012,6 +1157,58 @@ export const DEFAULT_PLAYER_SPAWN = Object.freeze({
1012
1157
  * forbidden — see `SharedDefaultsCyberia.js` and the
1013
1158
  * `/api/cyberia-client-hints` REST endpoint for presentation overrides.
1014
1159
  */
1160
+ export const PROGRESSION_RULES_DEFAULTS = Object.freeze({
1161
+ maxLevel: 100,
1162
+ xpPerLevel: 100,
1163
+ baseStats: Object.freeze({ effect: 5, resistance: 10, agility: 0, range: 0, intelligence: 0, utility: 0 }),
1164
+ perLevelStats: Object.freeze({ effect: 2, resistance: 5, agility: 1, range: 10, intelligence: 1, utility: 1 }),
1165
+ killXp: 25,
1166
+ questXp: 100,
1167
+ objectiveXp: 10,
1168
+ minAwardIntervalMs: 500,
1169
+ repeatWindowMs: 60000,
1170
+ maxRepeatAwards: 4,
1171
+ maxAwardsPerWindow: 30,
1172
+ defaultBotLevel: 1,
1173
+ });
1174
+
1175
+ export const PROGRESSION_RULE_LIMITS = Object.freeze(Object.fromEntries(Object.entries({
1176
+ maxLevel: [1, ENTITY_LEVEL_MAX], xpPerLevel: [1, 1000000],
1177
+ killXp: [0, 1000000], questXp: [0, 1000000], objectiveXp: [0, 1000000],
1178
+ minAwardIntervalMs: [1, 60000], repeatWindowMs: [1000, 3600000],
1179
+ maxRepeatAwards: [1, 100], maxAwardsPerWindow: [1, 1000], defaultBotLevel: [1, ENTITY_LEVEL_MAX],
1180
+ }).map(([key, bounds]) => [key, Object.freeze(bounds)])));
1181
+
1182
+ export function resolveProgressionRules(input = {}) {
1183
+ if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('Progression rules must be an object.');
1184
+ const defaults = PROGRESSION_RULES_DEFAULTS;
1185
+ const rules = {
1186
+ ...defaults, ...input,
1187
+ baseStats: { ...defaults.baseStats, ...input.baseStats },
1188
+ perLevelStats: { ...defaults.perLevelStats, ...input.perLevelStats },
1189
+ };
1190
+ for (const key of ['baseStats', 'perLevelStats']) {
1191
+ if (input[key] !== undefined && (!input[key] || typeof input[key] !== 'object' || Array.isArray(input[key]))) {
1192
+ throw new TypeError('Base stat curve must be an object: ' + key);
1193
+ }
1194
+ }
1195
+ if (rules.defaultBotLevel > rules.maxLevel) throw new RangeError('Default bot level exceeds maximum level.');
1196
+ for (const [key, [min, max]] of Object.entries(PROGRESSION_RULE_LIMITS)) {
1197
+ if (!Number.isInteger(rules[key]) || rules[key] < min || rules[key] > max) throw new RangeError('Invalid progression rule: ' + key);
1198
+ }
1199
+ for (const block of [rules.baseStats, rules.perLevelStats]) {
1200
+ for (const [key, value] of Object.entries(block)) {
1201
+ if (!STAT_TYPES.includes(key) || !Number.isInteger(value) || value < 0 || value > STAT_MODIFIER_MAX) {
1202
+ throw new RangeError('Invalid base stat curve: ' + key);
1203
+ }
1204
+ }
1205
+ }
1206
+ for (const key of Object.keys(rules)) {
1207
+ if (!(key in defaults)) throw new TypeError('Unknown progression rule: ' + key);
1208
+ }
1209
+ return rules;
1210
+ }
1211
+
1015
1212
  export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
1016
1213
  // ── Tick model ─────────────────────────────────────────────────────
1017
1214
  tickRate: 60,
@@ -1040,7 +1237,7 @@ export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
1040
1237
  // projectiles and every other entity keep entityBaseSpeed. 0 falls back to
1041
1238
  // entityBaseSpeed.
1042
1239
  playerBaseSpeed: 8,
1043
- sumStatsLimit: 500,
1240
+ progressionRules: PROGRESSION_RULES_DEFAULTS,
1044
1241
  maxActiveLayers: 4,
1045
1242
  initialLifeFraction: 1.0,
1046
1243
 
@@ -1060,29 +1257,33 @@ export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
1060
1257
  craftingFeePercent: 0.0,
1061
1258
  },
1062
1259
 
1063
- // ── Regen ──────────────────────────────────────────────────────────
1064
- lifeRegenChance: 300,
1065
- maxChance: 10000,
1260
+ // ── Chances, as fractions of 1 ───────────────────────────────────
1261
+ // A tap has lifeRegenChance to regenerate life; utility raises it. No chance the
1262
+ // stats raise passes maxChance, so nothing becomes a certainty.
1263
+ lifeRegenChance: 0.15,
1264
+ maxChance: 0.95,
1066
1265
 
1067
1266
  // ── Per-entity-type defaults ───────────────────────────────────────
1068
- entityDefaults: ENTITY_TYPE_DEFAULTS.map((e) => ({ ...e })),
1267
+ // References into the CyberiaEntityTypeDefault collection — see the schema.
1268
+ // Empty by default: a conf that names no document resolves against the
1269
+ // canonical ENTITY_TYPE_DEFAULTS above, so a fresh world is complete without
1270
+ // owning a single row.
1271
+ entityDefaults: [],
1069
1272
 
1070
1273
  // ── Status icons (numeric IDs only — visuals live in client defaults) ──
1071
1274
  statusIcons: STATUS_ICONS.map((s) => ({ ...s })),
1072
1275
 
1073
1276
  // ── Skill system ───────────────────────────────────────────────────
1074
- // Canonical skill definitions live in DefaultSkillConfig above.
1075
- // This reference is the single point of consumption for the gRPC
1076
- // fallback defaults any change goes through DefaultSkillConfig.
1077
- skillConfig: DefaultSkillConfig,
1078
-
1277
+ // No skillConfig: the skills an instance runs are derived from its own content, never
1278
+ // stored on the conf. See DefaultSkillConfig above for the definitions and
1279
+ // cyberia-instance-items.js for the membership rule.
1079
1280
  skillRules: {
1080
- projectileSpawnChance: 0.5,
1281
+ projectileSpawnChance: 0.75,
1081
1282
  projectileLifetimeMs: 2000,
1082
1283
  projectileWidth: 1,
1083
1284
  projectileHeight: 1,
1084
1285
  projectileSpeedMultiplier: 3,
1085
- doppelgangerSpawnChance: 0.5,
1286
+ doppelgangerSpawnChance: 0.6,
1086
1287
  doppelgangerLifetimeMs: 5000,
1087
1288
  doppelgangerSpawnRadius: 3,
1088
1289
  doppelgangerInitialLifeFraction: 1.0,
@@ -1104,7 +1305,7 @@ const _cloneDefault = (v) => JSON.parse(JSON.stringify(v));
1104
1305
  * Recursively backfill one value against its canonical default:
1105
1306
  * - null / undefined → deep clone of the default
1106
1307
  * - empty array → deep clone of the default (config lists must never
1107
- * export empty — e.g. skillConfig, entityDefaults)
1308
+ * export empty — e.g. entityDefaults, statusIcons)
1108
1309
  * - non-empty array → kept verbatim (author content)
1109
1310
  * - plain object → keep author-set keys, recurse to fill missing default keys
1110
1311
  * - scalar → kept verbatim when present (0, '', false count as present)
@@ -1124,26 +1325,12 @@ function _deepFillDefaults(value, defaultValue) {
1124
1325
  return value;
1125
1326
  }
1126
1327
 
1127
- /**
1128
- * Normalise a skillConfig array to the shape CyberiaInstanceConfSchema stores
1129
- * (`SkillConfigEntrySchema` = { triggerItemId, logicEventIds }). The canonical
1130
- * DefaultSkillConfig carries extra `skills` metadata the schema drops on write;
1131
- * stripping it here keeps export ⇄ DB round-trips churn-free.
1132
- */
1133
- function _normaliseSkillConfig(skillConfig) {
1134
- if (!Array.isArray(skillConfig)) return [];
1135
- return skillConfig.map((entry) => ({
1136
- triggerItemId: entry.triggerItemId,
1137
- logicEventIds: [...(entry.logicEventIds || [])],
1138
- }));
1139
- }
1140
-
1141
1328
  /**
1142
1329
  * Return a copy of a CyberiaInstanceConf document with every field defined by
1143
1330
  * CyberiaInstanceConfSchema present. Missing, null/undefined, or empty-array
1144
1331
  * fields — common when a doc is read with `.lean()` (which skips Mongoose schema
1145
1332
  * defaults), was created before a schema field existed, or was seeded with the
1146
- * schema's empty-array default (e.g. `skillConfig`) — are filled from
1333
+ * schema's empty-array default (e.g. `entityDefaults`) — are filled from
1147
1334
  * CYBERIA_INSTANCE_CONF_DEFAULTS, the same canonical values the fallback world
1148
1335
  * uses. Author-set scalars (including 0, '', false), non-empty arrays, and DB
1149
1336
  * metadata (_id, instanceCode, timestamps) are preserved untouched.
@@ -1157,6 +1344,8 @@ export function fillInstanceConfDefaults(conf = {}) {
1157
1344
  for (const key of Object.keys(CYBERIA_INSTANCE_CONF_DEFAULTS)) {
1158
1345
  out[key] = _deepFillDefaults(source[key], CYBERIA_INSTANCE_CONF_DEFAULTS[key]);
1159
1346
  }
1160
- out.skillConfig = _normaliseSkillConfig(out.skillConfig);
1347
+ // A conf from before skills were derived still carries the field; it is not part of the
1348
+ // schema any more, and leaving it would put a stale second answer back on the wire.
1349
+ delete out.skillConfig;
1161
1350
  return out;
1162
1351
  }