Files
shadow-over-bishkek001/WEBSOCKET_NETWORKING.md

6.9 KiB

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 player commands (or an empty command list).

The current command surface is intentionally limited to SetCharacterDestinationCommand.

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.
  • 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:

{
  "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 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:

cmake -S server -B server/build
cmake --build server/build --config Debug --target GameServer

Run it from the repository root:

.\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:

--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, 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.