@underpostnet/cyberia 3.2.90 → 3.3.73

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 (490) hide show
  1. package/.dockerignore +1 -0
  2. package/.env.example +16 -0
  3. package/.github/workflows/coverall.cyberia.ci.yml +107 -0
  4. package/.github/workflows/cyberia-client.cd.yml +6 -18
  5. package/.github/workflows/cyberia-server.cd.yml +6 -18
  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 +6 -78
  13. package/.github/workflows/engine-cyberia.ci.yml +4 -5
  14. package/.github/workflows/ghpkg.ci.yml +42 -21
  15. package/.github/workflows/hardhat.ci.yml +12 -11
  16. package/.github/workflows/npmpkg.ci.yml +17 -15
  17. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  18. package/.github/workflows/pwa-microservices-template-test.ci.yml +2 -2
  19. package/.github/workflows/release.cd.yml +2 -9
  20. package/.prettierignore +1 -0
  21. package/AGENTS.md +47 -0
  22. package/CHANGELOG.md +581 -2417
  23. package/CLI-HELP.md +416 -127
  24. package/Dockerfile +25 -9
  25. package/Dockerfile.dev +25 -9
  26. package/Dockerfile.test +4 -4
  27. package/README.md +21 -1
  28. package/bin/build.js +70 -43
  29. package/bin/build.template.js +8 -3
  30. package/bin/cyberia.js +372 -135
  31. package/bin/deploy.js +18 -104
  32. package/bin/index.js +372 -135
  33. package/compose.env +16 -0
  34. package/conf.js +93 -4
  35. package/deploy/cyberia-client/deploy.sh +47 -0
  36. package/deploy/cyberia-client/package.sh +18 -0
  37. package/deploy/cyberia-client/state.sh +14 -0
  38. package/deploy/cyberia-server/deploy.sh +47 -0
  39. package/deploy/cyberia-server/package.sh +18 -0
  40. package/deploy/cyberia-server/state.sh +14 -0
  41. package/deploy/dd-cyberia/deploy.sh +22 -0
  42. package/deploy/dd-cyberia/init.sh +55 -0
  43. package/deploy/dd-cyberia/package.sh +17 -0
  44. package/deploy/dd-cyberia/state.sh +14 -0
  45. package/deploy/dd-cyberia/sync-deploy.sh +116 -0
  46. package/deploy/lib/github-actions-logging.sh +594 -0
  47. package/deploy/lib/host.sh +185 -0
  48. package/deploy/lib/state.sh +62 -0
  49. package/deploy/pwa-microservices-template/deploy.sh +45 -0
  50. package/deploy/release/deploy.sh +32 -0
  51. package/deployment.yaml +7 -2
  52. package/docker-compose.yml +31 -37
  53. package/examples/static-page/README.md +19 -19
  54. package/examples/static-page/ssr-components/CustomPage.js +2 -2
  55. package/hardhat/hardhat.config.js +1 -6
  56. package/hardhat/package-lock.json +104 -43
  57. package/hardhat/package.json +3 -3
  58. package/manifests/alertmanager/deployment.yaml +81 -0
  59. package/manifests/blackbox-exporter/deployment.yaml +75 -0
  60. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +36 -7
  61. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +77 -0
  62. package/manifests/deployment/dd-cyberia-development/deployment.yaml +7 -2
  63. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  64. package/manifests/grafana/deployment.yaml +26 -7
  65. package/manifests/grafana/kustomization.yaml +1 -0
  66. package/manifests/grafana/service.yaml +6 -1
  67. package/manifests/mariadb/pv.yaml +1 -0
  68. package/manifests/mongodb/statefulset.yaml +9 -2
  69. package/manifests/mysql/pv-pvc.yaml +14 -1
  70. package/manifests/postgresql/pv.yaml +12 -1
  71. package/manifests/postgresql/pvc.yaml +1 -0
  72. package/manifests/prometheus/deployment.yaml +18 -22
  73. package/package.json +83 -10
  74. package/scripts/audit-selinux.sh +64 -0
  75. package/scripts/event-monitor.sh +56 -0
  76. package/scripts/gpu-diag.sh +0 -0
  77. package/scripts/ip-info.sh +0 -0
  78. package/scripts/k3s-node-setup.sh +30 -14
  79. package/scripts/kubeadm-node-setup.sh +40 -31
  80. package/scripts/lxd-vm-setup.sh +0 -0
  81. package/scripts/maas-nat-firewalld.sh +0 -0
  82. package/scripts/maas-setup.sh +8 -8
  83. package/scripts/nat-iptables.sh +2 -0
  84. package/scripts/rhel-grpc-setup.sh +0 -0
  85. package/scripts/rocky-kickstart.sh +25 -9
  86. package/scripts/selinux-normalize.sh +907 -0
  87. package/scripts/shutdown-machine.sh +843 -0
  88. package/scripts/test-monitor.sh +3 -3
  89. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +1 -1
  90. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +2 -2
  91. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +2 -2
  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 +1 -1
  99. package/src/api/cyberia-action/cyberia-action.service.js +2 -2
  100. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -1
  101. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +1 -1
  102. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +2 -2
  103. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +1 -1
  104. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
  105. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +2 -2
  106. package/src/api/cyberia-entity/cyberia-entity.controller.js +1 -1
  107. package/src/api/cyberia-entity/cyberia-entity.router.js +1 -1
  108. package/src/api/cyberia-entity/cyberia-entity.service.js +2 -2
  109. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +1 -1
  110. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +1 -1
  111. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +2 -2
  112. package/src/api/cyberia-instance/cyberia-fallback-capture.js +466 -0
  113. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  114. package/src/api/cyberia-instance/cyberia-fallback-world.js +16 -1
  115. package/src/api/cyberia-instance/cyberia-instance-map.service.js +1 -1
  116. package/src/api/cyberia-instance/cyberia-instance.controller.js +4 -1
  117. package/src/api/cyberia-instance/cyberia-instance.router.js +15 -2
  118. package/src/api/cyberia-instance/cyberia-instance.service.js +45 -2
  119. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +1 -1
  120. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  121. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +1 -1
  122. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +2 -2
  123. package/src/api/cyberia-map/cyberia-map.controller.js +1 -1
  124. package/src/api/cyberia-map/cyberia-map.router.js +1 -1
  125. package/src/api/cyberia-map/cyberia-map.service.js +2 -2
  126. package/src/api/cyberia-quest/cyberia-quest.controller.js +1 -1
  127. package/src/api/cyberia-quest/cyberia-quest.router.js +1 -1
  128. package/src/api/cyberia-quest/cyberia-quest.service.js +2 -2
  129. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +1 -1
  130. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +1 -1
  131. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +2 -2
  132. package/src/api/cyberia-saga/cyberia-saga.controller.js +1 -1
  133. package/src/api/cyberia-saga/cyberia-saga.router.js +1 -1
  134. package/src/api/cyberia-saga/cyberia-saga.service.js +2 -2
  135. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +16 -29
  136. package/src/api/cyberia-skill/cyberia-skill.controller.js +1 -1
  137. package/src/api/cyberia-skill/cyberia-skill.router.js +1 -1
  138. package/src/api/cyberia-skill/cyberia-skill.service.js +2 -2
  139. package/src/api/default/default.controller.js +1 -1
  140. package/src/api/default/default.router.js +1 -1
  141. package/src/api/default/default.service.js +2 -2
  142. package/src/api/document/document.controller.js +1 -1
  143. package/src/api/document/document.router.js +1 -1
  144. package/src/api/document/document.service.js +2 -2
  145. package/src/api/file/file.controller.js +1 -1
  146. package/src/api/file/file.router.js +1 -1
  147. package/src/api/file/file.service.js +2 -2
  148. package/src/api/instance/instance.controller.js +1 -1
  149. package/src/api/instance/instance.router.js +1 -1
  150. package/src/api/instance/instance.service.js +2 -2
  151. package/src/api/ipfs/ipfs.controller.js +1 -1
  152. package/src/api/ipfs/ipfs.router.js +2 -2
  153. package/src/api/ipfs/ipfs.service.js +2 -2
  154. package/src/api/object-layer/object-layer.controller.js +1 -1
  155. package/src/api/object-layer/object-layer.model.js +204 -3
  156. package/src/api/object-layer/object-layer.router.js +2 -2
  157. package/src/api/object-layer/object-layer.service.js +4 -17
  158. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +1 -1
  159. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +1 -1
  160. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +2 -2
  161. package/src/api/test/test.controller.js +1 -1
  162. package/src/api/test/test.service.js +1 -1
  163. package/src/api/user/guest.service.js +3 -3
  164. package/src/api/user/user.controller.js +1 -1
  165. package/src/api/user/user.router.js +2 -2
  166. package/src/api/user/user.service.js +4 -4
  167. package/src/api.js +5 -9
  168. package/src/cli/app.js +391 -0
  169. package/src/cli/baremetal.js +30 -36
  170. package/src/cli/client.js +306 -0
  171. package/src/cli/cloud-init.js +3 -3
  172. package/src/cli/cluster.js +242 -135
  173. package/src/cli/db.js +126 -67
  174. package/src/cli/deploy.js +158 -60
  175. package/src/cli/docker-compose.js +5 -5
  176. package/src/cli/domains.js +184 -0
  177. package/src/cli/dotenv-store.js +143 -0
  178. package/src/cli/event.js +2281 -0
  179. package/src/cli/fs.js +3 -3
  180. package/src/cli/host.js +672 -0
  181. package/src/cli/image.js +4 -7
  182. package/src/cli/index.js +426 -134
  183. package/src/cli/ipfs.js +34 -30
  184. package/src/cli/kickstart.js +1 -1
  185. package/src/cli/kubectl.js +218 -41
  186. package/src/cli/lxd.js +3 -3
  187. package/src/cli/monitor.js +1359 -26
  188. package/src/cli/package.js +90 -0
  189. package/src/cli/release.js +73 -34
  190. package/src/cli/repository.js +564 -367
  191. package/src/cli/run.js +362 -654
  192. package/src/cli/secrets.js +2158 -964
  193. package/src/cli/ssh.js +651 -331
  194. package/src/cli/state.js +394 -0
  195. package/src/cli/static.js +2 -2
  196. package/src/cli/system.js +26 -13
  197. package/src/cli/test.js +199 -125
  198. package/src/cli/vultr.js +623 -0
  199. package/src/cli/wireguard.js +2930 -0
  200. package/src/client/components/core/CalendarCore.js +53 -96
  201. package/src/client/components/core/Docs.js +258 -211
  202. package/src/client/components/core/FileExplorer.js +0 -21
  203. package/src/client/components/core/PanelForm.js +4 -4
  204. package/src/client/components/core/Repository.js +56 -0
  205. package/src/client/components/core/Translate.js +7 -0
  206. package/src/client/components/core/{FullScreen.js → ViewModeController.js} +42 -39
  207. package/src/client/components/core/Wallet.js +0 -11
  208. package/src/client/components/core/Worker.js +7 -1
  209. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +2 -2
  210. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  211. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +35 -1
  212. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  213. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +2 -2
  214. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  215. package/src/client/components/default/AppShellDefault.js +1 -1
  216. package/src/client/components/default/SettingsDefault.js +2 -2
  217. package/src/client/components/itemledger/SettingsItemledger.js +2 -2
  218. package/src/client/components/underpost/AppShellUnderpost.js +3 -2
  219. package/src/client/components/underpost/SettingsUnderpost.js +2 -2
  220. package/src/client/public/cyberia-docs/ARCHITECTURE.md +17 -3
  221. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +32 -5
  222. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  223. package/src/client/public/cyberia-docs/CYBERIA.md +21 -1
  224. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +23 -6
  225. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  226. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  227. package/src/client/public/default/android-chrome-144x144.png +0 -0
  228. package/src/client/public/default/android-chrome-192x192.png +0 -0
  229. package/src/client/public/default/android-chrome-256x256.png +0 -0
  230. package/src/client/public/default/android-chrome-36x36.png +0 -0
  231. package/src/client/public/default/android-chrome-384x384.png +0 -0
  232. package/src/client/public/default/android-chrome-48x48.png +0 -0
  233. package/src/client/public/default/android-chrome-512x512.png +0 -0
  234. package/src/client/public/default/android-chrome-72x72.png +0 -0
  235. package/src/client/public/default/android-chrome-96x96.png +0 -0
  236. package/src/client/public/default/apple-touch-icon-1024x1024.png +0 -0
  237. package/src/client/public/default/apple-touch-icon-114x114.png +0 -0
  238. package/src/client/public/default/apple-touch-icon-120x120.png +0 -0
  239. package/src/client/public/default/apple-touch-icon-144x144.png +0 -0
  240. package/src/client/public/default/apple-touch-icon-152x152.png +0 -0
  241. package/src/client/public/default/apple-touch-icon-167x167.png +0 -0
  242. package/src/client/public/default/apple-touch-icon-180x180.png +0 -0
  243. package/src/client/public/default/apple-touch-icon-57x57.png +0 -0
  244. package/src/client/public/default/apple-touch-icon-60x60.png +0 -0
  245. package/src/client/public/default/apple-touch-icon-72x72.png +0 -0
  246. package/src/client/public/default/apple-touch-icon-76x76.png +0 -0
  247. package/src/client/public/default/apple-touch-icon-precomposed.png +0 -0
  248. package/src/client/public/default/apple-touch-icon.png +0 -0
  249. package/src/client/public/default/apple-touch-startup-image-1125x2436.png +0 -0
  250. package/src/client/public/default/apple-touch-startup-image-1136x640.png +0 -0
  251. package/src/client/public/default/apple-touch-startup-image-1170x2532.png +0 -0
  252. package/src/client/public/default/apple-touch-startup-image-1179x2556.png +0 -0
  253. package/src/client/public/default/apple-touch-startup-image-1242x2208.png +0 -0
  254. package/src/client/public/default/apple-touch-startup-image-1242x2688.png +0 -0
  255. package/src/client/public/default/apple-touch-startup-image-1284x2778.png +0 -0
  256. package/src/client/public/default/apple-touch-startup-image-1290x2796.png +0 -0
  257. package/src/client/public/default/apple-touch-startup-image-1334x750.png +0 -0
  258. package/src/client/public/default/apple-touch-startup-image-1488x2266.png +0 -0
  259. package/src/client/public/default/apple-touch-startup-image-1536x2048.png +0 -0
  260. package/src/client/public/default/apple-touch-startup-image-1620x2160.png +0 -0
  261. package/src/client/public/default/apple-touch-startup-image-1640x2160.png +0 -0
  262. package/src/client/public/default/apple-touch-startup-image-1668x2224.png +0 -0
  263. package/src/client/public/default/apple-touch-startup-image-1668x2388.png +0 -0
  264. package/src/client/public/default/apple-touch-startup-image-1792x828.png +0 -0
  265. package/src/client/public/default/apple-touch-startup-image-2048x1536.png +0 -0
  266. package/src/client/public/default/apple-touch-startup-image-2048x2732.png +0 -0
  267. package/src/client/public/default/apple-touch-startup-image-2160x1620.png +0 -0
  268. package/src/client/public/default/apple-touch-startup-image-2160x1640.png +0 -0
  269. package/src/client/public/default/apple-touch-startup-image-2208x1242.png +0 -0
  270. package/src/client/public/default/apple-touch-startup-image-2224x1668.png +0 -0
  271. package/src/client/public/default/apple-touch-startup-image-2266x1488.png +0 -0
  272. package/src/client/public/default/apple-touch-startup-image-2388x1668.png +0 -0
  273. package/src/client/public/default/apple-touch-startup-image-2436x1125.png +0 -0
  274. package/src/client/public/default/apple-touch-startup-image-2532x1170.png +0 -0
  275. package/src/client/public/default/apple-touch-startup-image-2556x1179.png +0 -0
  276. package/src/client/public/default/apple-touch-startup-image-2688x1242.png +0 -0
  277. package/src/client/public/default/apple-touch-startup-image-2732x2048.png +0 -0
  278. package/src/client/public/default/apple-touch-startup-image-2778x1284.png +0 -0
  279. package/src/client/public/default/apple-touch-startup-image-2796x1290.png +0 -0
  280. package/src/client/public/default/apple-touch-startup-image-640x1136.png +0 -0
  281. package/src/client/public/default/apple-touch-startup-image-750x1334.png +0 -0
  282. package/src/client/public/default/apple-touch-startup-image-828x1792.png +0 -0
  283. package/src/client/public/default/assets/background/white.jpg +0 -0
  284. package/src/client/public/default/browserconfig.xml +1 -1
  285. package/src/client/public/default/favicon-16x16.png +0 -0
  286. package/src/client/public/default/favicon-32x32.png +0 -0
  287. package/src/client/public/default/favicon-48x48.png +0 -0
  288. package/src/client/public/default/favicon.ico +0 -0
  289. package/src/client/public/default/manifest.webmanifest +5 -5
  290. package/src/client/public/default/mstile-144x144.png +0 -0
  291. package/src/client/public/default/mstile-150x150.png +0 -0
  292. package/src/client/public/default/mstile-310x150.png +0 -0
  293. package/src/client/public/default/mstile-310x310.png +0 -0
  294. package/src/client/public/default/mstile-70x70.png +0 -0
  295. package/src/client/public/default/yandex-browser-50x50.png +0 -0
  296. package/src/client/public/default/yandex-browser-manifest.json +1 -1
  297. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  298. package/src/client/services/object-layer/object-layer.management.js +4 -4
  299. package/src/client/ssr/RootDocument.js +1 -1
  300. package/src/client/ssr/body/404.js +1 -1
  301. package/src/client/ssr/body/500.js +1 -1
  302. package/src/client/ssr/body/CacheControl.js +1 -1
  303. package/src/client/ssr/body/CyberiaDefaultSplashScreen.js +1 -1
  304. package/src/client/ssr/body/DefaultSplashScreen.js +1 -1
  305. package/src/client/ssr/body/SwaggerDarkMode.js +1 -1
  306. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +1 -1
  307. package/src/client/ssr/head/CryptokoynScripts.js +1 -1
  308. package/src/client/ssr/head/Css.js +1 -1
  309. package/src/client/ssr/head/CyberiaPortalScripts.js +1 -1
  310. package/src/client/ssr/head/DefaultScripts.js +1 -1
  311. package/src/client/ssr/head/ItemledgerScripts.js +1 -1
  312. package/src/client/ssr/head/Microdata.js +1 -1
  313. package/src/client/ssr/head/Production.js +1 -1
  314. package/src/client/ssr/head/Pwa.js +1 -1
  315. package/src/client/ssr/head/PwaItemledger.js +1 -1
  316. package/src/client/ssr/head/Seo.js +1 -1
  317. package/src/client/ssr/head/UnderpostScripts.js +1 -1
  318. package/src/client/ssr/mailer/DefaultRecoverEmail.js +1 -1
  319. package/src/client/ssr/mailer/DefaultVerifyEmail.js +1 -1
  320. package/src/client/ssr/views/Cyberia404.js +7 -5
  321. package/src/client/ssr/views/CyberiaServerMetrics.js +1 -1
  322. package/src/client/ssr/views/Maintenance.js +1 -1
  323. package/src/client/ssr/views/NoNetworkConnection.js +1 -1
  324. package/src/client/ssr/views/Test.js +1 -1
  325. package/src/client/sw/core.sw.js +20 -14
  326. package/src/client-builder/client-build-docs.js +61 -46
  327. package/src/client-builder/client-build-live.js +2 -2
  328. package/src/client-builder/client-build.js +58 -44
  329. package/src/client-builder/client-dev-server.js +3 -3
  330. package/src/client-builder/client-icons.js +2 -2
  331. package/src/client-builder/ssr.js +3 -3
  332. package/src/client.build.js +3 -6
  333. package/src/client.dev.js +3 -6
  334. package/src/db/DataBaseProvider.js +186 -3
  335. package/src/db/mariadb/MariaDB.js +3 -2
  336. package/src/db/mongo/MongoBootstrap.js +273 -101
  337. package/src/db/mongo/MongooseDB.js +11 -8
  338. package/src/{server/valkey.js → db/valkey/Valkey.js} +5 -4
  339. package/src/grpc/cyberia/grpc-server.js +1 -1
  340. package/src/index.js +103 -23
  341. package/src/mailer/EmailRender.js +2 -2
  342. package/src/mailer/MailerInterceptor.js +87 -0
  343. package/src/mailer/MailerProvider.js +50 -6
  344. package/src/projects/cyberia/atlas-sprite-sheet-generator.js +1 -1
  345. package/src/projects/cyberia/besu-genesis-generator.js +3 -3
  346. package/src/projects/cyberia/catalog-cyberia.js +54 -6
  347. package/src/projects/cyberia/gemini-client.js +1 -1
  348. package/src/projects/cyberia/generate-saga.js +15 -24
  349. package/src/projects/cyberia/hot-reload-trigger.js +1 -1
  350. package/src/projects/cyberia/instance-data.js +22 -3
  351. package/src/projects/cyberia/ipfs-client.js +4 -4
  352. package/src/projects/cyberia/map-preview-generator.js +1 -1
  353. package/src/projects/cyberia/object-layer.js +12 -22
  354. package/src/projects/cyberia/semantic-layer-generator.js +1 -1
  355. package/src/projects/underpost/catalog-underpost.js +13 -2
  356. package/src/proxy.js +4 -7
  357. package/src/runtime/cyberia-client/Dockerfile +9 -4
  358. package/src/runtime/cyberia-client/Dockerfile.dev +8 -2
  359. package/src/runtime/cyberia-server/Dockerfile +9 -4
  360. package/src/runtime/cyberia-server/Dockerfile.dev +8 -2
  361. package/src/runtime/engine-cyberia/Dockerfile +25 -9
  362. package/src/runtime/engine-cyberia/Dockerfile.dev +25 -9
  363. package/src/runtime/engine-cyberia/Dockerfile.test +4 -4
  364. package/src/runtime/engine-cyberia/compose.env +16 -0
  365. package/src/runtime/engine-cyberia/docker-compose.yml +31 -37
  366. package/src/runtime/express/Express.js +32 -27
  367. package/src/runtime/lampp/Lampp.js +22 -70
  368. package/src/runtime/nginx/Nginx.js +1 -1
  369. package/src/runtime/wp/Wp.js +16 -48
  370. package/src/server/{catalog.js → build/catalog.js} +15 -5
  371. package/src/server/build/coverage.js +117 -0
  372. package/src/server/build/execution.js +402 -0
  373. package/src/server/build/package.js +465 -0
  374. package/src/server/build/testing.js +528 -0
  375. package/src/server/{dns.js → network/dns.js} +241 -14
  376. package/src/server/network/forward-proxy.js +541 -0
  377. package/src/server/{middlewares.js → network/middlewares.js} +59 -4
  378. package/src/server/network/node-capability.js +98 -0
  379. package/src/server/{peer.js → network/peer.js} +3 -3
  380. package/src/server/{proxy.js → network/proxy.js} +7 -13
  381. package/src/server/network/router.js +356 -0
  382. package/src/server/{tls.js → network/tls.js} +2 -2
  383. package/src/server/network/underpost-compression.js +186 -0
  384. package/src/server/{underpost-gateway.js → network/underpost-gateway.js} +26 -16
  385. package/src/server/{underpost-ingress.js → network/underpost-ingress.js} +19 -3
  386. package/src/server/{cri.js → ops/cri.js} +2 -2
  387. package/src/server/ops/cron.js +949 -0
  388. package/src/server/ops/event-notification.js +284 -0
  389. package/src/server/{logger.js → ops/logger.js} +108 -23
  390. package/src/server/ops/monitoring.js +1724 -0
  391. package/src/server/ops/systemd.js +259 -0
  392. package/src/server/{conf.js → runtime/conf.js} +521 -520
  393. package/src/server/runtime/config-scope.js +215 -0
  394. package/src/server/runtime/environment.js +149 -0
  395. package/src/server/{process.js → runtime/process.js} +153 -13
  396. package/src/server/{runtime-status.js → runtime/runtime-status.js} +155 -16
  397. package/src/server/{runtime.js → runtime/runtime.js} +12 -8
  398. package/src/server/{start.js → runtime/start.js} +128 -39
  399. package/src/server/{auth.js → security/auth.js} +7 -8
  400. package/src/server/security/container-storage.js +382 -0
  401. package/src/server/{crypto.js → security/crypto.js} +1 -1
  402. package/src/server/security/selinux.js +185 -0
  403. package/src/server/{backup.js → storage/backup.js} +17 -8
  404. package/src/server/{data-query.js → storage/data-query.js} +1 -1
  405. package/src/server/{downloader.js → storage/downloader.js} +2 -2
  406. package/src/server/storage/repository.js +68 -0
  407. package/src/server.js +4 -7
  408. package/src/ws/IoInterface.js +1 -1
  409. package/src/ws/IoServer.js +1 -1
  410. package/src/ws/core/core.ws.connection.js +1 -1
  411. package/src/ws/core/core.ws.emit.js +1 -1
  412. package/src/ws/core/core.ws.server.js +1 -1
  413. package/src/ws/default/default.ws.connection.js +1 -1
  414. package/src/ws/default/default.ws.emit.js +1 -1
  415. package/src/ws/default/default.ws.server.js +1 -1
  416. package/test/e2e/event-e2e-public-ingress-down.js +45 -0
  417. package/test/e2e/event-e2e-wireguard-server-down.js +38 -0
  418. package/test/e2e/event-e2e-wireguard-spoke-down.js +40 -0
  419. package/test/{api.test.js → integration/app/api.test.js} +33 -14
  420. package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +30 -0
  421. package/test/{cyberia-instance-conf-defaults.test.js → integration/app/cyberia/cyberia-instance-conf-defaults.test.js} +2 -1
  422. package/test/integration/app/cyberia/cyberia-load.test.js +467 -0
  423. package/test/integration/app/cyberia/fallback-world-capture.test.js +253 -0
  424. package/test/integration/app/cyberia/object-layer-item-id.test.js +327 -0
  425. package/test/{shape-generator.test.js → integration/app/cyberia/shape-generator.test.js} +1 -1
  426. package/test/integration/infra/1-security/config-scope.test.js +305 -0
  427. package/test/integration/infra/1-security/container-storage.test.js +356 -0
  428. package/test/integration/infra/1-security/in-pod-cli-surface.test.js +86 -0
  429. package/test/integration/infra/1-security/secret-onboarding.test.js +930 -0
  430. package/test/integration/infra/1-security/selinux.test.js +69 -0
  431. package/test/{sops-secret-store.test.js → integration/infra/1-security/sops-secret-store.test.js} +415 -61
  432. package/test/integration/infra/1-security/systemd-service.test.js +70 -0
  433. package/test/integration/infra/1-security/underpost-config-secret.test.js +367 -0
  434. package/test/integration/infra/2-network/dns-firewall.test.js +566 -0
  435. package/test/integration/infra/2-network/node-capability.test.js +182 -0
  436. package/test/integration/infra/2-network/wireguard-cli.test.js +1163 -0
  437. package/test/integration/infra/2-network/wireguard-edge.test.js +1731 -0
  438. package/test/{cluster-instances.test.js → integration/infra/3-cluster/cluster-instances.test.js} +11 -15
  439. package/test/{deploy-node-placement.test.js → integration/infra/3-cluster/deploy-node-placement.test.js} +3 -3
  440. package/test/integration/infra/3-cluster/docker-compose-stack.test.js +313 -0
  441. package/test/integration/infra/4-ingress/deploy-routes.test.js +379 -0
  442. package/test/integration/infra/4-ingress/gateway-static-assets.test.js +453 -0
  443. package/test/{instance-traffic-plan.test.js → integration/infra/4-ingress/instance-traffic-plan.test.js} +10 -6
  444. package/test/{underpost-gateway.test.js → integration/infra/4-ingress/underpost-gateway.test.js} +47 -6
  445. package/test/{underpost-ingress.test.js → integration/infra/4-ingress/underpost-ingress.test.js} +56 -4
  446. package/test/integration/infra/5-observability/cron-jobs.test.js +652 -0
  447. package/test/{deploy-monitor.test.js → integration/infra/5-observability/deploy-monitor.test.js} +29 -31
  448. package/test/integration/infra/5-observability/event-notification.test.js +197 -0
  449. package/test/integration/infra/5-observability/event-remediation.test.js +502 -0
  450. package/test/integration/infra/5-observability/event-targets.test.js +1174 -0
  451. package/test/integration/infra/5-observability/monitoring-stack.test.js +902 -0
  452. package/test/support/shell-harness.js +73 -0
  453. package/test/unit/build-template.test.js +97 -0
  454. package/test/unit/build-workflow-offline.test.js +132 -0
  455. package/test/unit/catalog.test.js +71 -0
  456. package/test/unit/client-build-docs.test.js +334 -0
  457. package/test/unit/conf-loading.test.js +303 -0
  458. package/test/unit/conf-resolution.test.js +979 -0
  459. package/test/unit/coverage-artifact.test.js +130 -0
  460. package/test/{crypto.test.js → unit/crypto.test.js} +1 -1
  461. package/test/unit/deploy-env-resolution.test.js +38 -0
  462. package/test/unit/deploy-log-table.test.js +447 -0
  463. package/test/unit/execution-profiles.test.js +219 -0
  464. package/test/unit/fleet-sync-source.test.js +149 -0
  465. package/test/unit/logger-redaction.test.js +112 -0
  466. package/test/unit/package.test.js +403 -0
  467. package/test/unit/prepare-host.test.js +299 -0
  468. package/test/unit/process-environment.test.js +18 -0
  469. package/test/unit/propagation-message.test.js +78 -0
  470. package/test/unit/redeploy-plan.test.js +24 -0
  471. package/test/unit/release-bump.test.js +474 -0
  472. package/test/unit/ssh-output-redaction.test.js +28 -0
  473. package/test/unit/start-options.test.js +46 -0
  474. package/test/unit/test-tiers.test.js +193 -0
  475. package/vitest.config.js +58 -0
  476. package/.nycrc +0 -9
  477. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +0 -48
  478. package/manifests/mongodb/pv-pvc.yaml +0 -59
  479. package/scripts/link-local-underpost-cli.sh +0 -6
  480. package/src/api/user/user.build.js +0 -16
  481. package/src/cli/env.js +0 -177
  482. package/src/client/components/core/Blockchain.js +0 -41
  483. package/src/client/components/core/Blog.js +0 -9
  484. package/src/client/components/core/KeyboardAvoidance.js +0 -145
  485. package/src/client/public/default/assets/background/dark.jpg +0 -0
  486. package/src/client/public/default/assets/background/white0-min.jpg +0 -0
  487. package/src/client/public/default/assets/background/white0.jpg +0 -0
  488. package/src/client/public/doc/favicon.ico +0 -0
  489. package/src/client/public/doc/sitemap +0 -148
  490. package/src/server/cron.js +0 -476
@@ -0,0 +1,467 @@
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 `DefaultMaxConnectionsPerIP` (cyberia-server/game/connection_guard.go) and
18
+ // reports a refusal, so the target is 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 the order of the Direction enum
66
+ // in cyberia-server/game/types.go. NONE is 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. Same mapping the server
94
+ * uses in `updateBotDirection` (cyberia-server/game/ai.go): the enum starts at
95
+ * UP, so the angle index shifts by two quadrants.
96
+ */
97
+ function directionToward(dx, dy) {
98
+ const index = (((Math.round(Math.atan2(dy, dx) / (Math.PI / 4)) + 2) % 8) + 8) % 8;
99
+ return DIRECTIONS[index];
100
+ }
101
+
102
+ /**
103
+ * Gives every client its own destination on a lattice over the map, so the
104
+ * fleet fans out instead of piling on the spawn cell. Players spawn top-left,
105
+ * so the walk runs right, down, and bottom-right — which is what spreads the
106
+ * taps, and the AOI load with them, across the whole map.
107
+ *
108
+ * A lattice point that lands on the spawn is pushed out onto a quarter-turn fan
109
+ * from right to down. Leaving one client parked on the spawn would keep it in
110
+ * the crossfire of every later joiner, dying and respawning for the whole run.
111
+ */
112
+ function dispersalTarget(index, total, gridW, gridH, spawn) {
113
+ const columns = Math.max(1, Math.ceil(Math.sqrt(total)));
114
+ const rows = Math.max(1, Math.ceil(total / columns));
115
+ const column = index % columns;
116
+ const row = Math.floor(index / columns) % rows;
117
+
118
+ const spread = (slot, slots, extent) => {
119
+ const usable = Math.max(1, extent - 1 - 2 * DISPERSAL_MARGIN_CELLS);
120
+ if (slots <= 1) return DISPERSAL_MARGIN_CELLS + Math.round(usable / 2);
121
+ return DISPERSAL_MARGIN_CELLS + Math.round((usable * slot) / (slots - 1));
122
+ };
123
+
124
+ let x = spread(column, columns, gridW);
125
+ let y = spread(row, rows, gridH);
126
+
127
+ if (Math.hypot(x - spawn.x, y - spawn.y) < SPAWN_CLEARANCE_CELLS) {
128
+ const angle = (index / Math.max(1, total)) * (Math.PI / 2); // right → down-right → down
129
+ x = spawn.x + Math.cos(angle) * SPAWN_CLEARANCE_CELLS;
130
+ y = spawn.y + Math.sin(angle) * SPAWN_CLEARANCE_CELLS;
131
+ }
132
+
133
+ return {
134
+ x: clamp(Math.round(x), gridW - 1),
135
+ y: clamp(Math.round(y), gridH - 1),
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Probes the endpoint with a plain GET before the run. A WebSocket error event
141
+ * carries no status code, so this is the only way to tell a server that refused
142
+ * the connection from a server that is not there.
143
+ *
144
+ * A healthy /ws answers 400 to a non-upgrade request; the admission guard
145
+ * answers 503.
146
+ */
147
+ async function preflight(url) {
148
+ const httpURL = url.replace(/^ws/, 'http');
149
+ try {
150
+ const res = await fetch(httpURL, { method: 'GET' });
151
+ if (res.status === 503) return { state: 'refused', detail: (await res.text()).trim() };
152
+ return { state: 'reachable', detail: `HTTP ${res.status}` };
153
+ } catch (err) {
154
+ return { state: 'unreachable', detail: err?.cause?.code ?? err?.message ?? 'connect failed' };
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Opens one simulated client. `index` of `total` picks its region of the map.
160
+ *
161
+ * `ready` settles once the client has joined, or once the connection failed.
162
+ * `tapping` settles on its first tap. `closed` settles when the socket is fully
163
+ * closed. None of them ever hangs, so the caller can await the whole fleet.
164
+ */
165
+ function startSimulatedClient(url, index, total) {
166
+ const stats = {
167
+ joined: false,
168
+ tapping: false,
169
+ taps: 0,
170
+ snapshots: 0,
171
+ sequence: 0, // highest sequence sent
172
+ lastAck: 0, // highest sequence the server reported back
173
+ serverTick: 0, // tick of the newest snapshot
174
+ tickRate: 60,
175
+ tickAt: 0, // wall clock when that snapshot arrived
176
+ gridW: 0,
177
+ gridH: 0,
178
+ posX: 0,
179
+ posY: 0,
180
+ selfSamples: 0, // snapshots carrying a self block
181
+ deadSamples: 0, // ... of which the player was a ghost awaiting respawn
182
+ closeCode: null,
183
+ failure: null,
184
+ handshakeFailed: false,
185
+ };
186
+
187
+ let tapTimer = null;
188
+ let warmupTimer = null;
189
+ let spawn = null; // learned from the first snapshot
190
+ let target = null; // this client's region of the map
191
+ let settleReady;
192
+ let settleTapping;
193
+ let settleClosed;
194
+ const ready = new Promise((resolve) => (settleReady = resolve));
195
+ const tapping = new Promise((resolve) => (settleTapping = resolve));
196
+ const closed = new Promise((resolve) => (settleClosed = resolve));
197
+ const settleAll = () => {
198
+ settleReady();
199
+ settleTapping();
200
+ };
201
+ const connectTimer = setTimeout(() => {
202
+ stats.failure ??= 'init_data timeout';
203
+ settleAll();
204
+ }, CONNECT_TIMEOUT_MS);
205
+
206
+ const label = `client ${index + 1}/${total}`;
207
+ const ws = new WebSocket(url);
208
+ ws.binaryType = 'arraybuffer';
209
+ console.log(`[cyberia-load] ${label} dialing`);
210
+
211
+ const send = (type, payload) => {
212
+ if (ws.readyState === WebSocket.OPEN) ws.send(pack(type, payload));
213
+ };
214
+
215
+ // Mirrors session_server_tick_estimate: extrapolate from the newest snapshot
216
+ // so the tick stamped on a tap is the client's best guess at the server tick.
217
+ const clientTick = () => {
218
+ if (stats.serverTick === 0) return 0;
219
+ const elapsedMs = Date.now() - stats.tickAt;
220
+ return stats.serverTick + Math.max(0, Math.floor((elapsedMs * stats.tickRate) / 1000));
221
+ };
222
+
223
+ // Walk toward this client's region until it arrives, then wander inside it.
224
+ // Heading for a spread-out region is what disperses the fleet; wandering once
225
+ // there keeps its own patch of the map busy for the rest of the run.
226
+ //
227
+ // Near the spawn it never wanders. Death returns a player to the spawn cell,
228
+ // so this is also what makes it walk back out after every respawn.
229
+ const tapDirection = () => {
230
+ if (!target) return randomDirection();
231
+ const dx = target.x - stats.posX;
232
+ const dy = target.y - stats.posY;
233
+ const onSpawn = spawn && Math.hypot(stats.posX - spawn.x, stats.posY - spawn.y) < SPAWN_CLEARANCE_CELLS;
234
+ if (!onSpawn && Math.hypot(dx, dy) <= ARRIVE_RADIUS_CELLS) return randomDirection();
235
+ return directionToward(dx, dy);
236
+ };
237
+
238
+ const tap = () => {
239
+ const [dx, dy] = tapDirection();
240
+ send('player_action', {
241
+ x: clamp(Math.round(stats.posX) + dx * TAP_STEP_CELLS, stats.gridW - 1),
242
+ y: clamp(Math.round(stats.posY) + dy * TAP_STEP_CELLS, stats.gridH - 1),
243
+ tick: clientTick(),
244
+ seq: ++stats.sequence,
245
+ });
246
+ stats.taps++;
247
+ stats.tapping = true;
248
+ settleTapping();
249
+ };
250
+
251
+ const stopTapping = () => {
252
+ if (warmupTimer) clearTimeout(warmupTimer);
253
+ warmupTimer = null;
254
+ if (tapTimer) clearInterval(tapTimer);
255
+ tapTimer = null;
256
+ stats.tapping = false;
257
+ };
258
+
259
+ const onInitData = (payload) => {
260
+ console.log(`[cyberia-load] ${label} joined`);
261
+ stats.gridW = payload.gridW;
262
+ stats.gridH = payload.gridH;
263
+ if (payload.tickRate > 0) stats.tickRate = payload.tickRate;
264
+ // Every join spawns frozen under the server's "loading" protection; taps are
265
+ // dropped by phaseInput until the matching freeze_end releases it.
266
+ send('freeze_end', { reason: 'loading' });
267
+ stats.joined = true;
268
+ clearTimeout(connectTimer);
269
+ settleReady();
270
+
271
+ // Hold before the first tap, then start on a random phase of the tap period.
272
+ warmupTimer = setTimeout(
273
+ () => {
274
+ warmupTimer = null;
275
+ tap();
276
+ tapTimer = setInterval(tap, TAP_PERIOD_MS);
277
+ },
278
+ TAP_WARMUP_MS + Math.random() * TAP_PERIOD_MS,
279
+ );
280
+ };
281
+
282
+ const onSnapshot = (payload) => {
283
+ stats.snapshots++;
284
+ if (payload.tick !== undefined) {
285
+ stats.serverTick = payload.tick;
286
+ stats.tickAt = Date.now();
287
+ }
288
+ if (payload.ack !== undefined && payload.ack > stats.lastAck) stats.lastAck = payload.ack;
289
+ if (payload.self) {
290
+ stats.posX = payload.self.posX;
291
+ stats.posY = payload.self.posY;
292
+ // The spawn cell is only knowable from the server. Learn it once, then
293
+ // pick the region this client walks to.
294
+ if (!spawn) {
295
+ spawn = { x: stats.posX, y: stats.posY };
296
+ target = dispersalTarget(index, total, stats.gridW, stats.gridH, spawn);
297
+ }
298
+ if (payload.self.respawnIn > 0) stats.deadSamples++;
299
+ stats.selfSamples++;
300
+ }
301
+ };
302
+
303
+ ws.addEventListener('open', () => send('handshake', { name: 'cyberia-load', version: '1.0.0' }));
304
+
305
+ ws.addEventListener('message', (event) => {
306
+ const msg = unpack(event.data);
307
+ if (!msg) return;
308
+ if (msg.type === 'init_data') onInitData(msg.payload);
309
+ else if (msg.type === 'snapshot') onSnapshot(msg.payload);
310
+ });
311
+
312
+ ws.addEventListener('error', () => {
313
+ // The event carries no status code. Whether this was an admission refusal
314
+ // or an unreachable server is settled by the preflight probe.
315
+ if (!stats.joined && stats.snapshots === 0) {
316
+ stats.handshakeFailed = true;
317
+ console.log(`[cyberia-load] ${label} REFUSED by the server`);
318
+ }
319
+ stats.failure ??= 'socket error';
320
+ });
321
+
322
+ ws.addEventListener('close', (event) => {
323
+ stopTapping();
324
+ stats.closeCode = event.code;
325
+ if (event.code !== 1000) stats.failure ??= `closed with code ${event.code}`;
326
+ clearTimeout(connectTimer);
327
+ settleAll();
328
+ settleClosed();
329
+ });
330
+
331
+ // Ask for a normal closure and resolve once the socket is really closed.
332
+ const disconnect = () => {
333
+ stopTapping();
334
+ clearTimeout(connectTimer);
335
+ settleAll();
336
+ if (ws.readyState === WebSocket.CONNECTING || ws.readyState === WebSocket.OPEN) ws.close(1000);
337
+ else settleClosed();
338
+ return closed;
339
+ };
340
+
341
+ const isClosed = () => ws.readyState === WebSocket.CLOSED;
342
+
343
+ return { stats, ready, tapping, closed, disconnect, stopTapping, isClosed };
344
+ }
345
+
346
+ const sum = (clients, field) => clients.reduce((total, client) => total + client.stats[field], 0);
347
+
348
+ const LOAD_TIMEOUT_MS =
349
+ RAMP_WINDOW_MS +
350
+ TAP_WARMUP_MS +
351
+ TAP_PERIOD_MS +
352
+ DURATION_MS +
353
+ CONNECT_TIMEOUT_MS +
354
+ SETTLE_MS +
355
+ CLOSE_TIMEOUT_MS +
356
+ 60000;
357
+
358
+ // Without a named cyberia-server there is no subject to load, so the suite is
359
+ // skipped at collection rather than failing on connect.
360
+ describe.skipIf(!WS_URL)(
361
+ 'Cyberia load — simulated tap clients against cyberia-server',
362
+ { timeout: LOAD_TIMEOUT_MS },
363
+ () => {
364
+ const clients = [];
365
+
366
+ beforeAll(() => {
367
+ if (typeof WebSocket === 'undefined') throw new Error('global WebSocket requires Node >= 22');
368
+ });
369
+
370
+ // Nothing this test opened may outlive it, on any exit path.
371
+ afterAll(async () => {
372
+ await Promise.all(clients.map((client) => Promise.race([client.disconnect(), sleep(CLOSE_TIMEOUT_MS)])));
373
+ });
374
+
375
+ it(`sustains ${CONNECTIONS} clients tapping at ${TAP_FREQUENCY} Hz for ${DURATION_MS} ms`, async () => {
376
+ // Ramp the joins. Arriving together puts the whole fleet on the spawn cell
377
+ // at once, and since every tap fires a skill, the pile kills itself before
378
+ // the run reaches steady state.
379
+ for (let i = 0; i < CONNECTIONS; i++) {
380
+ clients.push(startSimulatedClient(WS_URL, i, CONNECTIONS));
381
+ if (JOIN_GAP_MS > 0 && i < CONNECTIONS - 1) await sleep(JOIN_GAP_MS);
382
+ // Stop as soon as the server refuses one. The concurrent per-address cap
383
+ // cannot clear while the run holds its connections, so every later join
384
+ // would be refused too — dialing them out just delays the diagnosis.
385
+ if (clients.some((client) => client.stats.handshakeFailed)) break;
386
+ }
387
+ await Promise.all(clients.map((client) => client.ready));
388
+
389
+ const joinedCount = clients.filter((client) => client.stats.joined).length;
390
+ const refusedCount = clients.filter((client) => client.stats.handshakeFailed).length;
391
+ if (refusedCount > 0) {
392
+ // A partial success is decisive on its own: the server accepted some and
393
+ // turned the rest away, which only an admission limit does. Probing again
394
+ // would be misleading, because the connect-rate bucket refills within a
395
+ // second and then answers as if nothing was ever refused.
396
+ let cause;
397
+ if (joinedCount > 0) {
398
+ cause =
399
+ `the server admitted ${joinedCount} and refused the next one, so an admission limit is ` +
400
+ `capping the run — ${joinedCount} is almost certainly the effective ` +
401
+ 'CYBERIA_MAX_CONNECTIONS_PER_IP, because every simulated client shares one address. ' +
402
+ 'Start cyberia-server with CYBERIA_DISABLE_CONNECTION_LIMITS=1 for a load run (note: ' +
403
+ '0 leaves the limits ON), or raise CYBERIA_MAX_CONNECTIONS_PER_IP, ' +
404
+ 'CYBERIA_CONNECT_BURST_PER_IP and CYBERIA_CONNECT_RATE_PER_IP';
405
+ } else {
406
+ const probe = await preflight(WS_URL);
407
+ cause =
408
+ probe.state === 'unreachable'
409
+ ? `the endpoint is unreachable (${probe.detail}) — is cyberia-server running?`
410
+ : probe.state === 'refused'
411
+ ? `the endpoint refuses connections (503 "${probe.detail}"). Start cyberia-server ` +
412
+ 'with CYBERIA_DISABLE_CONNECTION_LIMITS=1 for a load run (0 leaves them ON)'
413
+ : `the endpoint answers (${probe.detail}) but every upgrade failed`;
414
+ }
415
+ expect.fail(`only ${joinedCount} of ${CONNECTIONS} clients joined: ${cause}`);
416
+ }
417
+
418
+ const joinFailed = clients.filter((client) => !client.stats.joined);
419
+ expect(joinFailed.length, `clients that never joined: ${joinFailed[0]?.stats.failure ?? ''}`).to.equal(0);
420
+
421
+ // Every client is through its warm-up and tapping. Only now does the run
422
+ // window open, so durationMs measures steady-state load and the expected
423
+ // tap count is not diluted by the ramp.
424
+ await Promise.all(clients.map((client) => client.tapping));
425
+ const tapsAtSteadyState = sum(clients, 'taps');
426
+
427
+ await sleep(DURATION_MS);
428
+
429
+ // Stop the load, then keep reading so the final acks land before judging.
430
+ for (const client of clients) client.stopTapping();
431
+ const droppedMidRun = clients.filter((client) => client.stats.failure);
432
+ await sleep(SETTLE_MS);
433
+
434
+ const nominalTaps = CONNECTIONS * TAP_FREQUENCY * (DURATION_MS / 1000);
435
+ const steadyStateTaps = sum(clients, 'taps') - tapsAtSteadyState;
436
+ expect(steadyStateTaps, 'player_action commands sent during the run window').to.be.at.least(
437
+ nominalTaps * TAP_YIELD_FLOOR,
438
+ );
439
+
440
+ const starved = clients.filter((client) => client.stats.snapshots === 0);
441
+ expect(starved.length, 'clients that received no AOI snapshot').to.equal(0);
442
+
443
+ // The ack is the highest input sequence the server took for this player —
444
+ // the same header field the real cyberia-client reconciles against. It must
445
+ // have caught up with what each client sent, not merely be non-zero.
446
+ const behind = clients.filter((client) => client.stats.lastAck < client.stats.sequence - ACK_LAG_TOLERANCE);
447
+ expect(
448
+ behind.length,
449
+ `clients whose input the server did not process: e.g. acked ${behind[0]?.stats.lastAck} of ` +
450
+ `${behind[0]?.stats.sequence} sent`,
451
+ ).to.equal(0);
452
+
453
+ expect(droppedMidRun.length, `clients that failed mid-run: ${droppedMidRun[0]?.stats.failure ?? ''}`).to.equal(0);
454
+
455
+ // Every client closes normally, and the run does not finish until they have.
456
+ await Promise.all(clients.map((client) => Promise.race([client.disconnect(), sleep(CLOSE_TIMEOUT_MS)])));
457
+
458
+ const stillOpen = clients.filter((client) => !client.isClosed());
459
+ expect(stillOpen.length, 'connections still open after the run').to.equal(0);
460
+
461
+ const unclean = clients.filter((client) => client.stats.closeCode !== 1000);
462
+ expect(unclean.length, `clients that did not close cleanly: first code ${unclean[0]?.stats.closeCode}`).to.equal(
463
+ 0,
464
+ );
465
+ });
466
+ },
467
+ );