cyberia 3.2.9 → 3.2.12
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/.github/workflows/engine-cyberia.cd.yml +6 -0
- package/.github/workflows/npmpkg.ci.yml +1 -0
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -0
- package/.vscode/extensions.json +9 -9
- package/.vscode/settings.json +20 -4
- package/CHANGELOG.md +213 -1
- package/CLI-HELP.md +92 -23
- package/README.md +190 -348
- package/bin/build.js +24 -8
- package/bin/build.template.js +187 -0
- package/bin/cyberia.js +229 -52
- package/bin/deploy.js +12 -2
- package/bin/index.js +229 -52
- package/bump.config.js +26 -0
- package/conf.js +130 -24
- package/deployment.yaml +4 -2
- package/hardhat/package-lock.json +113 -144
- package/hardhat/package.json +4 -3
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -2
- package/manifests/kind-config-dev.yaml +8 -0
- package/manifests/lxd/lxd-admin-profile.yaml +12 -3
- package/manifests/mongodb/pv-pvc.yaml +44 -8
- package/manifests/mongodb/statefulset.yaml +55 -68
- package/manifests/mongodb-4.4/headless-service.yaml +10 -0
- package/manifests/mongodb-4.4/kustomization.yaml +3 -1
- package/manifests/mongodb-4.4/mongodb-nodeport.yaml +17 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +10 -14
- package/manifests/mongodb-4.4/statefulset.yaml +79 -0
- package/manifests/mongodb-4.4/storage-class.yaml +9 -0
- package/manifests/valkey/statefulset.yaml +1 -1
- package/manifests/valkey/valkey-nodeport.yaml +17 -0
- package/package.json +27 -15
- package/scripts/ipxe-setup.sh +52 -49
- package/scripts/k3s-node-setup.sh +81 -46
- package/scripts/lxd-vm-setup.sh +193 -8
- package/scripts/maas-nat-firewalld.sh +145 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +38 -33
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +16 -16
- package/src/api/core/core.router.js +19 -14
- package/src/api/core/core.service.js +5 -5
- package/src/api/crypto/crypto.router.js +18 -12
- package/src/api/crypto/crypto.service.js +3 -3
- package/src/api/cyberia-action/cyberia-action.model.js +1 -1
- package/src/api/cyberia-action/cyberia-action.router.js +22 -18
- package/src/api/cyberia-action/cyberia-action.service.js +5 -5
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +74 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +99 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +98 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +152 -0
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +25 -20
- package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +6 -6
- package/src/api/cyberia-entity/cyberia-entity.router.js +22 -18
- package/src/api/cyberia-entity/cyberia-entity.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-fallback-world.js +79 -4
- package/src/api/cyberia-instance/cyberia-instance.router.js +57 -52
- package/src/api/cyberia-instance/cyberia-instance.service.js +10 -10
- package/src/api/cyberia-instance/cyberia-world-generator.js +3 -3
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +14 -48
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +22 -18
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +5 -5
- package/src/api/cyberia-map/cyberia-map.router.js +35 -30
- package/src/api/cyberia-map/cyberia-map.service.js +7 -7
- package/src/api/cyberia-quest/cyberia-quest.model.js +1 -1
- package/src/api/cyberia-quest/cyberia-quest.router.js +22 -18
- package/src/api/cyberia-quest/cyberia-quest.service.js +5 -5
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +22 -18
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +5 -5
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +451 -0
- package/src/api/default/default.router.js +22 -18
- package/src/api/default/default.service.js +5 -5
- package/src/api/document/document.router.js +28 -23
- package/src/api/document/document.service.js +100 -23
- package/src/api/file/file.router.js +19 -13
- package/src/api/file/file.service.js +9 -7
- package/src/api/instance/instance.router.js +29 -24
- package/src/api/instance/instance.service.js +6 -6
- package/src/api/ipfs/ipfs.router.js +21 -16
- package/src/api/ipfs/ipfs.service.js +8 -8
- package/src/api/object-layer/object-layer.router.js +512 -507
- package/src/api/object-layer/object-layer.service.js +17 -14
- package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +22 -18
- package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +5 -5
- package/src/api/test/test.router.js +17 -12
- package/src/api/types.js +24 -0
- package/src/api/user/guest.service.js +5 -4
- package/src/api/user/user.router.js +297 -288
- package/src/api/user/user.service.js +100 -35
- package/src/cli/baremetal.js +132 -101
- package/src/cli/cluster.js +700 -232
- package/src/cli/db.js +59 -60
- package/src/cli/deploy.js +216 -137
- package/src/cli/fs.js +13 -3
- package/src/cli/index.js +80 -15
- package/src/cli/ipfs.js +4 -6
- package/src/cli/kubectl.js +4 -1
- package/src/cli/lxd.js +1099 -223
- package/src/cli/monitor.js +9 -3
- package/src/cli/release.js +334 -140
- package/src/cli/repository.js +68 -23
- package/src/cli/run.js +193 -49
- package/src/cli/secrets.js +11 -2
- package/src/cli/test.js +9 -3
- package/src/client/Default.index.js +9 -3
- package/src/client/components/core/Auth.js +5 -0
- package/src/client/components/core/ClientEvents.js +76 -0
- package/src/client/components/core/EventBus.js +4 -0
- package/src/client/components/core/Modal.js +82 -41
- package/src/client/components/core/PanelForm.js +56 -52
- package/src/client/components/core/Worker.js +162 -363
- package/src/client/components/cyberia/MapEngineCyberia.js +1 -1
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +330 -0
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +50 -410
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +114 -327
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +200 -222
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +203 -185
- package/src/client/public/cyberia-docs/CYBERIA.md +259 -0
- package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +2 -2
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/UNDERPOST-PLATFORM.md +106 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
- package/src/client/services/cyberia-client-hints/cyberia-client-hints.service.js +99 -0
- package/src/client/ssr/views/CyberiaServerMetrics.js +982 -0
- package/src/client/sw/core.sw.js +174 -112
- package/src/db/DataBaseProvider.js +115 -15
- package/src/db/mariadb/MariaDB.js +2 -1
- package/src/db/mongo/MongoBootstrap.js +657 -0
- package/src/db/mongo/MongooseDB.js +129 -21
- package/src/grpc/cyberia/grpc-server.js +25 -57
- package/src/index.js +1 -1
- package/src/runtime/cyberia-client/Dockerfile +24 -3
- package/src/runtime/cyberia-client/Dockerfile.dev +82 -0
- package/src/runtime/cyberia-server/Dockerfile +29 -4
- package/src/runtime/cyberia-server/Dockerfile.dev +71 -0
- package/src/runtime/express/Express.js +2 -2
- package/src/runtime/wp/Wp.js +8 -5
- package/src/server/auth.js +2 -2
- package/src/server/client-build-docs.js +1 -1
- package/src/server/client-build.js +94 -129
- package/src/server/conf.js +86 -83
- package/src/server/process.js +180 -19
- package/src/server/proxy.js +9 -2
- package/src/server/runtime.js +1 -1
- package/src/server/start.js +17 -5
- package/src/server/valkey.js +2 -0
- package/src/ws/IoInterface.js +16 -16
- package/src/ws/core/channels/core.ws.chat.js +11 -11
- package/src/ws/core/channels/core.ws.mailer.js +29 -29
- package/src/ws/core/channels/core.ws.stream.js +19 -19
- package/src/ws/core/core.ws.connection.js +8 -8
- package/src/ws/core/core.ws.server.js +6 -5
- package/src/ws/default/channels/default.ws.main.js +10 -10
- package/src/ws/default/default.ws.connection.js +4 -4
- package/src/ws/default/default.ws.server.js +4 -3
- package/bin/file.js +0 -202
- package/bin/vs.js +0 -74
- package/bin/zed.js +0 -84
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +0 -574
- package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +0 -467
- package/src/client/ssr/email/DefaultRecoverEmail.js +0 -21
- package/src/client/ssr/email/DefaultVerifyEmail.js +0 -17
- package/src/client/ssr/pages/CyberiaServerMetrics.js +0 -461
- /package/src/client/ssr/{offline → views}/Maintenance.js +0 -0
- /package/src/client/ssr/{offline → views}/NoNetworkConnection.js +0 -0
- /package/src/client/ssr/{pages → views}/Test.js +0 -0
|
@@ -1,260 +1,278 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
+
It is **not** the content authority. World content is loaded 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
|
-
##
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
```text
|
|
24
|
+
engine-cyberia (Node.js) cyberia-server (Go) cyberia-client (C/WASM)
|
|
25
|
+
content authority authoritative simulation presentation runtime
|
|
26
|
+
gRPC + REST tick + AOI + snapshots render + prediction
|
|
27
|
+
|
|
28
|
+
└── gRPC GetFullInstance ──► └── WebSocket binary ──►
|
|
29
|
+
world config snapshots -> / input <-
|
|
44
30
|
```
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
- Each service is supervised independently and owns its own monitor and reconnector.
|
|
33
|
+
- `cyberia-server` loads world configuration from `engine-cyberia` and does not fabricate a world.
|
|
34
|
+
- If any one of the three services is unhealthy, the game moves to standby until all three recover.
|
|
35
|
+
|
|
36
|
+
The server speaks two protocols:
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 |
|
|
38
|
+
- **gRPC inbound** to consume world configuration from `engine-cyberia`
|
|
39
|
+
- **WebSocket binary** to deliver snapshots and accept typed input commands
|
|
40
|
+
|
|
41
|
+
There is no per-tick traffic between `cyberia-server` and `engine-cyberia`, and no presentation authority in the Go runtime.
|
|
73
42
|
|
|
74
43
|
---
|
|
75
44
|
|
|
76
|
-
##
|
|
45
|
+
## Tick model
|
|
77
46
|
|
|
78
|
-
|
|
47
|
+
The tick is the universal coordinate of the simulation.
|
|
79
48
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
49
|
+
| Concept | Default | Notes |
|
|
50
|
+
| ----------------- | --------------- | ------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| **tick** | `uint32` | Monotonic, advanced once per simulation step. Resets only on world rebuild. |
|
|
52
|
+
| **tick rate** | `30` Hz | Simulation Hz. Loaded from world configuration. **The string `fps` is not used to describe this rate.** |
|
|
53
|
+
| **snapshot rate** | `20` Hz | AOI replication Hz. Decoupled from tick rate so bandwidth scales independently of simulation fidelity. |
|
|
54
|
+
| **tick duration** | `1 / tick_rate` | The dt used by every simulation phase. |
|
|
55
|
+
| **current tick** | `uint32` | The simulation step about to run (or just produced). Stamped into every outgoing snapshot. |
|
|
85
56
|
|
|
86
|
-
|
|
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
|
-
```
|
|
57
|
+
Two independent tickers:
|
|
95
58
|
|
|
96
|
-
|
|
59
|
+
| Ticker | Rate | Responsibility |
|
|
60
|
+
| ----------- | ----------------- | ------------------------------------------------------------ |
|
|
61
|
+
| simulation | `tickRate` Hz | advance world by exactly one tick; run phases in fixed order |
|
|
62
|
+
| replication | `snapshotRate` Hz | per-player AOI filter + encode + dispatch |
|
|
63
|
+
|
|
64
|
+
Movement integration is `dt`-based: `step = speed * tickDuration.Seconds()`. Frame-count integration is not used anywhere on the server.
|
|
97
65
|
|
|
98
66
|
---
|
|
99
67
|
|
|
100
|
-
##
|
|
68
|
+
## Simulation phases
|
|
69
|
+
|
|
70
|
+
Inside one simulation tick, the phases run in a fixed order. Phases are the **only** functions allowed to mutate world state.
|
|
71
|
+
|
|
72
|
+
1. **`phaseInput`** — drain each player's `InputQueue`; dispatch typed input commands to gameplay handlers.
|
|
73
|
+
2. **`phaseLifecycle`** — respawn timers, despawn expirations.
|
|
74
|
+
3. **`phaseSkills`** — skill projectile collisions.
|
|
75
|
+
4. **`phaseAI`** — bot behaviour decisions.
|
|
76
|
+
5. **`phaseMovement`** — integrate positions using `tickDuration`.
|
|
77
|
+
6. **`phasePortals`** — portal entry and teleport.
|
|
101
78
|
|
|
102
|
-
|
|
79
|
+
Separately, on the replication ticker:
|
|
103
80
|
|
|
104
|
-
|
|
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 |
|
|
81
|
+
7. **`phaseReplication`** — per player: compute AOI rectangle, build snapshot, dispatch via the player's WebSocket write channel.
|
|
112
82
|
|
|
113
|
-
|
|
83
|
+
Phases never read presentation data. Phases consume world configuration (gameplay rules) and the per-player input queue.
|
|
114
84
|
|
|
115
85
|
---
|
|
116
86
|
|
|
117
|
-
##
|
|
87
|
+
## Input command pipeline
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
WS frame (binary) → decode → typed InputCommand{kind, clientTick, sequence, payload}
|
|
93
|
+
│
|
|
94
|
+
▼
|
|
95
|
+
dispatchInputCommand
|
|
96
|
+
│
|
|
97
|
+
▼
|
|
98
|
+
PlayerState.InputQueue (per-player, bounded ring)
|
|
99
|
+
│
|
|
100
|
+
▼
|
|
101
|
+
phaseInput (under world mutex, once per simulation tick)
|
|
102
|
+
│
|
|
103
|
+
▼
|
|
104
|
+
phase_input_handlers.go — typed dispatch per InputKind
|
|
105
|
+
│
|
|
106
|
+
▼
|
|
107
|
+
authoritative world state
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Property | Detail |
|
|
111
|
+
| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| One queue per player | drained exactly once per simulation tick |
|
|
113
|
+
| One typed handler per `InputKind` | each handler runs under the world mutex held by `phaseInput` |
|
|
114
|
+
| One source of truth | `phase_input_handlers.go` is the only file that translates an input command into world state |
|
|
115
|
+
| Sequence numbering | `InputCommand.Sequence` is monotonic per client; the server tracks the highest applied sequence per player in `PlayerState.LastAckedInputSequence` |
|
|
116
|
+
|
|
117
|
+
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
118
|
|
|
119
|
-
|
|
119
|
+
### Input kinds
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
| Kind | Wire byte | Effect |
|
|
122
|
+
| ---------------- | --------- | ------------------------------------------------------------------------------- |
|
|
123
|
+
| `PlayerAction` | `0x11` | TAP — movement intent + skill trigger |
|
|
124
|
+
| `ItemActivation` | `0x12` | Equip/unequip an ObjectLayer item; validated against equipment rules |
|
|
125
|
+
| `FreezeStart` | `0x13` | Enter FrozenInteractionState (blocks movement/damage; rest of world unaffected) |
|
|
126
|
+
| `FreezeEnd` | `0x14` | Exit FrozenInteractionState |
|
|
127
|
+
| `Chat` | `0x15` | Pure relay; no game-state mutation |
|
|
128
|
+
| `GetItemsIDs` | `0x16` | Skill-item-id lookup; produces a response frame |
|
|
129
|
+
| `Handshake` | `0x10` | Connection establishment; no gameplay effect |
|
|
125
130
|
|
|
126
131
|
---
|
|
127
132
|
|
|
128
|
-
##
|
|
133
|
+
## AOI replication
|
|
129
134
|
|
|
130
|
-
|
|
135
|
+
The AOI system filters world state per-player so each client receives only what its character can perceive.
|
|
136
|
+
|
|
137
|
+
Per player:
|
|
138
|
+
|
|
139
|
+
- AOI is a rectangle centered on the player position with size determined by `aoiRadius` from world configuration.
|
|
140
|
+
- On each replication tick, the server iterates the player's map, includes any entity whose bounding rectangle overlaps the AOI, and emits a snapshot.
|
|
141
|
+
|
|
142
|
+
### Snapshot header (binary, little-endian)
|
|
131
143
|
|
|
132
144
|
```
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
145
|
+
[0] u8 msgType 0x01 = aoi_update, 0x03 = full_aoi
|
|
146
|
+
[1..4] u32 tick simulation tick at which the snapshot was produced
|
|
147
|
+
[5..8] u32 lastAcked highest InputCommand.Sequence applied for this player
|
|
148
|
+
[9..10] u16 entityCount entity blocks that follow
|
|
143
149
|
```
|
|
144
150
|
|
|
145
|
-
|
|
151
|
+
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
152
|
|
|
147
|
-
|
|
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 |
|
|
153
|
+
Other message types (init data, FCT) carry their own headers and are not part of the per-tick replication stream.
|
|
157
154
|
|
|
158
155
|
---
|
|
159
156
|
|
|
160
|
-
##
|
|
157
|
+
## World configuration
|
|
161
158
|
|
|
162
|
-
|
|
159
|
+
World configuration is loaded once at boot from engine-cyberia via gRPC `GetFullInstance(instanceCode)`. The simulation consumes the following from it:
|
|
163
160
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
166
|
-
| `
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
161
|
+
| Field | Used for |
|
|
162
|
+
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
163
|
+
| `cellSize` | grid math |
|
|
164
|
+
| `tickRate` | simulation Hz |
|
|
165
|
+
| `aoiRadius` | per-player AOI rectangle size |
|
|
166
|
+
| `entityBaseSpeed`, `entityBaseMaxLife`, `entityBaseActionCooldownMs` | base stats |
|
|
167
|
+
| `economyRules` | Fountain & Sink coin economy |
|
|
168
|
+
| `skillRules` | projectile / doppelganger spawn rates and lifetimes |
|
|
169
|
+
| `equipmentRules` | item activation constraints (one-per-type, requireSkin, activeItemTypes) |
|
|
170
|
+
| `entityDefaults[*]` | per-entity-type gameplay defaults: live/dead/drop item IDs, default object layers |
|
|
170
171
|
|
|
171
|
-
|
|
172
|
+
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.
|
|
173
|
+
|
|
174
|
+
Hot reload of ObjectLayers is supported via periodic `GetObjectLayerManifest` calls; world topology and gameplay rules are reloaded only on server restart.
|
|
172
175
|
|
|
173
176
|
---
|
|
174
177
|
|
|
175
|
-
##
|
|
178
|
+
## Presentation metadata ownership
|
|
176
179
|
|
|
177
|
-
|
|
180
|
+
`cyberia-server` holds **no** presentation state. There is no field on the server for:
|
|
178
181
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
- palette
|
|
183
|
+
- status-icon iconId or border color
|
|
184
|
+
- camera smoothing or camera zoom
|
|
185
|
+
- dev-overlay flag
|
|
186
|
+
- screen-factor overrides
|
|
187
|
+
- interpolation window
|
|
184
188
|
|
|
185
|
-
|
|
189
|
+
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
190
|
|
|
187
|
-
|
|
191
|
+
### `sim_palette.go`
|
|
188
192
|
|
|
189
|
-
|
|
193
|
+
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
194
|
|
|
191
|
-
|
|
195
|
+
- compile-time constant
|
|
196
|
+
- not loaded from any contract (gRPC, REST, proto, env)
|
|
197
|
+
- read only at world-build and one-shot spawn paths
|
|
198
|
+
- never consulted during any per-tick simulation phase
|
|
192
199
|
|
|
193
|
-
|
|
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.
|
|
200
|
+
The client treats those wire bytes as a hint and resolves the actual fallback color from its own palette by entity type.
|
|
197
201
|
|
|
198
202
|
---
|
|
199
203
|
|
|
200
|
-
##
|
|
204
|
+
## Source layout
|
|
205
|
+
|
|
206
|
+
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/`.
|
|
207
|
+
|
|
208
|
+
| File | Responsibility |
|
|
209
|
+
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| `main.go` | Entry point — loads `.env`, dials engine-cyberia gRPC, mounts WS and `/api` router, starts listener |
|
|
211
|
+
| `src/tick.go` | `Tick`, `InputSequence` types and tick rate constants |
|
|
212
|
+
| `src/server.go` | `GameServer` struct, simulation+replication tickers, `ApplyInstanceConfig`, world mutator orchestration |
|
|
213
|
+
| `src/types.go` | Core data structures (`PlayerState`, `BotState`, `MapState`, `ObjectLayerState`, etc.) |
|
|
214
|
+
| `src/simulation_phases.go` | Phase entry points called from the tick loop |
|
|
215
|
+
| `src/phase_input_handlers.go` | Typed dispatch per `InputKind`; the only translator from input commands to world state |
|
|
216
|
+
| `src/input_command.go` | `InputCommand` struct, `InputKind` constants, per-player queue helper |
|
|
217
|
+
| `src/aoi_binary.go` | Binary AOI snapshot encoder; message type constants |
|
|
218
|
+
| `src/object_layer.go` | ObjectLayer Go types mirroring MongoDB schema |
|
|
219
|
+
| `src/instance_loader.go` | World reconstruction from gRPC payload |
|
|
220
|
+
| `src/collision.go` | Grid collision, portal transitions, death handling |
|
|
221
|
+
| `src/pathfinding.go` | A\* pathfinding for bot and player navigation |
|
|
222
|
+
| `src/skill.go`, `src/skill_dispatcher.go`, `src/skill_projectile.go`, `src/skill_doppelganger.go` | Skill registry and per-skill handlers |
|
|
223
|
+
| `src/economy.go` | Fountain & Sink coin economy |
|
|
224
|
+
| `src/life_regen.go` | HP regeneration |
|
|
225
|
+
| `src/ai.go` | Bot AI |
|
|
226
|
+
| `src/stats.go` | Active stat aggregation, sum-stats limit enforcement |
|
|
227
|
+
| `src/entity_status.go` | Entity Status Indicator (ESI) numeric IDs |
|
|
228
|
+
| `src/frozen_state.go` | FrozenInteractionState |
|
|
229
|
+
| `src/handlers.go` | WebSocket lifecycle, binary uplink decoder, JSON-uplink back-compat adapter |
|
|
230
|
+
| `src/sim_palette.go` | Internal RGBA fill for AOI wire bytes (not a contract) |
|
|
231
|
+
| `src/grpcclient/` | gRPC client + world builder for engine-cyberia |
|
|
232
|
+
| `api/router.go`, `api/metrics.go` | chi router; `/api/v1/*` endpoints |
|
|
233
|
+
| `proto/cyberia.proto` | gRPC service contract shared with engine-cyberia |
|
|
234
|
+
|
|
235
|
+
---
|
|
201
236
|
|
|
202
|
-
|
|
237
|
+
## REST surface
|
|
203
238
|
|
|
204
|
-
|
|
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) |
|
|
239
|
+
`/api/v1/*` is the operational surface; it is independent of the WebSocket gameplay protocol.
|
|
212
240
|
|
|
213
|
-
|
|
241
|
+
| Endpoint | Method | Description |
|
|
242
|
+
| --------------------------- | ------ | ----------------------------------------- |
|
|
243
|
+
| `/api/v1/health` | GET | Simple health check |
|
|
244
|
+
| `/api/v1/metrics` | GET | Complete server metrics snapshot |
|
|
245
|
+
| `/api/v1/metrics/health` | GET | Detailed health with entity/player counts |
|
|
246
|
+
| `/api/v1/metrics/entities` | GET | Entity-type breakdown |
|
|
247
|
+
| `/api/v1/metrics/websocket` | GET | Active connections, message rates |
|
|
248
|
+
| `/api/v1/metrics/workload` | GET | Per-map entity workload |
|
|
249
|
+
|
|
250
|
+
All content data (ObjectLayer metadata, asset blobs, optional client hints) is served directly by engine-cyberia REST. `cyberia-server` does not proxy content.
|
|
214
251
|
|
|
215
252
|
---
|
|
216
253
|
|
|
217
|
-
## Environment
|
|
254
|
+
## Environment
|
|
218
255
|
|
|
219
|
-
| Variable | Default | Description
|
|
220
|
-
| --------------------------------- | ----------------- |
|
|
221
|
-
| `ENGINE_GRPC_ADDRESS` | `localhost:50051` |
|
|
222
|
-
| `INSTANCE_CODE` | `default` | Instance code to load on startup
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `
|
|
256
|
+
| Variable | Default | Description |
|
|
257
|
+
| --------------------------------- | ----------------- | -------------------------------------------------- |
|
|
258
|
+
| `ENGINE_GRPC_ADDRESS` | `localhost:50051` | engine-cyberia gRPC address (**required**) |
|
|
259
|
+
| `INSTANCE_CODE` | `default` | Instance code to load on startup |
|
|
260
|
+
| `ENGINE_API_BASE_URL` | _(empty)_ | engine-cyberia REST base URL; forwarded to clients |
|
|
261
|
+
| `ENGINE_GRPC_RELOAD_INTERVAL_SEC` | _(disabled)_ | ObjectLayer hot-reload polling interval |
|
|
262
|
+
| `SERVER_PORT` | `8081` | WebSocket + HTTP listen port |
|
|
263
|
+
| `STATIC_DIR` | `./public` | Directory for static WASM client files |
|
|
227
264
|
|
|
228
265
|
---
|
|
229
266
|
|
|
230
|
-
## Build and
|
|
267
|
+
## Build and run
|
|
231
268
|
|
|
232
269
|
```bash
|
|
233
|
-
# Development
|
|
234
270
|
cd cyberia-server
|
|
271
|
+
|
|
272
|
+
# Development
|
|
235
273
|
go run main.go
|
|
236
274
|
|
|
237
275
|
# Production binary
|
|
238
276
|
go build -o cyberia-server .
|
|
239
277
|
./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
278
|
```
|