@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
@@ -0,0 +1,466 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module cyberia-load.test
5
+ * @description Controlled WebSocket load against a running `cyberia-server`, so a real
6
+ * `cyberia-client` connected to the same instance can be observed for prediction and
7
+ * reconciliation while the authoritative runtime is busy.
8
+ */
9
+
10
+ import { expect } from 'chai';
11
+ import { setTimeout as sleep } from 'node:timers/promises';
12
+
13
+ // Opt-in, with no default endpoint: every simulated client shares one address,
14
+ // so a fleet this size only fits an instance started for load — one running with
15
+ // CYBERIA_DISABLE_CONNECTION_LIMITS=1, or with its per-IP admission limits raised
16
+ // past CYBERIA_LOAD_CONNECTIONS. Pointed at an untuned instance the run is capped
17
+ // by the server's per-IP admission limit and reports a refusal, so the target is
18
+ // named deliberately rather than defaulted to.
19
+ // `underpost cyberia test` sets this, along with the fleet size and the duration.
20
+ const WS_URL = process.env.CYBERIA_LOAD_WS_URL ?? '';
21
+ const CONNECTIONS = Number(process.env.CYBERIA_LOAD_CONNECTIONS ?? 5);
22
+ const TAP_FREQUENCY = Number(process.env.CYBERIA_LOAD_TAP_FREQUENCY ?? 5);
23
+ const DURATION_MS = Number(process.env.CYBERIA_LOAD_DURATION_MS ?? 3000);
24
+
25
+ const CONNECT_TIMEOUT_MS = 30000;
26
+ // Time to keep reading after the last tap, so the final acks arrive before the
27
+ // run is judged. Two snapshot periods at the slowest sane rate.
28
+ const SETTLE_MS = 1500;
29
+ const CLOSE_TIMEOUT_MS = 10000;
30
+ // Cells per tap. Far enough that the server re-plans a path, short enough that
31
+ // the walk stays inside the AOI the real client is watching.
32
+ const TAP_STEP_CELLS = 4;
33
+ // Fraction of the nominal tap count a client must reach for the run to count as
34
+ // sustained load rather than a stalled connection.
35
+ const TAP_YIELD_FLOOR = 0.8;
36
+ // Taps that may still be in flight when the run ends. The ack in a snapshot is
37
+ // the highest sequence the server queued at the moment that snapshot was built.
38
+ const ACK_LAG_TOLERANCE = 3;
39
+
40
+ const TAP_PERIOD_MS = 1000 / TAP_FREQUENCY;
41
+
42
+ // Joins are spread over a ramp instead of arriving together. The instance has a
43
+ // single fixed spawn cell, so a fleet that lands together stacks on one cell —
44
+ // and because every tap fires a skill, the stack kills itself. A dead player
45
+ // cannot walk out and respawns on the same cell, so the pile never clears.
46
+ // The gap gives each joiner time to walk clear before the next one lands.
47
+ const SPAWN_DRAIN_MS = 600;
48
+ const RAMP_WINDOW_MS = Math.min(CONNECTIONS * SPAWN_DRAIN_MS, Math.max(DURATION_MS / 2, 500), 120000);
49
+ const JOIN_GAP_MS = CONNECTIONS > 1 ? RAMP_WINDOW_MS / (CONNECTIONS - 1) : 0;
50
+
51
+ // A fresh player waits before its first tap: it has just spawned and is still
52
+ // loading. The random part spreads first taps across one tap period so the
53
+ // fleet never taps in lockstep.
54
+ const TAP_WARMUP_MS = Math.min(1000, DURATION_MS / 10);
55
+
56
+ // How near its assigned region a client must be before it stops walking toward
57
+ // it and wanders inside it instead.
58
+ const ARRIVE_RADIUS_CELLS = 6;
59
+ // Keep dispersal targets off the map edge, where paths often fail.
60
+ const DISPERSAL_MARGIN_CELLS = 3;
61
+ // No client may settle within this distance of the spawn cell. Anything that
62
+ // lingers there sits in the crossfire of every joining player.
63
+ const SPAWN_CLEARANCE_CELLS = 14;
64
+
65
+ // Unit vectors for the supported directions, in Direction enum order. NONE is
66
+ // excluded: a tap carries movement intent.
67
+ const DIRECTIONS = [
68
+ [0, -1], // UP
69
+ [1, -1], // UP_RIGHT
70
+ [1, 0], // RIGHT
71
+ [1, 1], // DOWN_RIGHT
72
+ [0, 1], // DOWN
73
+ [-1, 1], // DOWN_LEFT
74
+ [-1, 0], // LEFT
75
+ [-1, -1], // UP_LEFT
76
+ ];
77
+
78
+ const pack = (type, payload) => Buffer.from(JSON.stringify({ type, payload }));
79
+
80
+ const unpack = (data) => {
81
+ try {
82
+ return JSON.parse(typeof data === 'string' ? data : Buffer.from(data).toString('utf8'));
83
+ } catch {
84
+ return null;
85
+ }
86
+ };
87
+
88
+ const clamp = (value, max) => (value < 0 ? 0 : value > max ? max : value);
89
+
90
+ const randomDirection = () => DIRECTIONS[Math.floor(Math.random() * DIRECTIONS.length)];
91
+
92
+ /**
93
+ * Picks the supported direction closest to a vector, the same mapping the
94
+ * server uses. The enum starts at UP, so the angle index shifts two quadrants.
95
+ */
96
+ function directionToward(dx, dy) {
97
+ const index = (((Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) + 2) % 8) + 8) % 8;
98
+ return DIRECTIONS[index];
99
+ }
100
+
101
+ /**
102
+ * Gives every client its own destination on a lattice over the map, so the
103
+ * fleet fans out instead of piling on the spawn cell. Players spawn top-left,
104
+ * so the walk runs right, down, and bottom-right — which is what spreads the
105
+ * taps, and the AOI load with them, across the whole map.
106
+ *
107
+ * A lattice point that lands on the spawn is pushed out onto a quarter-turn fan
108
+ * from right to down. Leaving one client parked on the spawn would keep it in
109
+ * the crossfire of every later joiner, dying and respawning for the whole run.
110
+ */
111
+ function dispersalTarget(index, total, gridW, gridH, spawn) {
112
+ const columns = Math.max(1, Math.ceil(Math.sqrt(total)));
113
+ const rows = Math.max(1, Math.ceil(total / columns));
114
+ const column = index % columns;
115
+ const row = Math.floor(index / columns) % rows;
116
+
117
+ const spread = (slot, slots, extent) => {
118
+ const usable = Math.max(1, extent - 1 - 2 * DISPERSAL_MARGIN_CELLS);
119
+ if (slots <= 1) return DISPERSAL_MARGIN_CELLS + Math.round(usable / 2);
120
+ return DISPERSAL_MARGIN_CELLS + Math.round((usable * slot) / (slots - 1));
121
+ };
122
+
123
+ let x = spread(column, columns, gridW);
124
+ let y = spread(row, rows, gridH);
125
+
126
+ if (Math.hypot(x - spawn.x, y - spawn.y) < SPAWN_CLEARANCE_CELLS) {
127
+ const angle = (index / Math.max(1, total)) * (Math.PI / 2); // right → down-right → down
128
+ x = spawn.x + Math.cos(angle) * SPAWN_CLEARANCE_CELLS;
129
+ y = spawn.y + Math.sin(angle) * SPAWN_CLEARANCE_CELLS;
130
+ }
131
+
132
+ return {
133
+ x: clamp(Math.round(x), gridW - 1),
134
+ y: clamp(Math.round(y), gridH - 1),
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Probes the endpoint with a plain GET before the run. A WebSocket error event
140
+ * carries no status code, so this is the only way to tell a server that refused
141
+ * the connection from a server that is not there.
142
+ *
143
+ * A healthy /ws answers 400 to a non-upgrade request; the admission guard
144
+ * answers 503.
145
+ */
146
+ async function preflight(url) {
147
+ const httpURL = url.replace(/^ws/, 'http');
148
+ try {
149
+ const res = await fetch(httpURL, { method: 'GET' });
150
+ if (res.status === 503) return { state: 'refused', detail: (await res.text()).trim() };
151
+ return { state: 'reachable', detail: `HTTP ${res.status}` };
152
+ } catch (err) {
153
+ return { state: 'unreachable', detail: err?.cause?.code ?? err?.message ?? 'connect failed' };
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Opens one simulated client. `index` of `total` picks its region of the map.
159
+ *
160
+ * `ready` settles once the client has joined, or once the connection failed.
161
+ * `tapping` settles on its first tap. `closed` settles when the socket is fully
162
+ * closed. None of them ever hangs, so the caller can await the whole fleet.
163
+ */
164
+ function startSimulatedClient(url, index, total) {
165
+ const stats = {
166
+ joined: false,
167
+ tapping: false,
168
+ taps: 0,
169
+ snapshots: 0,
170
+ sequence: 0, // highest sequence sent
171
+ lastAck: 0, // highest sequence the server reported back
172
+ serverTick: 0, // tick of the newest snapshot
173
+ tickRate: 60,
174
+ tickAt: 0, // wall clock when that snapshot arrived
175
+ gridW: 0,
176
+ gridH: 0,
177
+ posX: 0,
178
+ posY: 0,
179
+ selfSamples: 0, // snapshots carrying a self block
180
+ deadSamples: 0, // ... of which the player was a ghost awaiting respawn
181
+ closeCode: null,
182
+ failure: null,
183
+ handshakeFailed: false,
184
+ };
185
+
186
+ let tapTimer = null;
187
+ let warmupTimer = null;
188
+ let spawn = null; // learned from the first snapshot
189
+ let target = null; // this client's region of the map
190
+ let settleReady;
191
+ let settleTapping;
192
+ let settleClosed;
193
+ const ready = new Promise((resolve) => (settleReady = resolve));
194
+ const tapping = new Promise((resolve) => (settleTapping = resolve));
195
+ const closed = new Promise((resolve) => (settleClosed = resolve));
196
+ const settleAll = () => {
197
+ settleReady();
198
+ settleTapping();
199
+ };
200
+ const connectTimer = setTimeout(() => {
201
+ stats.failure ??= 'init_data timeout';
202
+ settleAll();
203
+ }, CONNECT_TIMEOUT_MS);
204
+
205
+ const label = `client ${index + 1}/${total}`;
206
+ const ws = new WebSocket(url);
207
+ ws.binaryType = 'arraybuffer';
208
+ console.log(`[cyberia-load] ${label} dialing`);
209
+
210
+ const send = (type, payload) => {
211
+ if (ws.readyState === WebSocket.OPEN) ws.send(pack(type, payload));
212
+ };
213
+
214
+ // Mirrors session_server_tick_estimate: extrapolate from the newest snapshot
215
+ // so the tick stamped on a tap is the client's best guess at the server tick.
216
+ const clientTick = () => {
217
+ if (stats.serverTick === 0) return 0;
218
+ const elapsedMs = Date.now() - stats.tickAt;
219
+ return stats.serverTick + Math.max(0, Math.floor((elapsedMs * stats.tickRate) / 1000));
220
+ };
221
+
222
+ // Walk toward this client's region until it arrives, then wander inside it.
223
+ // Heading for a spread-out region is what disperses the fleet; wandering once
224
+ // there keeps its own patch of the map busy for the rest of the run.
225
+ //
226
+ // Near the spawn it never wanders. Death returns a player to the spawn cell,
227
+ // so this is also what makes it walk back out after every respawn.
228
+ const tapDirection = () => {
229
+ if (!target) return randomDirection();
230
+ const dx = target.x - stats.posX;
231
+ const dy = target.y - stats.posY;
232
+ const onSpawn = spawn && Math.hypot(stats.posX - spawn.x, stats.posY - spawn.y) < SPAWN_CLEARANCE_CELLS;
233
+ if (!onSpawn && Math.hypot(dx, dy) <= ARRIVE_RADIUS_CELLS) return randomDirection();
234
+ return directionToward(dx, dy);
235
+ };
236
+
237
+ const tap = () => {
238
+ const [dx, dy] = tapDirection();
239
+ send('player_action', {
240
+ x: clamp(Math.round(stats.posX) + dx * TAP_STEP_CELLS, stats.gridW - 1),
241
+ y: clamp(Math.round(stats.posY) + dy * TAP_STEP_CELLS, stats.gridH - 1),
242
+ tick: clientTick(),
243
+ seq: ++stats.sequence,
244
+ });
245
+ stats.taps++;
246
+ stats.tapping = true;
247
+ settleTapping();
248
+ };
249
+
250
+ const stopTapping = () => {
251
+ if (warmupTimer) clearTimeout(warmupTimer);
252
+ warmupTimer = null;
253
+ if (tapTimer) clearInterval(tapTimer);
254
+ tapTimer = null;
255
+ stats.tapping = false;
256
+ };
257
+
258
+ const onInitData = (payload) => {
259
+ console.log(`[cyberia-load] ${label} joined`);
260
+ stats.gridW = payload.gridW;
261
+ stats.gridH = payload.gridH;
262
+ if (payload.tickRate > 0) stats.tickRate = payload.tickRate;
263
+ // Every join spawns frozen under the server's "loading" protection; taps are
264
+ // dropped by phaseInput until the matching freeze_end releases it.
265
+ send('freeze_end', { reason: 'loading' });
266
+ stats.joined = true;
267
+ clearTimeout(connectTimer);
268
+ settleReady();
269
+
270
+ // Hold before the first tap, then start on a random phase of the tap period.
271
+ warmupTimer = setTimeout(
272
+ () => {
273
+ warmupTimer = null;
274
+ tap();
275
+ tapTimer = setInterval(tap, TAP_PERIOD_MS);
276
+ },
277
+ TAP_WARMUP_MS + Math.random() * TAP_PERIOD_MS,
278
+ );
279
+ };
280
+
281
+ const onSnapshot = (payload) => {
282
+ stats.snapshots++;
283
+ if (payload.tick !== undefined) {
284
+ stats.serverTick = payload.tick;
285
+ stats.tickAt = Date.now();
286
+ }
287
+ if (payload.ack !== undefined && payload.ack > stats.lastAck) stats.lastAck = payload.ack;
288
+ if (payload.self) {
289
+ stats.posX = payload.self.posX;
290
+ stats.posY = payload.self.posY;
291
+ // The spawn cell is only knowable from the server. Learn it once, then
292
+ // pick the region this client walks to.
293
+ if (!spawn) {
294
+ spawn = { x: stats.posX, y: stats.posY };
295
+ target = dispersalTarget(index, total, stats.gridW, stats.gridH, spawn);
296
+ }
297
+ if (payload.self.respawnIn > 0) stats.deadSamples++;
298
+ stats.selfSamples++;
299
+ }
300
+ };
301
+
302
+ ws.addEventListener('open', () => send('handshake', { name: 'cyberia-load', version: '1.0.0' }));
303
+
304
+ ws.addEventListener('message', (event) => {
305
+ const msg = unpack(event.data);
306
+ if (!msg) return;
307
+ if (msg.type === 'init_data') onInitData(msg.payload);
308
+ else if (msg.type === 'snapshot') onSnapshot(msg.payload);
309
+ });
310
+
311
+ ws.addEventListener('error', () => {
312
+ // The event carries no status code. Whether this was an admission refusal
313
+ // or an unreachable server is settled by the preflight probe.
314
+ if (!stats.joined && stats.snapshots === 0) {
315
+ stats.handshakeFailed = true;
316
+ console.log(`[cyberia-load] ${label} REFUSED by the server`);
317
+ }
318
+ stats.failure ??= 'socket error';
319
+ });
320
+
321
+ ws.addEventListener('close', (event) => {
322
+ stopTapping();
323
+ stats.closeCode = event.code;
324
+ if (event.code !== 1000) stats.failure ??= `closed with code ${event.code}`;
325
+ clearTimeout(connectTimer);
326
+ settleAll();
327
+ settleClosed();
328
+ });
329
+
330
+ // Ask for a normal closure and resolve once the socket is really closed.
331
+ const disconnect = () => {
332
+ stopTapping();
333
+ clearTimeout(connectTimer);
334
+ settleAll();
335
+ if (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN) ws.close(1000);
336
+ else settleClosed();
337
+ return closed;
338
+ };
339
+
340
+ const isClosed = () => ws.readyState === WebSocket.CLOSED;
341
+
342
+ return { stats, ready, tapping, closed, disconnect, stopTapping, isClosed };
343
+ }
344
+
345
+ const sum = (clients, field) => clients.reduce((total, client) => total + client.stats[field], 0);
346
+
347
+ const LOAD_TIMEOUT_MS =
348
+ RAMP_WINDOW_MS +
349
+ TAP_WARMUP_MS +
350
+ TAP_PERIOD_MS +
351
+ DURATION_MS +
352
+ CONNECT_TIMEOUT_MS +
353
+ SETTLE_MS +
354
+ CLOSE_TIMEOUT_MS +
355
+ 60000;
356
+
357
+ // Without a named cyberia-server there is no subject to load, so the suite is
358
+ // skipped at collection rather than failing on connect.
359
+ describe.skipIf(!WS_URL)(
360
+ 'Cyberia load — simulated tap clients against cyberia-server',
361
+ { timeout: LOAD_TIMEOUT_MS },
362
+ () => {
363
+ const clients = [];
364
+
365
+ beforeAll(() => {
366
+ if (typeof WebSocket === 'undefined') throw new Error('global WebSocket requires Node >= 22');
367
+ });
368
+
369
+ // Nothing this test opened may outlive it, on any exit path.
370
+ afterAll(async () => {
371
+ await Promise.all(clients.map((client) => Promise.race([client.disconnect(), sleep(CLOSE_TIMEOUT_MS)])));
372
+ });
373
+
374
+ it(`sustains ${CONNECTIONS} clients tapping at ${TAP_FREQUENCY} Hz for ${DURATION_MS} ms`, async () => {
375
+ // Ramp the joins. Arriving together puts the whole fleet on the spawn cell
376
+ // at once, and since every tap fires a skill, the pile kills itself before
377
+ // the run reaches steady state.
378
+ for (let i = 0; i < CONNECTIONS; i++) {
379
+ clients.push(startSimulatedClient(WS_URL, i, CONNECTIONS));
380
+ if (JOIN_GAP_MS > 0 && i < CONNECTIONS - 1) await sleep(JOIN_GAP_MS);
381
+ // Stop as soon as the server refuses one. The concurrent per-address cap
382
+ // cannot clear while the run holds its connections, so every later join
383
+ // would be refused too — dialing them out just delays the diagnosis.
384
+ if (clients.some((client) => client.stats.handshakeFailed)) break;
385
+ }
386
+ await Promise.all(clients.map((client) => client.ready));
387
+
388
+ const joinedCount = clients.filter((client) => client.stats.joined).length;
389
+ const refusedCount = clients.filter((client) => client.stats.handshakeFailed).length;
390
+ if (refusedCount > 0) {
391
+ // A partial success is decisive on its own: the server accepted some and
392
+ // turned the rest away, which only an admission limit does. Probing again
393
+ // would be misleading, because the connect-rate bucket refills within a
394
+ // second and then answers as if nothing was ever refused.
395
+ let cause;
396
+ if (joinedCount > 0) {
397
+ cause =
398
+ `the server admitted ${joinedCount} and refused the next one, so an admission limit is ` +
399
+ `capping the run — ${joinedCount} is almost certainly the effective ` +
400
+ 'CYBERIA_MAX_CONNECTIONS_PER_IP, because every simulated client shares one address. ' +
401
+ 'Start cyberia-server with CYBERIA_DISABLE_CONNECTION_LIMITS=1 for a load run (note: ' +
402
+ '0 leaves the limits ON), or raise CYBERIA_MAX_CONNECTIONS_PER_IP, ' +
403
+ 'CYBERIA_CONNECT_BURST_PER_IP and CYBERIA_CONNECT_RATE_PER_IP';
404
+ } else {
405
+ const probe = await preflight(WS_URL);
406
+ cause =
407
+ probe.state === 'unreachable'
408
+ ? `the endpoint is unreachable (${probe.detail}) — is cyberia-server running?`
409
+ : probe.state === 'refused'
410
+ ? `the endpoint refuses connections (503 "${probe.detail}"). Start cyberia-server ` +
411
+ 'with CYBERIA_DISABLE_CONNECTION_LIMITS=1 for a load run (0 leaves them ON)'
412
+ : `the endpoint answers (${probe.detail}) but every upgrade failed`;
413
+ }
414
+ expect.fail(`only ${joinedCount} of ${CONNECTIONS} clients joined: ${cause}`);
415
+ }
416
+
417
+ const joinFailed = clients.filter((client) => !client.stats.joined);
418
+ expect(joinFailed.length, `clients that never joined: ${joinFailed[0]?.stats.failure ?? ''}`).to.equal(0);
419
+
420
+ // Every client is through its warm-up and tapping. Only now does the run
421
+ // window open, so durationMs measures steady-state load and the expected
422
+ // tap count is not diluted by the ramp.
423
+ await Promise.all(clients.map((client) => client.tapping));
424
+ const tapsAtSteadyState = sum(clients, 'taps');
425
+
426
+ await sleep(DURATION_MS);
427
+
428
+ // Stop the load, then keep reading so the final acks land before judging.
429
+ for (const client of clients) client.stopTapping();
430
+ const droppedMidRun = clients.filter((client) => client.stats.failure);
431
+ await sleep(SETTLE_MS);
432
+
433
+ const nominalTaps = CONNECTIONS * TAP_FREQUENCY * (DURATION_MS / 1000);
434
+ const steadyStateTaps = sum(clients, 'taps') - tapsAtSteadyState;
435
+ expect(steadyStateTaps, 'player_action commands sent during the run window').to.be.at.least(
436
+ nominalTaps * TAP_YIELD_FLOOR,
437
+ );
438
+
439
+ const starved = clients.filter((client) => client.stats.snapshots === 0);
440
+ expect(starved.length, 'clients that received no AOI snapshot').to.equal(0);
441
+
442
+ // The ack is the highest input sequence the server took for this player —
443
+ // the same header field the real cyberia-client reconciles against. It must
444
+ // have caught up with what each client sent, not merely be non-zero.
445
+ const behind = clients.filter((client) => client.stats.lastAck < client.stats.sequence - ACK_LAG_TOLERANCE);
446
+ expect(
447
+ behind.length,
448
+ `clients whose input the server did not process: e.g. acked ${behind[0]?.stats.lastAck} of ` +
449
+ `${behind[0]?.stats.sequence} sent`,
450
+ ).to.equal(0);
451
+
452
+ expect(droppedMidRun.length, `clients that failed mid-run: ${droppedMidRun[0]?.stats.failure ?? ''}`).to.equal(0);
453
+
454
+ // Every client closes normally, and the run does not finish until they have.
455
+ await Promise.all(clients.map((client) => Promise.race([client.disconnect(), sleep(CLOSE_TIMEOUT_MS)])));
456
+
457
+ const stillOpen = clients.filter((client) => !client.isClosed());
458
+ expect(stillOpen.length, 'connections still open after the run').to.equal(0);
459
+
460
+ const unclean = clients.filter((client) => client.stats.closeCode !== 1000);
461
+ expect(unclean.length, `clients that did not close cleanly: first code ${unclean[0]?.stats.closeCode}`).to.equal(
462
+ 0,
463
+ );
464
+ });
465
+ },
466
+ );