@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.
- package/.env.example +50 -15
- package/.github/workflows/coverall.cyberia.ci.yml +87 -0
- package/.github/workflows/cyberia-client.cd.yml +2 -7
- package/.github/workflows/cyberia-server.cd.yml +2 -7
- package/.github/workflows/docker-image.cyberia-client.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.ci.yml +4 -4
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +4 -4
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +3 -3
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +3 -3
- package/.github/workflows/engine-cyberia.cd.yml +3 -67
- package/.github/workflows/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +291 -1
- package/CLI-HELP.md +174 -23
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +1 -1
- package/bin/build.js +7 -5
- package/bin/cyberia.js +272 -98
- package/bin/deploy.js +19 -17
- package/bin/index.js +272 -98
- package/compose.env +50 -15
- package/conf.js +2 -0
- package/deploy/dd-cyberia/deploy.sh +42 -0
- package/deploy/dd-cyberia/init.sh +63 -0
- package/deploy/dd-cyberia/sync-deploy.sh +194 -0
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/deployment.yaml +1 -210
- package/docker-compose.yml +90 -85
- package/hardhat/package-lock.json +139 -131
- package/hardhat/package.json +4 -4
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -210
- package/manifests/deployment/dd-cyberia-development/gateway.yaml +80 -0
- package/manifests/deployment/dd-cyberia-development/httproute.yaml +504 -0
- package/manifests/deployment/dd-cyberia-development/proxy.yaml +12 -12
- package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +0 -82
- package/manifests/deployment/dd-cyberia-development/traffic-service.yaml +121 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/playwright/deployment.yaml +1 -1
- package/manifests/mongodb/kustomization.yaml +4 -1
- package/manifests/mongodb/statefulset.yaml +4 -0
- package/manifests/mongodb/storage-class.yaml +9 -2
- package/nginx.conf +86 -16
- package/package.json +2 -2
- package/proxy.yaml +12 -12
- package/pv-pvc.yaml +0 -82
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test-cyberia.sh +24 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +12 -4
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +4 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -0
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +63 -0
- package/src/api/cyberia-instance/cyberia-fallback-world.js +49 -15
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +8 -12
- package/src/api/cyberia-instance/cyberia-instance.controller.js +3 -0
- package/src/api/cyberia-instance/cyberia-instance.router.js +13 -0
- package/src/api/cyberia-instance/cyberia-instance.service.js +43 -0
- package/src/api/cyberia-instance/cyberia-portal-connector.js +7 -5
- package/src/api/cyberia-instance/cyberia-random-source.js +80 -0
- package/src/api/cyberia-instance/cyberia-world-generator.js +4 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +1 -0
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +87 -1
- package/src/api/object-layer/object-layer.model.js +204 -3
- package/src/api/object-layer/object-layer.service.js +1 -14
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +786 -96
- package/src/cli/db.js +11 -4
- package/src/cli/deploy.js +1698 -177
- package/src/cli/docker-compose.js +19 -178
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +15 -7
- package/src/cli/index.js +245 -44
- package/src/cli/ipfs.js +82 -11
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +2 -2
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +12 -10
- package/src/cli/run.js +2195 -427
- package/src/cli/secrets.js +969 -0
- package/src/cli/ssh.js +206 -105
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +368 -0
- package/src/client/components/cyberia/InstanceSelectionView.js +11 -8
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +5 -0
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +33 -0
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +4 -0
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
- package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +106 -39
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +35 -3
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +44 -7
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +1 -1
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +40 -0
- package/src/client/services/object-layer/object-layer.management.js +4 -4
- package/src/client-builder/client-build.js +102 -13
- package/src/client-builder/ssr.js +27 -73
- package/src/db/mongo/MongoBootstrap.js +295 -54
- package/src/db/mongo/MongooseDB.js +51 -32
- package/src/index.js +25 -1
- package/src/projects/cyberia/besu-genesis-generator.js +3 -2
- package/src/projects/cyberia/catalog-cyberia.js +5 -0
- package/src/projects/cyberia/generate-saga.js +14 -23
- package/src/projects/cyberia/hot-reload-trigger.js +3 -3
- package/src/projects/cyberia/instance-data.js +63 -3
- package/src/projects/cyberia/object-layer.js +11 -21
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/runtime/cyberia-client/Dockerfile +1 -1
- package/src/runtime/cyberia-client/Dockerfile.dev +1 -1
- package/src/runtime/cyberia-server/Dockerfile +1 -1
- package/src/runtime/cyberia-server/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +1 -1
- package/src/runtime/engine-cyberia/compose.env +50 -15
- package/src/runtime/engine-cyberia/docker-compose.yml +90 -85
- package/src/runtime/engine-cyberia/nginx.conf +86 -16
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +1216 -168
- package/src/server/cri.js +70 -0
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +1083 -0
- package/src/server/underpost-ingress.js +380 -0
- package/test/cluster-instances.test.js +435 -0
- package/test/cyberia-instance-conf-defaults.test.js +1 -0
- package/test/deploy-node-placement.test.js +45 -0
- package/test/instance-traffic-plan.test.js +710 -0
- package/test/selinux.test.js +71 -0
- package/test/sops-secret-store.test.js +612 -0
- package/test/underpost-gateway.test.js +510 -0
- package/test/underpost-ingress.test.js +305 -0
- 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
|
|
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
|
-
|
|
23
|
-
|
|
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 //
|
|
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
|
|
73
|
+
## Action Capabilities
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
| `
|
|
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
|
-
|
|
119
|
-
G
|
|
120
|
-
E
|
|
121
|
-
|
|
122
|
-
P
|
|
123
|
-
|
|
124
|
-
P->>
|
|
125
|
-
G
|
|
126
|
-
Note over G:
|
|
127
|
-
G->>
|
|
128
|
-
G
|
|
129
|
-
G
|
|
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 — **
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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": "
|
|
296
|
-
"
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
"
|
|
301
|
-
"
|
|
302
|
-
"
|
|
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:
|
|
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.
|
|
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** |
|
|
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.
|
|
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
|
|
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
323
|
+
filter: 'agTextColumnFilter',
|
|
324
324
|
},
|
|
325
325
|
{
|
|
326
326
|
field: 'frame08',
|