cyberia 3.2.9 → 3.2.22

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 (184) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +7 -0
  2. package/.github/workflows/engine-cyberia.ci.yml +14 -2
  3. package/.github/workflows/ghpkg.ci.yml +1 -0
  4. package/.github/workflows/npmpkg.ci.yml +10 -5
  5. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  6. package/.github/workflows/release.cd.yml +1 -0
  7. package/.vscode/extensions.json +9 -9
  8. package/.vscode/settings.json +20 -4
  9. package/CHANGELOG.md +363 -1
  10. package/CLI-HELP.md +975 -1061
  11. package/README.md +190 -348
  12. package/bin/build.js +102 -125
  13. package/bin/build.template.js +33 -0
  14. package/bin/cyberia.js +238 -56
  15. package/bin/deploy.js +16 -3
  16. package/bin/index.js +238 -56
  17. package/bump.config.js +26 -0
  18. package/conf.js +131 -24
  19. package/deployment.yaml +76 -2
  20. package/hardhat/package-lock.json +113 -144
  21. package/hardhat/package.json +4 -3
  22. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  23. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  24. package/manifests/deployment/dd-cyberia-development/deployment.yaml +76 -2
  25. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  26. package/manifests/kind-config-dev.yaml +8 -0
  27. package/manifests/lxd/lxd-admin-profile.yaml +12 -3
  28. package/manifests/mongodb/pv-pvc.yaml +44 -8
  29. package/manifests/mongodb/statefulset.yaml +55 -68
  30. package/manifests/mongodb-4.4/headless-service.yaml +10 -0
  31. package/manifests/mongodb-4.4/kustomization.yaml +3 -1
  32. package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
  33. package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
  34. package/manifests/mongodb-4.4/statefulset.yaml +79 -0
  35. package/manifests/mongodb-4.4/storage-class.yaml +9 -0
  36. package/manifests/valkey/statefulset.yaml +1 -1
  37. package/manifests/valkey/valkey-nodeport.yaml +17 -0
  38. package/package.json +31 -19
  39. package/scripts/ipxe-setup.sh +52 -49
  40. package/scripts/k3s-node-setup.sh +81 -46
  41. package/scripts/link-local-underpost-cli.sh +6 -0
  42. package/scripts/lxd-vm-setup.sh +193 -8
  43. package/scripts/maas-nat-firewalld.sh +145 -0
  44. package/scripts/test-monitor.sh +250 -0
  45. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
  46. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
  47. package/src/api/core/core.router.js +19 -14
  48. package/src/api/core/core.service.js +5 -5
  49. package/src/api/crypto/crypto.router.js +18 -12
  50. package/src/api/crypto/crypto.service.js +3 -3
  51. package/src/api/cyberia-action/cyberia-action.model.js +1 -1
  52. package/src/api/cyberia-action/cyberia-action.router.js +22 -18
  53. package/src/api/cyberia-action/cyberia-action.service.js +5 -5
  54. package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
  55. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
  56. package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
  57. package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
  58. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
  59. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
  60. package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
  61. package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
  62. package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
  63. package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
  64. package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
  65. package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
  69. package/src/api/cyberia-map/cyberia-map.router.js +35 -30
  70. package/src/api/cyberia-map/cyberia-map.service.js +7 -7
  71. package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
  72. package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
  73. package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
  74. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
  75. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
  76. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +458 -0
  77. package/src/api/default/default.router.js +22 -18
  78. package/src/api/default/default.service.js +5 -5
  79. package/src/api/document/document.router.js +28 -23
  80. package/src/api/document/document.service.js +100 -23
  81. package/src/api/file/file.router.js +19 -13
  82. package/src/api/file/file.service.js +9 -7
  83. package/src/api/instance/instance.router.js +29 -24
  84. package/src/api/instance/instance.service.js +6 -6
  85. package/src/api/ipfs/ipfs.router.js +21 -16
  86. package/src/api/ipfs/ipfs.service.js +8 -8
  87. package/src/api/object-layer/object-layer.router.js +512 -507
  88. package/src/api/object-layer/object-layer.service.js +17 -14
  89. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
  90. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
  91. package/src/api/test/test.router.js +17 -12
  92. package/src/api/types.js +24 -0
  93. package/src/api/user/guest.service.js +5 -4
  94. package/src/api/user/user.router.js +297 -288
  95. package/src/api/user/user.service.js +100 -35
  96. package/src/cli/baremetal.js +132 -101
  97. package/src/cli/cluster.js +700 -232
  98. package/src/cli/db.js +59 -60
  99. package/src/cli/deploy.js +291 -294
  100. package/src/cli/env.js +1 -4
  101. package/src/cli/fs.js +13 -3
  102. package/src/cli/image.js +58 -4
  103. package/src/cli/index.js +127 -15
  104. package/src/cli/ipfs.js +4 -6
  105. package/src/cli/kubectl.js +4 -1
  106. package/src/cli/lxd.js +1099 -223
  107. package/src/cli/monitor.js +396 -9
  108. package/src/cli/release.js +355 -146
  109. package/src/cli/repository.js +169 -30
  110. package/src/cli/run.js +347 -117
  111. package/src/cli/secrets.js +11 -2
  112. package/src/cli/test.js +9 -3
  113. package/src/client/Default.index.js +9 -3
  114. package/src/client/components/core/Auth.js +5 -0
  115. package/src/client/components/core/ClientEvents.js +76 -0
  116. package/src/client/components/core/EventBus.js +4 -0
  117. package/src/client/components/core/Modal.js +82 -41
  118. package/src/client/components/core/PanelForm.js +14 -10
  119. package/src/client/components/core/Worker.js +162 -363
  120. package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
  121. package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
  122. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +55 -1
  123. package/src/client/public/cyberia-docs/ARCHITECTURE.md +223 -361
  124. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
  125. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
  126. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +212 -185
  127. package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
  128. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
  129. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +23 -1
  130. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  131. package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
  132. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  133. package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
  134. package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
  135. package/src/client/sw/core.sw.js +174 -112
  136. package/src/db/DataBaseProvider.js +115 -15
  137. package/src/db/mariadb/MariaDB.js +2 -1
  138. package/src/db/mongo/MongoBootstrap.js +657 -0
  139. package/src/db/mongo/MongooseDB.js +130 -21
  140. package/src/grpc/cyberia/grpc-server.js +25 -57
  141. package/src/index.js +1 -1
  142. package/src/runtime/cyberia-client/Dockerfile +10 -7
  143. package/src/runtime/cyberia-client/Dockerfile.dev +67 -0
  144. package/src/runtime/cyberia-server/Dockerfile +11 -6
  145. package/src/runtime/cyberia-server/Dockerfile.dev +47 -0
  146. package/src/runtime/express/Express.js +2 -2
  147. package/src/runtime/wp/Dockerfile +3 -3
  148. package/src/runtime/wp/Wp.js +8 -5
  149. package/src/server/auth.js +2 -2
  150. package/src/server/catalog-underpost.js +61 -0
  151. package/src/server/catalog.js +77 -0
  152. package/src/server/client-build-docs.js +1 -1
  153. package/src/server/client-build.js +94 -129
  154. package/src/server/conf.js +496 -135
  155. package/src/server/ipfs-client.js +5 -3
  156. package/src/server/process.js +180 -19
  157. package/src/server/proxy.js +9 -2
  158. package/src/server/runtime-status.js +235 -0
  159. package/src/server/runtime.js +1 -1
  160. package/src/server/start.js +44 -11
  161. package/src/server/valkey.js +2 -0
  162. package/src/ws/IoInterface.js +16 -16
  163. package/src/ws/core/channels/core.ws.chat.js +11 -11
  164. package/src/ws/core/channels/core.ws.mailer.js +29 -29
  165. package/src/ws/core/channels/core.ws.stream.js +19 -19
  166. package/src/ws/core/core.ws.connection.js +8 -8
  167. package/src/ws/core/core.ws.server.js +6 -5
  168. package/src/ws/default/channels/default.ws.main.js +10 -10
  169. package/src/ws/default/default.ws.connection.js +4 -4
  170. package/src/ws/default/default.ws.server.js +4 -3
  171. package/test/deploy-monitor.test.js +251 -0
  172. package/bin/file.js +0 -202
  173. package/bin/vs.js +0 -74
  174. package/bin/zed.js +0 -84
  175. package/manifests/deployment/dd-test-development/deployment.yaml +0 -254
  176. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  177. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
  178. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
  179. package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
  180. package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
  181. package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
  182. /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
  183. /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
  184. /package/src/client/ssr/{pages → views}/Test.js +0 -0
@@ -1,260 +1,287 @@
1
- # Cyberia Server
1
+ <p align="center">
2
+ <img src="https://www.cyberiaonline.com/assets/splash/apple-touch-icon-precomposed.png" alt="CYBERIA online"/>
3
+ </p>
2
4
 
3
- **Path:** `cyberia-server/` | **Language:** Go
5
+ <div align="center">
4
6
 
5
- ---
7
+ <h1>cyberia server</h1>
8
+
9
+ </div>
10
+
11
+ **Path:** `cyberia-server/` · **Language:** Go · **Role:** authoritative simulation runtime for Cyberia
6
12
 
7
- ## Overview
13
+ `cyberia-server` is the authoritative simulation runtime for the Cyberia MMO extension on Underpost Platform. It owns world state, advances a fixed-rate tick, drains typed input commands from connected clients, and dispatches AOI-filtered snapshots on a separately-paced replication tick.
8
14
 
9
- `cyberia-server` is the real-time multiplayer game server for Cyberia Online. It maintains the authoritative game state, runs the AI and physics simulation, and communicates with clients via a custom binary WebSocket protocol. It connects to the Node.js Engine at startup via gRPC to load the game world and Object Layer data.
15
+ It is **not** the content authority. World content is loaded once at boot from `engine-cyberia` over gRPC. It is **not** the render-policy authority. Presentation is owned by `cyberia-client`.
10
16
 
11
17
  ---
12
18
 
13
- ## Architecture
14
-
15
- ```mermaid
16
- graph TB
17
- subgraph cyberia-server["cyberia-server (Go)"]
18
- main["main.go\nHTTP + WS listener"]
19
- loader["instance_loader.go\nWorld Builder from gRPC"]
20
- server["server.go\nGame loop + Player registry"]
21
- aoi["aoi_binary.go\nBinary AOI Protocol"]
22
- collision["collision.go\nGrid collision + portals"]
23
- pathfind["pathfinding.go\nA* bot navigation"]
24
- skills["skill.go\nSkill tap/kill entry\nskill_dispatcher.go\nSkill registry"]
25
- economy["economy.go\nFountain & Sink"]
26
- ai["ai.go\nBot behavior"]
27
- frozen["frozen_state.go\nModal protection"]
28
- static["static.go\nStatic file serving (WASM client)"]
29
- end
30
-
31
- grpcClient["grpcclient/\nEngine gRPC client"] --> loader
32
- loader --> server
33
- server --> aoi
34
- server --> collision
35
- server --> pathfind
36
- server --> skills
37
- server --> economy
38
- server --> ai
39
- server --> frozen
40
-
41
- Engine["engine-cyberia :50051\n(gRPC)"] <-->|gRPC| grpcClient
42
- Client["cyberia-client\n(C/WASM)"] <-->|WS binary| aoi
43
- Client <-->|REST /api/v1/*| server
19
+ ## Operating model
20
+
21
+ Three independent processes, non-overlapping roles. The ecosystem is playable only when all three are running and healthy at the same time.
22
+
23
+ ```
24
+ engine-cyberia (Node.js) cyberia-server (Go) cyberia-client (C/WASM)
25
+ ───────────────────────── ────────────────── ──────────────────────
26
+ content authority authoritative simulation presentation runtime
27
+ persisted maps + rules tick + AOI + snapshots render + prediction
28
+
29
+ │ gRPC GetFullInstance │ WebSocket binary
30
+ │────────────────────────────────────► │ AOI snapshots + init
31
+ │ (world configuration: │ ▲
32
+ │ AOI radius, economy, │ │ typed input commands
33
+ │ skill, equipment, │ │
34
+ │ entity gameplay defaults) │ │
35
+ │ │ │
36
+ ▼ ▼ ▼
37
+ (boot + hot reload) tick loop + replication
44
38
  ```
45
39
 
46
- ---
40
+ - Each service is supervised independently and owns its own monitor and reconnector.
41
+ - `cyberia-server` dials `engine-cyberia` gRPC at boot and exits on dial failure rather than fabricate a world.
42
+ - On reconnect, world configuration is reloaded via `GetFullInstance(instanceCode)`.
43
+ - If any one of the three services is unhealthy, the game moves to standby until all three recover.
44
+
45
+ The server speaks two protocols:
47
46
 
48
- ## Source Files
49
-
50
- | File | Responsibility |
51
- | ----------------------- | ---------------------------------------------------------------------------------------------- |
52
- | `main.go` | Entry point — HTTP/WS server, signal handling |
53
- | `server.go` | WebSocket lifecycle, game loop, player/bot registry |
54
- | `types.go` | Core data structures (PlayerState, BotState, ObjectLayer, etc.) |
55
- | `aoi_binary.go` | Binary AOI wire format encoder + message type constants |
56
- | `object_layer.go` | ObjectLayer Go types mirroring MongoDB schema |
57
- | `instance_loader.go` | Reconstructs world from gRPC `GetFullInstanceResponse` |
58
- | `collision.go` | Grid collision detection, portal transitions, death handling |
59
- | `pathfinding.go` | A\* pathfinding for bot/player navigation |
60
- | `skill.go` | Skill entry points: `HandlePlayerTapAction`, `HandleOnKillSkills`, `GetAssociatedSkillItemIDs` |
61
- | `skill_dispatcher.go` | Skill registry: `InitSkills()`, `DispatchSkill()`, `dispatchSkillsForEntity()` |
62
- | `skill_projectile.go` | Projectile skill handler (spawns `skill` bot entities) |
63
- | `skill_doppelganger.go` | Doppelganger skill handler (spawns allied clone bots) |
64
- | `economy.go` | Fountain & Sink coin economy — all economy methods |
65
- | `life_regen.go` | HP regeneration ticker |
66
- | `ai.go` | Bot AI — aggro, wander, target selection |
67
- | `stats.go` | Active stat aggregation, sum-stats limit enforcement |
68
- | `entity_status.go` | Entity Status Indicator (ESI) computation |
69
- | `frozen_state.go` | FrozenInteractionState — modal protection for players |
70
- | `handlers.go` | WebSocket message handlers (move, action, inventory, etc.) |
71
- | `static.go` | Static file serving for the WASM client |
72
- | `grpcclient/` | gRPC client for the Engine data service |
47
+ - **gRPC, inbound, at boot and hot reload:** consumes world configuration from `engine-cyberia`.
48
+ - **WebSocket binary, ongoing:** delivers AOI snapshots to clients, accepts typed input commands.
49
+
50
+ There is no per-tick traffic between `cyberia-server` and `engine-cyberia`, and no presentation authority in the Go runtime.
73
51
 
74
52
  ---
75
53
 
76
- ## gRPC World Loading
54
+ ## Tick model
77
55
 
78
- At startup, the Go server dials the Engine gRPC endpoint and calls `GetFullInstance(instanceCode)`:
56
+ The tick is the universal coordinate of the simulation.
79
57
 
80
- ```mermaid
81
- sequenceDiagram
82
- participant G as Go Server
83
- participant E as Engine (gRPC :50051)
84
- participant DB as MongoDB
58
+ | Concept | Default | Notes |
59
+ | ----------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
60
+ | **tick** | `uint32` | Monotonic, advanced once per simulation step. Resets only on world rebuild. |
61
+ | **tick rate** | `30` Hz | Simulation Hz. Loaded from world configuration. **The string `fps` is not used to describe this rate.** |
62
+ | **snapshot rate** | `20` Hz | AOI replication Hz. Decoupled from tick rate so bandwidth scales independently of simulation fidelity. |
63
+ | **tick duration** | `1 / tick_rate` | The dt used by every simulation phase. |
64
+ | **current tick** | `uint32` | The simulation step about to run (or just produced). Stamped into every outgoing snapshot. |
85
65
 
86
- G->>E: GetFullInstance("cyberia-main")
87
- E->>DB: Query CyberiaInstance + all CyberiaMap + CyberiaEntity + ObjectLayer
88
- E-->>G: GetFullInstanceResponse { instance, maps[], entities[], objectLayers[], config }
89
- G->>G: BuildWorldFromInstance → in-memory map grid + entity registry
90
- G->>G: ApplyInstanceConfig → sets economy rules, skill config, equipment rules
91
- G->>G: ReplaceObjectLayerCache → indexes all ObjectLayer metadata by itemId
92
- G->>E: GetObjectLayerBatch() (stream — cache warm-up)
93
- Note over G: gRPC load complete — WebSocket server ready
94
- ```
66
+ Two independent tickers:
95
67
 
96
- **Fallback:** If the `instanceCode` doesn't match any MongoDB record, the Engine returns a minimal playable fallback (empty 64×64 map) instead of `NOT_FOUND`. If the Engine is unreachable, the Go server exits with a fatal error (gRPC is required).
68
+ | Ticker | Rate | Responsibility |
69
+ | ----------- | ----------------- | ------------------------------------------------------------ |
70
+ | simulation | `tickRate` Hz | advance world by exactly one tick; run phases in fixed order |
71
+ | replication | `snapshotRate` Hz | per-player AOI filter + encode + dispatch |
72
+
73
+ Movement integration is `dt`-based: `step = speed * tickDuration.Seconds()`. Frame-count integration is not used anywhere on the server.
97
74
 
98
75
  ---
99
76
 
100
- ## WebSocket Message Handlers
77
+ ## Simulation phases
78
+
79
+ Inside one simulation tick, the phases run in a fixed order. Phases are the **only** functions allowed to mutate world state.
80
+
81
+ 1. **`phaseInput`** — drain each player's `InputQueue`; dispatch typed input commands to gameplay handlers.
82
+ 2. **`phaseLifecycle`** — respawn timers, despawn expirations.
83
+ 3. **`phaseSkills`** — skill projectile collisions.
84
+ 4. **`phaseAI`** — bot behaviour decisions.
85
+ 5. **`phaseMovement`** — integrate positions using `tickDuration`.
86
+ 6. **`phasePortals`** — portal entry and teleport.
101
87
 
102
- Incoming client messages dispatch through `handlers.go`:
88
+ Separately, on the replication ticker:
103
89
 
104
- | Message Type | Description |
105
- | --------------------------------- | ------------------------------------------------------------------------------------------------------------- |
106
- | `player_action` | Player tap action — carries `targetX`/`targetY`; triggers `HandlePlayerTapAction` (movement + skill dispatch) |
107
- | `item_activation` | Equip/unequip an Object Layer item; enforces one-per-type and `maxActiveLayers` rules |
108
- | `get_items_ids` | Given an `itemId`, returns the list of associated skill entity item IDs (`skill_item_ids` response) |
109
- | `freeze_start` / `dialogue_start` | Enter FrozenInteractionState (blocks movement and damage); `dialogue_start` accepted for backward compat |
110
- | `freeze_end` / `dialogue_end` | Exit FrozenInteractionState |
111
- | `chat` | Pure relay — forward JSON chat message to target player, no game-state mutation |
90
+ 7. **`phaseReplication`** per player: compute AOI rectangle, build snapshot, dispatch via the player's WebSocket write channel.
112
91
 
113
- > **Pre-alpha scope:** Action/quest/shop/craft/portal WS handlers are not yet implemented in the Go server. These systems are defined in the Node.js Engine API (`src/api/cyberia-action`, `src/api/cyberia-quest`) and are planned for the alpha milestone.
92
+ Phases never read presentation data. Phases consume world configuration (gameplay rules) and the per-player input queue.
114
93
 
115
94
  ---
116
95
 
117
- ## Area of Interest (AOI)
96
+ ## Input command pipeline
97
+
98
+ Client input is typed end-to-end. There is no JSON intermediate on the binary path. The simulation tick is the only consumer of input commands.
99
+
100
+ ```
101
+ WS frame (binary) → decode → typed InputCommand{kind, clientTick, sequence, payload}
102
+
103
+
104
+ dispatchInputCommand
105
+
106
+
107
+ PlayerState.InputQueue (per-player, bounded ring)
108
+
109
+
110
+ phaseInput (under world mutex, once per simulation tick)
111
+
112
+
113
+ phase_input_handlers.go — typed dispatch per InputKind
114
+
115
+
116
+ authoritative world state
117
+ ```
118
+
119
+ | Property | Detail |
120
+ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
121
+ | One queue per player | drained exactly once per simulation tick |
122
+ | One typed handler per `InputKind` | each handler runs under the world mutex held by `phaseInput` |
123
+ | One source of truth | `phase_input_handlers.go` is the only file that translates an input command into world state |
124
+ | Sequence numbering | `InputCommand.Sequence` is monotonic per client; the server tracks the highest applied sequence per player in `PlayerState.LastAckedInputSequence` |
125
+
126
+ A second uplink path, `handleJSONUplink`, parses text-framed JSON uplinks into the same typed `InputCommand` and routes them through the same per-tick queue. No synchronous game-state mutation runs on the WebSocket read goroutine.
118
127
 
119
- The AOI system ensures each client receives only data about nearby entities, reducing bandwidth and server load:
128
+ ### Input kinds
120
129
 
121
- - Each player has a rectangular AOI bounding box centered on their position.
122
- - AOI size is configured per instance (`aoi_width`, `aoi_height` in `CyberiaInstanceConf`).
123
- - On each tick, the server computes the delta (entities entered/left AOI) and sends a binary `aoi_update` (0x01) message.
124
- - On initial connect, the server sends a full AOI snapshot (`full_aoi`, 0x03) plus `init_data` (0x02).
130
+ | Kind | Wire byte | Effect |
131
+ | ---------------- | --------- | ------------------------------------------------------------------------------- |
132
+ | `PlayerAction` | `0x11` | TAP movement intent + skill trigger |
133
+ | `ItemActivation` | `0x12` | Equip/unequip an ObjectLayer item; validated against equipment rules |
134
+ | `FreezeStart` | `0x13` | Enter FrozenInteractionState (blocks movement/damage; rest of world unaffected) |
135
+ | `FreezeEnd` | `0x14` | Exit FrozenInteractionState |
136
+ | `Chat` | `0x15` | Pure relay; no game-state mutation |
137
+ | `GetItemsIDs` | `0x16` | Skill-item-id lookup; produces a response frame |
138
+ | `Handshake` | `0x10` | Connection establishment; no gameplay effect |
125
139
 
126
140
  ---
127
141
 
128
- ## Skill System
142
+ ## AOI replication
129
143
 
130
- ### Trigger Pipeline
144
+ The AOI system filters world state per-player so each client receives only what its character can perceive.
145
+
146
+ Per player:
147
+
148
+ - AOI is a rectangle centered on the player position with size determined by `aoiRadius` from world configuration.
149
+ - On each replication tick, the server iterates the player's map, includes any entity whose bounding rectangle overlaps the AOI, and emits a snapshot.
150
+
151
+ ### Snapshot header (binary, little-endian)
131
152
 
132
153
  ```
133
- player_action WS message
134
- HandlePlayerTapAction(player, mapState, target)
135
- dispatchSkillsForEntity(player, mapState, target)
136
- iterate player.ObjectLayers (active items)
137
- → build skillMap: itemId → []SkillDefinition
138
- → for each SkillDefinition:
139
- DispatchSkill(logicEventId, SkillContext)
140
- "projectile" → skill_projectile.go handler
141
- "doppelganger" → skill_doppelganger.go handler
142
- "coin_drop_or_transaction" → economy.go handler
154
+ [0] u8 msgType 0x01 = aoi_update, 0x03 = full_aoi
155
+ [1..4] u32 tick simulation tick at which the snapshot was produced
156
+ [5..8] u32 lastAcked highest InputCommand.Sequence applied for this player
157
+ [9..10] u16 entityCount entity blocks that follow
143
158
  ```
144
159
 
145
- ### Skill Rules (SkillRules proto message server fields)
160
+ The `tick` and `lastAcked` fields are how the client reconciles its predicted self with authoritative state. The client drops input commands with `sequence ≤ lastAcked` from its replay buffer, then rewinds and replays the rest.
146
161
 
147
- | Field | Description |
148
- | --------------------------------- | ------------------------------------------------------ |
149
- | `projectileSpawnChance` | Probability [0–1] of spawning a projectile per trigger |
150
- | `projectileLifetimeMs` | Projectile lifetime in milliseconds |
151
- | `projectileWidth/Height` | Entity dimensions (in cells) |
152
- | `projectileSpeedMultiplier` | Speed multiplier relative to base entity speed |
153
- | `doppelgangerSpawnChance` | Probability of spawning a doppelganger |
154
- | `doppelgangerLifetimeMs` | Doppelganger lifetime |
155
- | `doppelgangerSpawnRadius` | Max spawn distance from triggering player (cells) |
156
- | `doppelgangerInitialLifeFraction` | Starting HP as fraction of player max HP |
162
+ Other message types (init data, FCT) carry their own headers and are not part of the per-tick replication stream.
157
163
 
158
164
  ---
159
165
 
160
- ## Bot AI
166
+ ## World configuration
161
167
 
162
- Bots are spawned from `CyberiaEntity` records with `entityType: "bot"`. The `ai.go` module drives their behavior:
168
+ World configuration is loaded once at boot from engine-cyberia via gRPC `GetFullInstance(instanceCode)`. The simulation consumes the following from it:
163
169
 
164
- | Behavior | Description |
165
- | --------- | ---------------------------------------------------------------------------------------- |
166
- | `hostile` | Has a weapon; will pathfind to and attack players/other bots within `aggroRange` |
167
- | `passive` | No weapon; wanders randomly within `spawnRadius` |
168
- | `skill` | Projectile entity moves in a fixed direction, despawns on collision or lifetime expiry |
169
- | `coin` | Static collectible grants coins on player contact, then despawns |
170
+ | Field | Used for |
171
+ | -------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
172
+ | `cellSize` | grid math |
173
+ | `tickRate` | simulation Hz |
174
+ | `aoiRadius` | per-player AOI rectangle size |
175
+ | `entityBaseSpeed`, `entityBaseMaxLife`, `entityBaseActionCooldownMs` | base stats |
176
+ | `economyRules` | Fountain & Sink coin economy |
177
+ | `skillRules` | projectile / doppelganger spawn rates and lifetimes |
178
+ | `equipmentRules` | item activation constraints (one-per-type, requireSkin, activeItemTypes) |
179
+ | `entityDefaults[*]` | per-entity-type gameplay defaults: live/dead/drop item IDs, default object layers |
170
180
 
171
- **Bot respawn:** Dead bots respawn at their `spawnPoint` after a configurable delay. Each respawn calls `FountainInitBot(bot)` to reset the coin wallet to `botSpawnCoins`.
181
+ World configuration is gameplay-only. Presentation fields (palette, status-icon visuals, camera knobs, dev-overlay flag, interpolation window, screen factors) are not part of this contract. Presentation metadata ownership is described in the next section.
182
+
183
+ Hot reload of ObjectLayers is supported via periodic `GetObjectLayerManifest` calls; world topology and gameplay rules are reloaded only on server restart.
172
184
 
173
185
  ---
174
186
 
175
- ## FrozenInteractionState
187
+ ## Presentation metadata ownership
176
188
 
177
- While a player has a modal open (dialogue, shop, inventory, craft), they enter **FrozenInteractionState**:
189
+ `cyberia-server` holds **no** presentation state. There is no field on the server for:
178
190
 
179
- ```go
180
- // frozen_state.go
181
- FreezePlayer(player, reason string) // freezes: no damage, no movement, no events
182
- ThawPlayer(player) // unfreezes: returns to normal gameplay
183
- ```
191
+ - palette
192
+ - status-icon iconId or border color
193
+ - camera smoothing or camera zoom
194
+ - dev-overlay flag
195
+ - screen-factor overrides
196
+ - interpolation window
184
197
 
185
- The player's `Frozen` flag is broadcast to clients on each AOI tick so other players see the frozen (chat-icon) status indicator.
198
+ These live in the client runtime's compile-time defaults. Per-instance presentation overrides are served by engine-cyberia at `GET /api/cyberia-client-hints/:instanceCode` and consumed directly by the client. The Go process never calls that endpoint.
186
199
 
187
- ---
200
+ ### `sim_palette.go`
188
201
 
189
- ## Hot-Reload
202
+ A small internal RGBA table inside `sim_palette.go` exists solely to fill the optional per-entity color bytes on the AOI wire for portals, skill projectiles, and freshly spawned players. The table is:
190
203
 
191
- The Go server supports **incremental ObjectLayer hot-reload** without restart:
204
+ - compile-time constant
205
+ - not loaded from any contract (gRPC, REST, proto, env)
206
+ - read only at world-build and one-shot spawn paths
207
+ - never consulted during any per-tick simulation phase
192
208
 
193
- 1. At interval `ENGINE_GRPC_RELOAD_INTERVAL_SEC`, the server calls `GetObjectLayerManifest()`.
194
- 2. It diffs the returned `{ itemId, sha256 }` pairs against the cached manifest.
195
- 3. For changed items, it calls `GetObjectLayer(itemId)` to fetch updated data.
196
- 4. `ReplaceObjectLayerCache` atomically replaces the stale entry.
209
+ The client treats those wire bytes as a hint and resolves the actual fallback color from its own palette by entity type.
197
210
 
198
211
  ---
199
212
 
200
- ## REST API
213
+ ## Source layout
214
+
215
+ Paths are relative to `cyberia-server/`. Gameplay logic lives under `src/`; the gRPC client and world builder under `src/grpcclient/`; the chi-based REST router under `api/`.
216
+
217
+ | File | Responsibility |
218
+ | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
219
+ | `main.go` | Entry point — loads `.env`, dials engine-cyberia gRPC, mounts WS and `/api` router, starts listener |
220
+ | `src/tick.go` | `Tick`, `InputSequence` types and tick rate constants |
221
+ | `src/server.go` | `GameServer` struct, simulation+replication tickers, `ApplyInstanceConfig`, world mutator orchestration |
222
+ | `src/types.go` | Core data structures (`PlayerState`, `BotState`, `MapState`, `ObjectLayerState`, etc.) |
223
+ | `src/simulation_phases.go` | Phase entry points called from the tick loop |
224
+ | `src/phase_input_handlers.go` | Typed dispatch per `InputKind`; the only translator from input commands to world state |
225
+ | `src/input_command.go` | `InputCommand` struct, `InputKind` constants, per-player queue helper |
226
+ | `src/aoi_binary.go` | Binary AOI snapshot encoder; message type constants |
227
+ | `src/object_layer.go` | ObjectLayer Go types mirroring MongoDB schema |
228
+ | `src/instance_loader.go` | World reconstruction from gRPC payload |
229
+ | `src/collision.go` | Grid collision, portal transitions, death handling |
230
+ | `src/pathfinding.go` | A\* pathfinding for bot and player navigation |
231
+ | `src/skill.go`, `src/skill_dispatcher.go`, `src/skill_projectile.go`, `src/skill_doppelganger.go` | Skill registry and per-skill handlers |
232
+ | `src/economy.go` | Fountain & Sink coin economy |
233
+ | `src/life_regen.go` | HP regeneration |
234
+ | `src/ai.go` | Bot AI |
235
+ | `src/stats.go` | Active stat aggregation, sum-stats limit enforcement |
236
+ | `src/entity_status.go` | Entity Status Indicator (ESI) numeric IDs |
237
+ | `src/frozen_state.go` | FrozenInteractionState |
238
+ | `src/handlers.go` | WebSocket lifecycle, binary uplink decoder, JSON-uplink back-compat adapter |
239
+ | `src/sim_palette.go` | Internal RGBA fill for AOI wire bytes (not a contract) |
240
+ | `src/grpcclient/` | gRPC client + world builder for engine-cyberia |
241
+ | `api/router.go`, `api/metrics.go` | chi router; `/api/v1/*` endpoints |
242
+ | `proto/cyberia.proto` | gRPC service contract shared with engine-cyberia |
243
+
244
+ ---
201
245
 
202
- The Go server exposes a metrics and health REST API under `/api/v1/`:
246
+ ## REST surface
203
247
 
204
- | Endpoint | Method | Description |
205
- | --------------------------- | ------ | ------------------------------------------------------------- |
206
- | `/api/v1/health` | GET | Simple health check — `{"status":"ok"}` |
207
- | `/api/v1/metrics` | GET | Complete server metrics snapshot |
208
- | `/api/v1/metrics/health` | GET | Detailed health with entity/player counts |
209
- | `/api/v1/metrics/entities` | GET | Entity type breakdown and active counts |
210
- | `/api/v1/metrics/websocket` | GET | Active WebSocket connections, message rates |
211
- | `/api/v1/metrics/workload` | GET | Per-map entity workload (players, bots, floors, ObjectLayers) |
248
+ `/api/v1/*` is the operational surface; it is independent of the WebSocket gameplay protocol.
212
249
 
213
- All other game data (ObjectLayer metadata, instance config, file blobs) is served by the **Node.js Engine REST API** directly to the client — the Go server does not proxy Engine data.
250
+ | Endpoint | Method | Description |
251
+ | --------------------------- | ------ | ----------------------------------------- |
252
+ | `/api/v1/health` | GET | Simple health check |
253
+ | `/api/v1/metrics` | GET | Complete server metrics snapshot |
254
+ | `/api/v1/metrics/health` | GET | Detailed health with entity/player counts |
255
+ | `/api/v1/metrics/entities` | GET | Entity-type breakdown |
256
+ | `/api/v1/metrics/websocket` | GET | Active connections, message rates |
257
+ | `/api/v1/metrics/workload` | GET | Per-map entity workload |
258
+
259
+ All content data (ObjectLayer metadata, asset blobs, optional client hints) is served directly by engine-cyberia REST. `cyberia-server` does not proxy content.
214
260
 
215
261
  ---
216
262
 
217
- ## Environment Variables
263
+ ## Environment
218
264
 
219
- | Variable | Default | Description |
220
- | --------------------------------- | ----------------- | ------------------------------------------------------------------------------------ |
221
- | `ENGINE_GRPC_ADDRESS` | `localhost:50051` | Engine gRPC address (**required**) |
222
- | `INSTANCE_CODE` | `default` | Instance code to load on startup |
223
- | `ENGINE_GRPC_RELOAD_INTERVAL_SEC` | _(disabled)_ | ObjectLayer hot-reload polling interval |
224
- | `SERVER_PORT` | `8081` | HTTP + WS listen port |
225
- | `STATIC_DIR` | `./public` | Directory for static WASM client files |
226
- | `READY_CMD` | _(empty)_ | Shell command to run after server starts (used by orchestration to signal readiness) |
265
+ | Variable | Default | Description |
266
+ | --------------------------------- | ----------------- | -------------------------------------------------- |
267
+ | `ENGINE_GRPC_ADDRESS` | `localhost:50051` | engine-cyberia gRPC address (**required**) |
268
+ | `INSTANCE_CODE` | `default` | Instance code to load on startup |
269
+ | `ENGINE_API_BASE_URL` | _(empty)_ | engine-cyberia REST base URL; forwarded to clients |
270
+ | `ENGINE_GRPC_RELOAD_INTERVAL_SEC` | _(disabled)_ | ObjectLayer hot-reload polling interval |
271
+ | `SERVER_PORT` | `8081` | WebSocket + HTTP listen port |
272
+ | `STATIC_DIR` | `./public` | Directory for static WASM client files |
227
273
 
228
274
  ---
229
275
 
230
- ## Build and Run
276
+ ## Build and run
231
277
 
232
278
  ```bash
233
- # Development
234
279
  cd cyberia-server
280
+
281
+ # Development
235
282
  go run main.go
236
283
 
237
284
  # Production binary
238
285
  go build -o cyberia-server .
239
286
  ./cyberia-server
240
-
241
- # Docker
242
- docker build -t cyberia-server .
243
- docker run -e ENGINE_GRPC_ADDRESS=engine:50051 -e INSTANCE_CODE=cyberia-main cyberia-server
244
- ```
245
-
246
- ---
247
-
248
- ## Kubernetes Deployment
249
-
250
- The Go server deploys as `mmo-server` in the Kubernetes cluster:
251
-
252
- ```yaml
253
- # Key environment variables in deployment.yaml
254
- - name: ENGINE_GRPC_ADDRESS
255
- value: 'dd-cyberia-service:50051' # cluster-internal Engine service
256
- - name: INSTANCE_CODE
257
- value: 'cyberia-main'
258
- - name: SERVER_PORT
259
- value: '8081'
260
287
  ```