PC3 Phase 2: orchestration under checkpoint control + networking doc
- pm/checkpoint_ops.py (framework): playtest backend now launches with GAME_SERVERS_ENABLED + GODOT_BIN + GAME_PROJECT_DIR + GAME_SERVER_HOST so playtesters actually share a world. - docs/architecture/networking.md 7a/7b: the orchestration + client-play design as built. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
@@ -237,6 +237,62 @@ snapshot_state:
|
||||
|
||||
---
|
||||
|
||||
## 7a. Server Orchestration (implemented, PC3 Phase 2)
|
||||
|
||||
**One dedicated server process per world**, spawned on demand.
|
||||
|
||||
```
|
||||
client backend (single worker) host
|
||||
│ POST /api/worlds/{id}/join (bearer JWT) │
|
||||
│ ───────────────────────────▶ GameServerSupervisor │
|
||||
│ ensure_server(world_id): │
|
||||
│ if none running for world ─── spawn:
|
||||
│ godot --headless
|
||||
│ res://scenes/server.tscn --
|
||||
│ --port <p> --world <id>
|
||||
│ --status-file <path>
|
||||
│ ◀─────────────────────────── { host, port, world_id } │
|
||||
│ │
|
||||
│ ENet connect host:port ───────────────────────────────────▶ dedicated server
|
||||
```
|
||||
|
||||
- **Supervisor** (`backend/app/services/game_servers.py`): in-process singleton,
|
||||
so the backend MUST run single-worker. Keys servers by `world_id`. Allocates
|
||||
ports from `GAME_SERVER_PORT_MIN..MAX`. A background reaper kills a world's
|
||||
server once its **status file** (`{players, tick, updated}`, rewritten by the
|
||||
server ~1×/s) reports `players == 0` for `GAME_SERVER_IDLE_TIMEOUT_SECONDS`.
|
||||
No crash-restart — the next join respawns.
|
||||
- **Enable flag:** `GAME_SERVERS_ENABLED` (default off). The checkpoint/playtest
|
||||
launcher (`pm/checkpoint_ops.py`) turns it on, passes `GODOT_BIN`,
|
||||
`GAME_PROJECT_DIR`, and `GAME_SERVER_HOST` (the LAN IP baked into the `.exe`).
|
||||
A backend with it off returns **503** from `/join`, and the client falls back
|
||||
to local single-player.
|
||||
- **Per-world content:** all worlds currently load the same test level (M2/M7
|
||||
work). `world_id` still scopes a distinct process + session.
|
||||
|
||||
### 7b. Client play path
|
||||
|
||||
`world_list.gd` → `POST /join` → `Session.begin(world_id, host, port)` →
|
||||
load `game.tscn`. `game.gd` sees `Session.active` and connects a
|
||||
`NetworkClient`:
|
||||
|
||||
- **Local player** keeps predicting with its own collision-aware
|
||||
`CharacterBody3D` (`player.gd`) — that *is* the prediction. Its input frame
|
||||
(`player.net_input`) is forwarded to the server every physics tick.
|
||||
- **Correction backstop:** if the local position drifts from the server's by
|
||||
more than `CORRECTION_THRESHOLD` (1.5 m — generous; catches teleport / a wall
|
||||
walked through / real desync, not prediction noise) it snaps.
|
||||
- **Remote players:** rendered from snapshots, position lerped. *Appearance sync
|
||||
is a follow-up* — remotes currently show as labelled capsules; the server
|
||||
does not yet replicate avatar loadouts.
|
||||
- **Disconnect** → status message → back to the world list.
|
||||
|
||||
The `NetworkClient`'s own flat-plane prediction + input-replay reconciliation
|
||||
(§5) is exercised by `smoke_test_network_client.gd`; the collision-aware game
|
||||
path above uses the lighter `send_input()` + drift-snap instead.
|
||||
|
||||
---
|
||||
|
||||
## 8. Security & Anti-Cheat
|
||||
|
||||
- **No client-authoritative gameplay values.** Clients only send *inputs* and
|
||||
|
||||
Reference in New Issue
Block a user