Deterministic lockstep server.
The server runs one authoritative game world. Clients receive snapshots and command frames, then run the same fixed-tick simulation locally for prediction and checksum validation.
- Low network overhead through semantic commands.
- Deterministic simulation with replay support.
- Predictable latency through fixed command delay.
The repo holds a library and a worked example of using it.
rts_engine/— the library, the only installable package. Owns the protocol: command ordering, frame assembly, history windows, resync payloads, desync detection. Owns no game and imports nothing from one.example_server/— a game built on it: esper world, components, physics, MOVE/JUMP. Copy this as a starting template.example_client/— browser canvas client for that game, no build step.docs/protocol.md— the contract the library owns; read it to write your own game or a client in another language.docs/client-guide.md— the example's game rules: components, physics, world layout.
Attach a game by implementing WorldProtocol — six methods and a command union. That
type is the entire seam; everything game-specific hangs off it.
One process runs one match: the example keeps state in a process-global ECS (esper). Scale by running more processes.
- Simulation uses integer coordinates and velocities.
- Every tick applies commands in canonical
(tick, player_id, sequence, command)order. - Entity iteration is sorted by stable unit id.
- Networking never mutates simulation state directly.
- Async is allowed around I/O, not inside the simulation update.
Install for development:
! REQUIRES python3.14
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytestRun the example server (tokens are pre-shared secrets issued to each client by the launcher):
python -m example_server --host 127.0.0.1 --port 8766 --player-tokens alice-token bob-tokenOmit --player-tokens to connections become spectators and cannot issue commands.
New clients receive a state_sync message:
snapshot: deterministic world snapshot atsnapshot_tick.command_frames: authoritative command history after the snapshot.current_tick: tick to replay up to before live frames continue.
The server keeps this snapshot as a bootstrap cache. Late-joining clients reconstruct state by loading the latest snapshot and replaying only the command tail after it.
Runtime transport uses websocket frames carrying MessagePack payloads. Full contract, including the checksum algorithm and its golden vector, is in docs/protocol.md.
Every connection must send an auth message as its first frame before any other message is accepted. The server closes the connection silently on an unknown token or a 10-second timeout.
# client → server (first message)
{"kind": "auth", "token": "<pre-shared-token>"}
# server → client (on success)
{"kind": "state_sync", "player_id": 6, "snapshot": ..., "command_frames": [...], ...}player_id is the server-assigned entity ID that owns the client's units.
Command frames contain intentions, not replicated unit state. The server
assigns the issuer from the authenticated connection — the wire field is
ignored.
# client → server
{"kind": "command", "command": {"type": "MOVE", "sequence": 1, "targets": [7, 8], "x": 1}}
# server → client
{"kind": "command_accepted", "sequence": 1, "assigned_tick": 105}
# server → all clients (each tick)
{"kind": "command_frame", "tick": 105, "commands": [...]}Clients periodically send deterministic state checksums. The server validates
only ticks within the window [snapshot_tick, current_tick + checksum_interval].
# client → server
{"kind": "checksum", "tick": 200, "checksum": "7d87f1ab"}Server compares client checksums for the same tick against its own
authoritative value and other clients. If values differ, it broadcasts a
desync_report with checksum groups by participant.
- No backpressure for broadcast
- No Rate-Limit on commands