@underpostnet/cyberia 3.2.80 → 3.3.0

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 (162) hide show
  1. package/.env.example +50 -15
  2. package/.github/workflows/coverall.cyberia.ci.yml +87 -0
  3. package/.github/workflows/cyberia-client.cd.yml +2 -7
  4. package/.github/workflows/cyberia-server.cd.yml +2 -7
  5. package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
  6. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
  7. package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
  8. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
  9. package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
  10. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
  11. package/.github/workflows/engine-cyberia.cd.yml +3 -67
  12. package/.github/workflows/ghpkg.ci.yml +7 -1
  13. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
  14. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  15. package/.github/workflows/release.cd.yml +1 -9
  16. package/CHANGELOG.md +291 -1
  17. package/CLI-HELP.md +174 -23
  18. package/Dockerfile +1 -1
  19. package/Dockerfile.dev +1 -1
  20. package/Dockerfile.test +1 -1
  21. package/bin/build.js +7 -5
  22. package/bin/cyberia.js +272 -98
  23. package/bin/deploy.js +19 -17
  24. package/bin/index.js +272 -98
  25. package/compose.env +50 -15
  26. package/conf.js +2 -0
  27. package/deploy/dd-cyberia/deploy.sh +42 -0
  28. package/deploy/dd-cyberia/init.sh +63 -0
  29. package/deploy/dd-cyberia/sync-deploy.sh +194 -0
  30. package/deploy/lib/logging.sh +96 -0
  31. package/deploy/pwa-microservices-template/deploy.sh +72 -0
  32. package/deploy/release/deploy.sh +62 -0
  33. package/deployment.yaml +1 -210
  34. package/docker-compose.yml +90 -85
  35. package/hardhat/package-lock.json +139 -131
  36. package/hardhat/package.json +4 -4
  37. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
  38. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  39. package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
  40. package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
  41. package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
  42. package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
  43. package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
  44. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
  45. package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
  46. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  47. package/manifests/deployment/playwright/deployment.yaml +1 -1
  48. package/manifests/mongodb/kustomization.yaml +4 -1
  49. package/manifests/mongodb/statefulset.yaml +4 -0
  50. package/manifests/mongodb/storage-class.yaml +9 -2
  51. package/nginx.conf +86 -16
  52. package/package.json +2 -2
  53. package/proxy.yaml +12 -12
  54. package/pv-pvc.yaml +0 -82
  55. package/scripts/audit-selinux.sh +64 -0
  56. package/scripts/coverall-test-cyberia.sh +24 -0
  57. package/scripts/coverall-test.sh +24 -0
  58. package/scripts/gpu-diag.sh +0 -0
  59. package/scripts/ip-info.sh +0 -0
  60. package/scripts/k3s-node-setup.sh +18 -15
  61. package/scripts/kubeadm-node-setup.sh +12 -23
  62. package/scripts/link-local-underpost-cli.sh +0 -0
  63. package/scripts/lxd-vm-setup.sh +0 -0
  64. package/scripts/maas-nat-firewalld.sh +0 -0
  65. package/scripts/nat-iptables.sh +12 -4
  66. package/scripts/rhel-grpc-setup.sh +0 -0
  67. package/scripts/rocky-kickstart.sh +25 -9
  68. package/scripts/test-monitor.sh +4 -3
  69. package/src/api/cyberia-action/cyberia-action.model.js +1 -0
  70. package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
  71. package/src/api/cyberia-instance/cyberia-fallback-world.js +49 -15
  72. package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
  73. package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
  74. package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
  75. package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
  76. package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
  77. package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
  78. package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
  79. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
  80. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +87 -1
  81. package/src/api/object-layer/object-layer.model.js +204 -3
  82. package/src/api/object-layer/object-layer.service.js +1 -14
  83. package/src/cli/baremetal.js +1 -2
  84. package/src/cli/cloud-init.js +1 -1
  85. package/src/cli/cluster.js +786 -96
  86. package/src/cli/db.js +11 -4
  87. package/src/cli/deploy.js +1698 -177
  88. package/src/cli/docker-compose.js +19 -178
  89. package/src/cli/env.js +1 -1
  90. package/src/cli/image.js +15 -7
  91. package/src/cli/index.js +245 -44
  92. package/src/cli/ipfs.js +82 -11
  93. package/src/cli/lxd.js +1 -1
  94. package/src/cli/monitor.js +2 -2
  95. package/src/cli/release.js +57 -22
  96. package/src/cli/repository.js +12 -10
  97. package/src/cli/run.js +2195 -427
  98. package/src/cli/secrets.js +969 -0
  99. package/src/cli/ssh.js +206 -105
  100. package/src/cli/system.js +26 -13
  101. package/src/cli/test.js +1 -1
  102. package/src/cli/vultr.js +583 -0
  103. package/src/cli/wireguard.js +2125 -0
  104. package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
  105. package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
  106. package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
  107. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
  108. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
  109. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  110. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
  111. package/src/client/public/cyberia-docs/ARCHITECTURE.md +35 -3
  112. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
  113. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
  114. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  115. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  116. package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
  117. package/src/client/services/object-layer/object-layer.management.js +4 -4
  118. package/src/client-builder/client-build.js +102 -13
  119. package/src/client-builder/ssr.js +27 -73
  120. package/src/db/mongo/MongoBootstrap.js +295 -54
  121. package/src/db/mongo/MongooseDB.js +51 -32
  122. package/src/index.js +25 -1
  123. package/src/projects/cyberia/besu-genesis-generator.js +3 -2
  124. package/src/projects/cyberia/catalog-cyberia.js +5 -0
  125. package/src/projects/cyberia/generate-saga.js +14 -23
  126. package/src/projects/cyberia/hot-reload-trigger.js +3 -3
  127. package/src/projects/cyberia/instance-data.js +63 -3
  128. package/src/projects/cyberia/object-layer.js +11 -21
  129. package/src/projects/underpost/catalog-underpost.js +4 -1
  130. package/src/runtime/cyberia-client/Dockerfile +1 -1
  131. package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
  132. package/src/runtime/cyberia-server/Dockerfile +1 -1
  133. package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
  134. package/src/runtime/engine-cyberia/Dockerfile +1 -1
  135. package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
  136. package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
  137. package/src/runtime/engine-cyberia/compose.env +50 -15
  138. package/src/runtime/engine-cyberia/docker-compose.yml +90 -85
  139. package/src/runtime/engine-cyberia/nginx.conf +86 -16
  140. package/src/server/backup.js +1 -1
  141. package/src/server/conf.js +1216 -168
  142. package/src/server/cri.js +70 -0
  143. package/src/server/cron.js +249 -51
  144. package/src/server/dns.js +100 -6
  145. package/src/server/environment.js +98 -0
  146. package/src/server/forward-proxy.js +549 -0
  147. package/src/server/middlewares.js +56 -1
  148. package/src/server/process.js +0 -1
  149. package/src/server/selinux.js +185 -0
  150. package/src/server/systemd.js +205 -0
  151. package/src/server/underpost-compression.js +186 -0
  152. package/src/server/underpost-gateway.js +1083 -0
  153. package/src/server/underpost-ingress.js +380 -0
  154. package/test/cluster-instances.test.js +435 -0
  155. package/test/cyberia-instance-conf-defaults.test.js +1 -0
  156. package/test/deploy-node-placement.test.js +45 -0
  157. package/test/instance-traffic-plan.test.js +710 -0
  158. package/test/selinux.test.js +71 -0
  159. package/test/sops-secret-store.test.js +612 -0
  160. package/test/underpost-gateway.test.js +510 -0
  161. package/test/underpost-ingress.test.js +305 -0
  162. package/test/wireguard-edge.test.js +1177 -0
@@ -8,7 +8,7 @@
8
8
 
9
9
  The Action System defines how NPC entities interact with players. An **Action** is a spatial, typed payload attached to a map entity that the player activates by tapping the NPC. Actions drive dialogue, shops, crafting, storage, and quest grant events.
10
10
 
11
- > **Implementation status — Alpha (talk / quest-talk):** The CyberiaAction and CyberiaDialogue MongoDB schemas and Engine REST API (`src/api/cyberia-action`, `src/api/cyberia-dialogue`) are defined. The `talk` and `quest-talk` paths are wired end-to-end: the Go server binds actions to entities at instance init, validates dialogue completion, grants quests, and advances `talk` objectives (see **Dialogue Interaction Protocol** below). Shop / craft / storage transaction processing remains planned for a later Alpha increment. The `freeze_start`/`freeze_end` WS messages for modal protection are implemented; dialogue freeze now rides on the `dlg_*` frames.
11
+ > **Implementation status — Alpha (talk / quest-talk / shop):** The CyberiaAction and CyberiaDialogue MongoDB schemas and Engine REST API (`src/api/cyberia-action`, `src/api/cyberia-dialogue`) are defined. The `talk` and `quest-talk` paths are wired end-to-end: the Go server binds actions to entities at instance init, validates dialogue completion, grants quests, and advances `talk` objectives (see **Dialogue Interaction Protocol** below). The `shop` path is wired end-to-end as well (see **Shop Transaction Flow**). Craft / storage transaction processing remains planned for a later Alpha increment. The `freeze_start`/`freeze_end` WS messages for modal protection are implemented; dialogue freeze now rides on the `dlg_*` frames.
12
12
 
13
13
  ---
14
14
 
@@ -18,9 +18,9 @@ The Action System defines how NPC entities interact with players. An **Action**
18
18
 
19
19
  ```
20
20
  CyberiaAction {
21
- code: String // stable unique slug
22
- type: String // see Action Types below
23
- label: String // display label on interaction button
21
+ code: String // stable unique, location-scoped slug
22
+ label: String // NPC overhead nameplate (there is no `type` field —
23
+ // see Action Capabilities below)
24
24
 
25
25
  // Spatial origin — NPC entity cell providing this action
26
26
  sourceMapCode: String
@@ -50,7 +50,7 @@ CyberiaAction {
50
50
  ingredients: [{ itemId: String, qty: Number }]
51
51
  }]
52
52
 
53
- storageSlots: Number // storage capacity (type='storage' only)
53
+ storageSlots: Number // vault capacity in slots; 0 disables the capability
54
54
  }
55
55
  ```
56
56
 
@@ -70,15 +70,19 @@ A single `code` groups many ordered dialogue lines. The C client fetches all lin
70
70
 
71
71
  ---
72
72
 
73
- ## Action Types
73
+ ## Action Capabilities
74
74
 
75
- | Type | Description | Active Payload |
76
- | ------------ | -------------------------------------------------------------- | ---------------------------------------------------- |
77
- | `quest-talk` | Awards the quest bound to its cell (via Take Quest), shows dialogue | quests bound by cell, `dialogCode`, `questDialogueCodes` |
78
- | `talk` | NPC dialogue only — satisfies `talk` quest objectives | `dialogCode`, `questDialogueCodes` |
79
- | `shop` | Item shop — player buys items with in-game currency | `shopItems[]` |
80
- | `craft` | Crafting station consume ingredients to produce output items | `craftRecipes[]` |
81
- | `storage` | Personal item storage vault | `storageSlots` |
75
+ An action has **no type**. Its capabilities are whatever payloads are populated,
76
+ resolved per player at interaction time — one action can be a shop and a
77
+ quest-talk giver at once.
78
+
79
+ | Capability | Active when | Payload |
80
+ | ------------ | --------------------------------------------------------------- | ---------------------------------------------------- |
81
+ | `quest-talk` | CyberiaQuests are bound to this action's cell | quests bound by cell, `dialogCode`, `questDialogueCodes` |
82
+ | `talk` | always — satisfies `talk` quest objectives | `dialogCode`, `questDialogueCodes` |
83
+ | `shop` | `shopItems[]` is non-empty — player buys items with a currency | `shopItems[]` |
84
+ | `craft` | `craftRecipes[]` is non-empty — player assembles outputs | `craftRecipes[]` |
85
+ | `storage` | `storageSlots > 0` — player banks items in a personal vault | `storageSlots` |
82
86
 
83
87
  ---
84
88
 
@@ -109,26 +113,58 @@ graph LR
109
113
 
110
114
  ## Shop Transaction Flow
111
115
 
116
+ An action carrying a non-empty `shopItems[]` is a **vendor** — there is no type
117
+ flag. The catalog reaches the two runtimes on their own transports: the Go
118
+ server receives it with the world over gRPC (`CyberiaActionMessage.shop_items`),
119
+ the C client fetches it by action code over REST and renders the **Shop** tab.
120
+ Only the server prices a purchase.
121
+
122
+ A live vendor also lights the **action-provider** capability bit
123
+ (`InteractionFlagAction`), so it carries the same overhead attention icon,
124
+ orbiting particles, and coloured interaction-column border as a pending
125
+ action-talk — the player can see there is something to do before tapping.
126
+
112
127
  ```mermaid
113
128
  sequenceDiagram
114
- participant P as Player
129
+ participant P as Player (C client)
115
130
  participant G as Go Server
116
131
  participant E as Engine (Node.js)
117
132
 
118
- P->>G: Tap shop NPC
119
- G->>E: GET /api/cyberia-action?sourceMapCode=...&sourceCellX=...
120
- E-->>G: CyberiaAction { type: 'shop', shopItems: [...] }
121
- G-->>P: init_data shop payload (item list + prices)
122
- P-->>G: FrozenInteractionState (modal open)
123
-
124
- P->>G: Buy request { itemId, quantity }
125
- G->>E: GET player coin balance
126
- Note over G: balance >= price * quantity?
127
- G->>E: Deduct coins + grant item to inventory
128
- G-->>P: FCT: CoinLoss + ItemGain events
129
- G-->>P: ThawPlayer (modal close allowed)
133
+ E-->>G: getFullInstance CyberiaAction { shopItems: [...] } (world build)
134
+ G-->>P: AOI bot block → actionCode + action-provider capability bit
135
+ P->>E: GET /api/cyberia-action/code/:code (interaction modal opens)
136
+ E-->>P: CyberiaAction { label, dialogCode, questDialogueCodes, shopItems }
137
+ Note over P: Shop tab leads the strip and opens active.<br/>Two columns of cards: item slot, name,<br/>price icon + qty, Buy (wallet icon)
138
+
139
+ P->>P: Buy quantity picker (◀ / ▶ #N, 1..10, capped by what the player<br/>can pay) with a running total, then Cancel or Buy
140
+ P->>G: shop_buy { entityId, itemId, quantity }
141
+ Note over G: vendor bound to entity? row on sale?<br/>entity inside the player's AOI?<br/>held(priceItemId) >= priceQty × quantity?
142
+ G->>G: FreezePlayer("interact") no kill mid-trade
143
+ G->>G: removePlayerItem(priceItemId, priceQty × quantity)
144
+ G->>G: addPlayerItem(itemId, quantity) + collect-objective reconcile
145
+ G-->>P: shop_ack { entityId, itemId, quantity, ok, reason }
146
+ G-->>P: AOI self-player block → authoritative inventory
147
+ Note over P: the card holds until the grant lands, then the<br/>currency's "-N" pop + expend spray play, and the<br/>item flies from the picker's slot into its<br/>inventory slot ("+N" pop)
130
148
  ```
131
149
 
150
+ The picker deliberately waits for the grant before animating: a first copy has
151
+ no inventory slot until the server delivers it, so launching the flight on the
152
+ button press would aim at the bar's fallback centre instead of the item's own
153
+ slot. Waiting also fixes the ordering — the spend is seen leaving before the
154
+ goods arrive.
155
+
156
+ Binary uplink opcode: `shop_buy` `0x1C` — `[u8 kind][str entityId][str itemId][u8 quantity]`.
157
+ The quantity is clamped server-side to `[1, shopBuyMaxQty]` (10); a client that
158
+ sends 0 means one unit. A purchase is all-or-nothing: an unaffordable total is
159
+ rejected rather than partially filled.
160
+
161
+ Rejection reasons on `shop_ack`: `no_vendor`, `not_for_sale`, `out_of_range`,
162
+ `insufficient_funds`.
163
+
164
+ `shop_ack` is notify-only, and only a rejection is surfaced (as a toast). The
165
+ inventory itself always arrives through the AOI self-player block, so a dropped
166
+ ack costs the player nothing.
167
+
132
168
  ---
133
169
 
134
170
  ## Craft Transaction Flow
@@ -159,11 +195,18 @@ sequenceDiagram
159
195
  ## Dialogue Interaction Protocol (talk / quest-talk)
160
196
 
161
197
  Tapping an interaction bubble opens the Raylib-native **`modal_interact`** modal
162
- (top half of the screen). It has a tab strip — **stack** (active item slots),
163
- **stats** (six-stat stack totals), and **action** (mission interface, shown only
164
- for action-provider entities, ESI 8) over a fixed bottom bar of right-aligned
198
+ (top half of the screen). It has a tab strip — **shop** (vendor catalog, shown
199
+ only when its action carries `shopItems`), **quest** (mission interface, shown
200
+ only when the entity provides quest codes), **stack** (active item slots), and
201
+ **stats** (six-stat stack totals) — over a fixed bottom bar of right-aligned
165
202
  integration buttons (**Chat**, **Integration**) that open the JS overlay. The
166
- action tab's **Talk / Take mission** opens `modal_dialogue` (bottom half).
203
+ paired `modal_dialogue` (bottom half) carries the talk flow.
204
+
205
+ Capability tabs lead the strip, and the leading one opens active — Shop for a
206
+ vendor, else Quest. Because the catalog resolves through an async REST fetch
207
+ after the modal is already open, the active tab keeps tracking the leading
208
+ capability until the player picks a tab themselves. Switching tabs plays a
209
+ pop-in transition, during which content taps are ignored.
167
210
 
168
211
  The client is identical for `talk` and `quest-talk`; the **server** branches after
169
212
  `dlg_complete`. The client never declares the action type, quest code, or quest
@@ -186,6 +229,25 @@ Binary uplink opcodes: `dlg_start` `0x17`, `dlg_complete` `0x18`, `dlg_cancel`
186
229
  client upserts into its local `quest_store` (Quest Journal); it never gates
187
230
  simulation state.
188
231
 
232
+ ### Provider freeze
233
+
234
+ A dialogue is one step of a provider session, not the whole of it: the interact
235
+ modal stays open afterwards with its shop and quest tabs live. So when the
236
+ talked-to entity has a bound `CyberiaAction`, `dlg_complete` / `dlg_cancel`
237
+ re-bridge the freeze to `"interact"` instead of thawing, and `shop_buy` asserts
238
+ the same freeze before it mutates anything. The player therefore cannot be
239
+ killed anywhere inside a provider session, whether or not the client
240
+ re-asserted the freeze itself.
241
+
242
+ The client half holds up its end for as long as a modal is open. `modal_interact`,
243
+ `inventory_modal` and `modal_instance_map` each own a freeze reason
244
+ (`"interact"`, `"inventory"`, `"instance-map"`) and call
245
+ `local_player_keep_freeze()` every frame they stay open — without that renewal
246
+ the 30-second freeze watchdog auto-sends `freeze_end`, and a player browsing a
247
+ shop longer than that would silently become killable. A modal closing over
248
+ another one that still owns a freeze re-bridges to it rather than ending the
249
+ freeze, so there is never a thawed frame between them.
250
+
189
251
  ### Server `dlg_complete` handling
190
252
 
191
253
  1. Validate `player.activeDialogueEntityID == msg.entityId`; drop otherwise.
@@ -292,16 +354,21 @@ The C client fetches the full `code` group sorted by `order`, then renders lines
292
354
 
293
355
  ```json
294
356
  {
295
- "code": "wason-npc",
296
- "type": "quest-talk",
297
- "label": "Talk",
298
- "sourceMapCode": "cyberia-village",
299
- "sourceCellX": 12,
300
- "sourceCellY": 8,
301
- "dialogCode": "default-wason",
302
- "questDialogueCodes": ["default-wason"],
303
- "shopItems": [],
357
+ "code": "loc-fallback-map-0-18-16",
358
+ "label": "Punk",
359
+ "sourceMapCode": "fallback-map-0",
360
+ "sourceCellX": 18,
361
+ "sourceCellY": 16,
362
+ "dialogCode": "default-punk",
363
+ "questDialogueCodes": [],
364
+ "shopItems": [{ "itemId": "tim-knife", "priceItemId": "coin", "priceQty": 10 }],
304
365
  "craftRecipes": [],
305
366
  "storageSlots": 0
306
367
  }
307
368
  ```
369
+
370
+ This is the vendor shipped in the canonical defaults
371
+ (`DefaultCyberiaActions`): the `punk`-skinned NPC on `fallback-map-0` at
372
+ (18, 16) sells `tim-knife` for 10 coins. `bin/cyberia run-workflow
373
+ seed-actions-quests` upserts it; the procedural fallback world serves it
374
+ unpersisted.
@@ -133,6 +133,24 @@ Underpost Platform deploy orchestration ensures the backend layer is ready befor
133
133
 
134
134
  ---
135
135
 
136
+ ## Edge tier
137
+
138
+ Every `cyberiaonline.com` hostname is served through one Envoy Gateway data plane over HTTP/1.1, HTTP/2 and HTTP/3 (QUIC), with TLS terminated per hostname by SNI. In development the certificates are self-signed and locally trusted, and the hostnames are mapped in `/etc/hosts`, so a browser reaches the real routing stack rather than a dev proxy.
139
+
140
+ Status pages never reach the engine at all, and the engine knows nothing about them. All three runtimes are agnostic: they return a standard HTTP status code or become unreachable. `underpost-gateway` — one shared Nginx workload in the gateway tier — proxies the site paths and intercepts those statuses, serving the declared document with the original URI and the original status code.
141
+
142
+ | Condition | Declared by | Answered from |
143
+ | --------------------------- | --------------------------------------- | -------------------------------------------------------- |
144
+ | `404` on any unmatched path | a `CyberiaPortal` view with path `/404` | `www.cyberiaonline.com/root/status-pages/404/index.html` |
145
+ | `502` / `503` / `504` | the view flagged `maintenanceDefault` | `www.cyberiaonline.com/root/maintenance/index.html` |
146
+ | `/offline`, `/maintenance` | `offlineDefault` / `maintenanceDefault` | the matching context directory |
147
+
148
+ A request to an unknown path reaches the portal, which answers a bare 404; the gateway swaps in the page. The address bar keeps the path the player typed, and the response is a true 404 — no redirect, no client-side script, and nothing for `engine-cyberia`, `cyberia-server` or `cyberia-client` to implement. API sub-paths bypass the interception, so a JSON 404 stays JSON.
149
+
150
+ Per-instance hosts follow the same layout under their own sub-path — `client.cyberiaonline.com/FOREST/status-pages/404/index.html` — from the `customStatusPages` entries in `conf.instances.json`. Both configuration files live in `engine-private/`, which is a private repository: expect the layout to be referenced without assuming the files are present locally.
151
+
152
+ ---
153
+
136
154
  ## Tick model
137
155
 
138
156
  The tick is the universal coordinate of the simulation. Every server→client snapshot and every client→server input command carries a tick value.
@@ -175,7 +193,7 @@ The render frame runs at vsync. Inside one render frame, `cyberia-client` perfor
175
193
 
176
194
  1. Poll any pending optional client-hints fetch.
177
195
  2. Capture raw input → build typed input command (`kind`, `clientTick`, `sequence`, payload) → apply to prediction → send on the wire.
178
- 3. Reconcile against the latest snapshot: drop input commands `lastAckedSequence`, rewind self to authoritative position, replay unacked commands.
196
+ 3. Reconcile against the latest snapshot: difference the authoritative position against the position this client predicted for that same tick, shift the prediction trail by that error, and — once `moveAck` covers the newest command — adopt the server's own `targetPos` and route as the walk destination.
179
197
  4. Fixed-timestep simulation: while accumulator ≥ `tickDuration`, advance prediction one tick.
180
198
  5. Interpolation: compute remote-entity view positions at `renderTick`.
181
199
  6. Render. Read view models; never mutate world state.
@@ -218,7 +236,20 @@ WS frame (binary) → decode → typed InputCommand{kind, clientTick, sequen
218
236
  authoritative world state
219
237
  ```
220
238
 
221
- `InputCommand.Sequence` is monotonic per client. The server tracks the highest applied sequence per player; `phaseReplication` writes it into every snapshot header. The client drops acknowledged input commands from its prediction replay buffer using this value.
239
+ `InputCommand.Sequence` is monotonic per client. Every snapshot carries two acknowledgements of it, and they are not interchangeable:
240
+
241
+ | Field | Meaning | Client use |
242
+ | --------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- |
243
+ | `ack` | Highest sequence **received** for this player. | Retires commands from the prediction buffer. |
244
+ | `moveAck` | Highest `PlayerAction` sequence that **re-planned movement**. | Gates adoption of the authoritative `targetPos` / `path`. |
245
+
246
+ **Movement re-plans once per player per tick, and nothing else throttles it.** Handlers record the tap's destination; `phaseInput` runs one A* per player after the queue is drained. Taps that land in the same tick describe the same instant, so only the newest is planned — the rest were superseded before they could mean anything. That coalescing is the only bound on pathfinder cost.
247
+
248
+ This is why the two acknowledgements diverge: a superseded tap is acked on arrival and never planned, so `self.path` and `self.targetPos` can still describe an earlier command. A client that adopted the route on `ack` alone would turn back toward the abandoned target — worst during rapid changes of direction, where it reads as input lag and as a walk that sets off the wrong way.
249
+
250
+ Skills are not coalesced: they fire on every accepted tap, so the uplink carries every tap and the inbound rate limiter (`DefaultMessageRate`, 30/s) is what bounds the stream.
251
+
252
+ The client predicts every tap immediately and never waits on a cadence. `self.actionCooldownMs` is the skill-trigger period only; keyboard steering paces its *refresh* by it, while a change of heading emits at once.
222
253
 
223
254
  ---
224
255
 
@@ -253,7 +284,8 @@ Every Cyberia document uses the same terms. Aliases are not permitted.
253
284
  | **tick rate** | Simulation Hz on `cyberia-server`. |
254
285
  | **snapshot** | AOI-filtered world view at one tick for one player. |
255
286
  | **prediction** | Optimistic local apply of input commands to the predicted self entity. |
256
- | **reconciliation** | Drop acknowledged inputs, rewind self to authoritative position, replay unacked inputs. |
287
+ | **reconciliation** | Correct prediction by the error measured at one tick: authoritative position minus the position predicted for that tick. Input is a destination, not a per-tick impulse, so the client keeps walking toward the `targetPos` that `moveAck` confirms, rather than replaying a command log. |
288
+ | **move coalescing** | One movement re-plan per player per tick, from the newest tap of that tick. The only bound on pathfinder cost, and the reason `moveAck` trails `ack`. |
257
289
  | **display smoothing** | Per-render-frame exponential lerp from the discrete predicted self position to a continuous on-screen position. Decouples the visible main player from sim-tick boundaries. |
258
290
  | **interpolation** | Render-time smoothing of remote entities, sampled from snapshot history. |
259
291
  | **authoritative server** | `cyberia-server`. Sole authority on world state. |
@@ -176,14 +176,14 @@ cyberia chain unpause [--network besu-k8s]
176
176
 
177
177
  Named scripts from the `scripts/` directory for seeding and build maintenance.
178
178
 
179
- | Subcommand | Description |
180
- | ---------------------------- | ----------------------------------------------------------------------------------- |
179
+ | Subcommand | Description |
180
+ | ---------------------------- | -------------------------------------------------------------------------------------- |
181
181
  | `import-default-items` | Import default object layers, skills, dialogues, actions/quests, client-hints to Mongo |
182
- | `seed-skills` | Upsert `DefaultSkillConfig` into the `cyberia-skill` collection (full records) |
183
- | `seed-dialogues` | Upsert `DefaultCyberiaDialogues` into the `cyberia-dialogue` collection |
184
- | `generate-semantic-examples` | Generate one procedural example per registered semantic prefix |
185
- | `build-manifest` | Build K8s Deployment + Service manifests for mmo-client / mmo-server |
186
- | `build-server-dashboard` | Build the static cyberia-server metrics/status dashboard (`--dev`, `--output-path`) |
182
+ | `seed-skills` | Upsert `DefaultSkillConfig` into the `cyberia-skill` collection (full records) |
183
+ | `seed-dialogues` | Upsert `DefaultCyberiaDialogues` into the `cyberia-dialogue` collection |
184
+ | `generate-semantic-examples` | Generate one procedural example per registered semantic prefix |
185
+ | `build-manifest` | Build K8s Deployment + Service manifests for mmo-client / mmo-server |
186
+ | `build-server-dashboard` | Build the static cyberia-server metrics/status dashboard (`--dev`, `--output-path`) |
187
187
 
188
188
  ```bash
189
189
  cyberia run-workflow import-default-items --env-path ./engine-private/conf/dd-cyberia/.env.development
@@ -195,6 +195,43 @@ cyberia run-workflow build-server-dashboard
195
195
 
196
196
  ---
197
197
 
198
+ ## Bringing up the full stack locally
199
+
200
+ ```bash
201
+ node bin run cluster 'express,dd-cyberia' --dev
202
+ ```
203
+
204
+ One command, no extra flags. It resets and rebuilds the node, deploys MongoDB / IPFS / Valkey, imports each database from its git backup, installs the Gateway API control plane, and deploys `dd-cyberia` behind it.
205
+
206
+ What `--dev` implies, rather than requiring you to pass it:
207
+
208
+ - **Gateway API + Envoy Gateway**, with **HTTP/3 (QUIC) on by default** beside HTTP/2 and HTTP/1.1.
209
+ - **Self-signed, locally trusted TLS** for every hostname in `conf.server.json`, plus the matching `/etc/hosts` entries — so a local Chromium reaches `https://www.cyberiaonline.com` through the real data plane.
210
+ - **The gateway static tier seeded** with the portal's `/404`, `/offline` and `/maintenance` documents before the routes are applied, then refreshed from the running container once it is Ready. See [Architecture → Edge tier](./ARCHITECTURE.md).
211
+
212
+ The run ends with a gateway status report: listener and route conditions, the workloads behind them, and an HTTPS probe of every route hostname.
213
+
214
+ ### With the MMO services
215
+
216
+ The optional third path segment brings up custom instances from `engine-private/conf/dd-cyberia/conf.instances.json` in the same run:
217
+
218
+ ```bash
219
+ node bin run cluster 'express,dd-cyberia,mmo-server' --dev
220
+ node bin run cluster 'express,dd-cyberia,mmo-server+mmo-client' --dev
221
+ ```
222
+
223
+ Each id runs only where `dd-cyberia` declares it, and only once the portal workload has rolled out — `cyberia-server` dials the engine's gRPC ClusterIP for its world configuration at boot, so the content authority has to be serving first. `mmo-server` names the whole variant family (`amethyst-strata-expansion`, `FOREST`, `TEST`); `mmo-server-forest` names one variant.
224
+
225
+ `server.cyberiaonline.com` and `client.cyberiaonline.com` are issued the same self-signed certificates as the portal hosts and written into the same `/etc/hosts` pass, so the three services are reachable over TLS from a local browser without further setup. In production the same segment issues cert-manager certificates instead.
226
+
227
+ To place the static documents again without redeploying — after rebuilding the portal client, for instance:
228
+
229
+ ```bash
230
+ node bin deploy dd-cyberia development --sync-static --gateway-api --kubeadm
231
+ ```
232
+
233
+ ---
234
+
198
235
  ## Operational rules
199
236
 
200
237
  - Preserve public CLI entrypoints and command names unless a change is intentionally breaking.
@@ -157,7 +157,7 @@ Per player:
157
157
  [9..10] u16 entityCount entity blocks that follow
158
158
  ```
159
159
 
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.
160
+ The `tick` and `lastAcked` fields are how the client reconciles its predicted self with authoritative state. `lastAcked` proves arrival, not acceptance: movement re-plans once per player per tick from the newest tap of that tick, so a tap superseded within its own tick is acknowledged and never planned. The snapshot therefore carries a second acknowledgement, `moveAck` the highest `PlayerAction` sequence that actually re-planned movement — and the client adopts the authoritative `targetPos` / `path` only once `moveAck` covers its newest command. See ARCHITECTURE.md § Input replication.
161
161
 
162
162
  Other message types (init data, FCT) carry their own headers and are not part of the per-tick replication stream.
163
163
 
@@ -1,6 +1,6 @@
1
1
  # Cyberia Online — Development Roadmap
2
2
 
3
- **Current version:** 3.2.80 | **Target milestone:** Open Alpha
3
+ **Current version:** 3.3.0 | **Target milestone:** Open Alpha
4
4
 
5
5
  ---
6
6
 
@@ -18,7 +18,7 @@ _Stackable Rendering Layers as a Unified Tokenized Reality_
18
18
 
19
19
  ---
20
20
 
21
- **Version:** 3.2.80 | **Status:** Draft | **Authors:** Underpost Engineering
21
+ **Version:** 3.3.0 | **Status:** Draft | **Authors:** Underpost Engineering
22
22
 
23
23
  ---
24
24
 
@@ -96,6 +96,46 @@ class CyberiaInstanceService {
96
96
  return reject(error);
97
97
  }),
98
98
  );
99
+ /** Fallback-world default items currently staged on the engine process. */
100
+ static getFallbackDefaultItems = () =>
101
+ new Promise((resolve, reject) =>
102
+ fetch(getApiBaseUrl({ id: 'fallback-world/default-items', endpoint }), {
103
+ method: 'GET',
104
+ headers: headersFactory(),
105
+ credentials: 'include',
106
+ })
107
+ .then(async (res) => res.json())
108
+ .then((res) => {
109
+ logger.info(res);
110
+ return resolve(res);
111
+ })
112
+ .catch((error) => {
113
+ logger.error(error);
114
+ return reject(error);
115
+ }),
116
+ );
117
+ /**
118
+ * Stage the fallback world's default items and reload a running cyberia-server
119
+ * (moderator/admin). The items ride along with the trigger — nothing is persisted.
120
+ */
121
+ static fallbackHotReload = (options = { body: {} }) =>
122
+ new Promise((resolve, reject) =>
123
+ fetch(getApiBaseUrl({ id: 'fallback-world/hot-reload', endpoint }), {
124
+ method: 'POST',
125
+ headers: headersFactory(),
126
+ credentials: 'include',
127
+ body: JSON.stringify(options.body ?? {}),
128
+ })
129
+ .then(async (res) => res.json())
130
+ .then((res) => {
131
+ logger.info(res);
132
+ return resolve(res);
133
+ })
134
+ .catch((error) => {
135
+ logger.error(error);
136
+ return reject(error);
137
+ }),
138
+ );
99
139
  static portalConnect = (options = { id: '' }) =>
100
140
  new Promise((resolve, reject) =>
101
141
  fetch(getApiBaseUrl({ id: `${options.id}/portal-connect`, endpoint }), {
@@ -293,7 +293,7 @@ class ObjectLayerManagement {
293
293
  width: 160,
294
294
  editable: false,
295
295
  sortable: false,
296
- filter: false,
296
+ filter: 'agTextColumnFilter',
297
297
  },
298
298
  {
299
299
  field: 'cid',
@@ -302,7 +302,7 @@ class ObjectLayerManagement {
302
302
  cellRenderer: CidRenderer,
303
303
  editable: false,
304
304
  sortable: false,
305
- filter: false,
305
+ filter: 'agTextColumnFilter',
306
306
  },
307
307
  {
308
308
  field: 'data.render.cid',
@@ -311,7 +311,7 @@ class ObjectLayerManagement {
311
311
  cellRenderer: AtlasCidRenderer,
312
312
  editable: false,
313
313
  sortable: false,
314
- filter: false,
314
+ filter: 'agTextColumnFilter',
315
315
  },
316
316
  {
317
317
  field: 'data.render.metadataCid',
@@ -320,7 +320,7 @@ class ObjectLayerManagement {
320
320
  cellRenderer: MetadataCidRenderer,
321
321
  editable: false,
322
322
  sortable: false,
323
- filter: false,
323
+ filter: 'agTextColumnFilter',
324
324
  },
325
325
  {
326
326
  field: 'frame08',