# WebSocket Deterministic Lockstep Networking ## Scope The Windows WebSocket transport uses Boost.Asio and Boost.Beast to run a server-sequenced deterministic lockstep simulation. A client receives one authoritative state at a completed tick boundary. After that, the server sends only numbered tick messages containing ordered destination and presence commands (or an empty command list). Client intent is intentionally limited to `SetCharacterDestinationCommand`. The server also sends `SetCharacterEnabledCommand` in tick bundles when a configured player slot connects or disconnects. ## Runtime Architecture ### Server `WebsocketGameServer` wraps the headless `InternalGameServer`. - The simulation tick is fixed at 100 ms. - Each accepted connection is assigned the first free character ID from the campaign's `networkPlayerSlots`; the connection is rejected when all slots are occupied. - Player slots start disabled. The server consumes and broadcasts an enabled command on the next tick for each connection and disconnection, including to the newly connected client. - The initial message contains the state, completed snapshot tick, assigned player ID, tick duration, two-tick input delay, and compatibility fingerprint. - Destination inputs cross from the Asio thread through a queue. The simulation thread validates client sequence numbers, replaces client identity with the assigned slot, and schedules commands two ticks in the future. - Every tick is broadcast, including ticks with no commands. No periodic state snapshots are sent. ### Client `WebsocketGameClient` implements `IGameServer`. - It connects asynchronously to `ws://127.0.0.1:8080/`. - Received messages are queued by the network thread and applied on the game thread from `update()`. - It maintains an internal `InternalGameServer` replica so existing snapshot consumers and transitional runtime-state bindings continue to work. - Camera orientation lives in host-owned `LocationLocalState`, so incoming authoritative snapshots cannot overwrite camera movement. - `submit()` serializes destination intent only. Other command variants remain local no-ops for a remote connection. - Tick bundles are buffered by ID. The replica advances only while the next contiguous tick is available, applying its commands before one exact 100 ms simulation step. - Completed simulation events are passed to the runtime `Location` for presentation, and ordinary server events are drainable through `IGameServer`. - Client-side save and load calls are ignored because persistence remains server-side. `RemoteGameServerHost` prepares local presentation resources and runtime locations against the replica and presents completed-tick events. It never advances simulation from a render-frame delta. ## Campaign Routing The graphical game uses stable router objects for `IGameServer` and `IGameServerHost`, allowing existing UI and runtime references to remain valid. When `NETWORK` is defined: - `battle_zone_multiplayer` uses `WebsocketGameClient` and `RemoteGameServerHost`. - `main`, `battle_zone`, and every other campaign use `InternalGameServer` and `LocalGameServerHost`. The WebSocket client is created only when `battle_zone_multiplayer` is selected and is destroyed when that campaign is unloaded. The single-player and multiplayer Battle Zone entries share location content and the start-state path, so the state can be regenerated through the single-player entry. At startup, the standalone server loads the authoritative half of that shared save and rewrites its campaign ID to `battle_zone_multiplayer`; each remote client restores only the local presentation half. The multiplayer campaign currently declares the pre-existing `player` and `player2` state entries as connection slots; the random test-bot controller is disabled. The campaign's `networkCompatibility` token is the fingerprint; release builds must bump it whenever deterministic content changes. If DNS resolution, TCP connection, the WebSocket handshake, or the active socket fails, the client unloads the remote campaign and returns to the campaign selection screen. ## JSON Protocol v2 Every message uses a versioned JSON envelope: ```json { "protocolVersion": 2, "type": "tick", "payload": { "tickId": 81, "commands": [], "stateHash": "0123456789abcdef" } } ``` Message types are: - `initial_state` (server to client): one state snapshot and lockstep metadata; - `destination_input` (client to server): monotonic client sequence and a centimetre-normalized finite target; - `tick` (server to client): tick ID, globally sequenced destination and character-presence commands, and post-tick diagnostic state hash; - `error` (server to client): invalid input, incompatibility, or full server. Protocol v1 is not compatible. Malformed payloads, non-finite or out-of-range coordinates, duplicate client sequences, and unsupported commands cannot enter the authoritative command stream. WebSocket ordering is not used as simulation time. Integer tick IDs are the only authoritative clock. The server uses `steady_clock` only to pace its loop, and clients may have different wall clocks without affecting the result. A client stalls on a missing tick instead of predicting or rolling back. Single-player persistence remains a separate envelope combining authoritative and client-local state. `savedAt`, camera state, and presentation objects are excluded from the diagnostic deterministic hash. ## Build Configuration Networking is enabled only for Windows: - `proj-windows/CMakeLists.txt` defines `NETWORK`, compiles the WebSocket client and remote host, and links `ws2_32` and `mswsock`. - `server/CMakeLists.txt` defines `NETWORK` for the Windows `GameServer`, compiles the WebSocket server, and links the Windows socket libraries. - Linux, Android, and Web builds do not define `NETWORK` and do not compile the network transport. The previous Web-build `NETWORK` definition was removed. ## Running Build the server: ```powershell cmake -S server -B server/build cmake --build server/build --config Debug --target GameServer ``` Run it from the repository root: ```powershell .\server\build\Debug\GameServer.exe --campaign battle_zone_multiplayer --location uni_exterior ``` The listener defaults to `0.0.0.0:8080`. It can be changed with: ```text --address IP --port N ``` Launch the Windows game and select `battle_zone_multiplayer`. The client connects to `127.0.0.1:8080`. Selecting another campaign continues to use the local server. ## Verification CTest covers protocol-v2 round trips and validation, input-delay ordering, fixed-tick replica behavior, campaign slot metadata, initial connection and slot assignment, connection/disconnection presence propagation, destination submission, and contiguous tick consumption. ## Current Limitations - Only preconfigured player slots can connect; dynamic join/spawn commands are not implemented. - There is no authentication or reconnect/replay history. - A hash mismatch is logged but does not yet trigger checkpoint resynchronization. - The endpoint is hardcoded in the graphical client. - Multiplayer Lua and dialogue behavior remains unsupported. - Determinism is supported for identical executable/content builds. The floating-point simulation is not guaranteed bit-identical across platforms or compilers.