Multiplayer Architecture
Domination now has a real online multiplayer foundation:
Server-computed casual XP and persistent player stats — see multiplayer-ranking.md.
Release integrity mode is
peer_unranked. The relay does not run the simulation and cannot independently verify a peer-reported result, so new public matches do not changerankedXporrankedGames. Existing ladder rows are historical/frozen until authoritative simulation or a verifiable result protocol ships.A headless dedicated TLS server in
server/src/server.js.A Unity
SslStreamclient inAssets/Scripts/Multiplayerwith system trust, certificate lifetime, and hostname validation.A main-menu multiplayer flow for connect, lobby browser, create/join lobby (masked password), faction/team/ready, host map-change/lock/kick controls, and host start. Lobby dropdowns offer the server's map registry (including approved custom maps) when connected.
Lobby chat with quick comms (server-relayed, sanitized, 200-char cap, 500ms per-sender throttle) and rank insignia on every roster/post-game entry — player ranks ride on all lobby/match player payloads and refresh when the lobby reopens after a match.
AI opponents: the host's ADD AI button seats a bot (per-slot faction/team/start/difficulty, always ready and pre-loaded, editable, kickable, and persistent across rematches). Every client simulates bot brains locally; remote humans' brains stay idle. Bot matches award participation/time XP only — no win/loss bonuses, so farming bots cannot climb the ladder — and bots earn nothing. Bot brains run on the fixed-step, seeded, tick-aligned sim like everything else; the bot-heavy sync-test variant formally verifies AI determinism with the same TLS lockstep gate as human-only matches.
The 16-player capacity, compatibility, authored-start, performance, UI, and live-test contract is documented in multiplayer-16-player-validation.md.
Server-authored match configuration containing map id/hash, player ids, factions, teams, spawn indices, tick settings, and match seed.
Loading synchronization before simulation is unpaused.
Server-accepted command relay for movement, attack-move, exact unit/building attacks, stop, stance, building placement, unit production, rally points, and surrender. The exact fail-closed player-action inventory is documented in multiplayer-command-surface.md.
Reconnect tokens and grace windows for lobby and pre-simulation loading disconnects. Tokens are private — each player only ever receives their own.
Running-match reconnect is deliberately disabled for 1.0. Command replay is not an authoritative state snapshot and cannot restore missed simulation ticks, economy, combat, RNG, ownership, or dynamic-world state. The server returns
running_reconnect_unavailable; Unity keeps the stale simulation paused and shows the disconnect outcome instead of resuming into a desync.Lockstep completeness horizon: the server broadcasts
tickSync{tick}every 5 ticks; clients derive the safe horizon from the negotiated command lead and never simulate pasttick + commandLeadTicks - 1. TCP ordering makes late commands structurally impossible — load spikes become a brief hold, not divergence. Protocol 0.1 is fixed at 10 Hz with a canonical 3-tick lead; valid leads are 1–20 ticks (100 ms–2 s). Missing or incompatible timing is rejected before match start/loading reconnect instead of falling back to a divergent local default. World-gen is fully client-symmetric online (base-zone skips iterate ALL starts).Desync containment: three divergent comparisons in a rolling five-report window stop the match, so alternating mismatch/agreement cannot reset the detector forever. The result is
integrity_stop: no winner, XP, win, loss, or ranked mutation; the replay and authenticated evidence are archived. A strict-majority outlier in 3+ player games is recorded as an outlier but never treated as proof of a winner. A 1v1 or tied split is explicitly ambiguous. Durable integrity incidents and escalating review cooldowns prevent consequence-free repeated voids. See multiplayer-integrity-containment.md. Checksum cohorts are frozen per tick, accepted hashes survive disconnect or surrender, and normal results wait briefly for an active peer's pending report. Full agreed/diverged trigger evidence is journaled atomically before the multi-profile incident transaction and client result broadcast.Online determinism stack: fixed-step sim (0.1s quanta, catch-up capped), tick-aligned command execution (accepted commands buffer and apply at their server-stamped executeTick in (tick, seq) order inside the step loop), per-match seeded sim RNG (feat barrages, intercept rolls, storm strikes — which also no longer target the local camera), symmetric per-faction wallet accrual (every human faction earns by one neutral formula on every client), and checksums captured at exact tick boundaries. Offline skirmish/campaign paths are byte-identical to before.
Replay archive: every finished match is stored server-side at
<dataDir>/replays/<matchId>.json(validated setup, ordered retained command suffix, result; reconnect tokens blanked) and served vialistReplays/downloadReplay. Large downloads use negotiated 256 KiB chunks, one outstanding chunk per client, exact application acknowledgements, SHA-256 verification, bounded concurrent transfers, and an atomic client-side publish. Clients also stream a local copy at match end topersistentDataPath/replays/, which the REPLAYS menu lists without loading a large command array into memory. Command retention is independently bounded by count and bytes: 65,536 entries for both archives, 32 MiB on the dedicated server, and 128 MiB for the Unity client's local archive. Both archive types explicitly record whether trimming occurred and their retained count/bytes; the server also records the exact dropped count for its retained suffix. Neither is presented as complete after trimming. Both archives retaintickRateandcommandLeadTicks. Playback is a future step and may consume only an untrimmed archive as a complete match stream.Crash-recoverable normal results: every completed match stages one immutable write-ahead transaction spanning all participant profiles and the canonical replay. The server broadcasts progression only after the durable commit marker; unresolved recovery makes readiness fail closed. See multiplayer-result-transactions.md.
Server-side result quorum for defeated players and final match result broadcasts.
Model
The first implementation uses a hybrid deterministic-lockstep control model.
Clients request actions. The server validates:
- game version
- exact multiplayer protocol version (
lockstep-0.1) - session/player/match ownership
- map hash
- command kind, required fields, and payload bounds
- active match/player state
- per-connection message rate (token bucket: 40 msg/s sustained, 120 burst,
tunable via
MESSAGE_RATE_PER_SEC/MESSAGE_BURST; sustained floods are dropped, logged, and finally disconnected)
The server then assigns authoritative sequence/order and broadcasts accepted commands to every client. Clients apply only accepted commands in online matches. Clients also send periodic state checksums; if hashes disagree, the server broadcasts a desync warning.
Unsupported player mutations are hidden or honestly disabled and hard-guarded; rejected online input never falls through to a local simulation write. See the command-surface contract.
All public traffic uses native TLS 1.2 or newer. This includes passwords,
account/session/reconnect tokens, lobby passwords, chat, commands, map packages,
and match state. Public startup fails without certificate/key material. Plain
TCP is restricted to explicitly opted-in numeric-loopback development on both
server and client; hostnames (even localhost) and URL-like HTTP prefixes are
rejected rather than silently downgraded. TCP connect and TLS authentication
are asynchronous, independently time-bounded, cancellable, and generation
scoped so a stale completion cannot mutate a newer Unity session. The server
also caps pending pre-auth TLS handshakes independently from authenticated
connections.
The current Unity sim still runs locally on each client, so public play should keep map seeds, factions, teams, and command order identical. A future hardening pass should move more simulation validation into a dedicated authoritative sim process.
Online Flow
- Main menu -> Multiplayer.
- Enter
MULTIPLAYER_SERVER_URL/tls://hostname:portand player name. A bare hostname defaults to TLS; plaintext is available only for explicitly opted-in loopback development. - Connect to the dedicated server; the client and server negotiate the exact
game and
lockstep-0.1protocol versions plus the client's supported-player ceiling. Missing capability is contained to the legacy eight-player limit. - Browse or create lobby.
- Select map, faction, and team.
- Ready up.
- Host starts match.
- Server validates lobby and sends match config.
- Clients verify map hash and build the same procedural match.
- Clients send loading-ready.
- Server releases match start.
- Commands flow through server validation and ordered broadcast.
- Clients report defeat/checksum state.
- Server broadcasts an explicitly unranked result with per-player casual XP awards, or an integrity stop with no result progression.
- Server reopens the lobby (ready flags reset); clients show the post-game OPERATION REPORT and can rematch from the same lobby.
Current Limitations
- Per-player ownership: every online player owns a distinct sim faction —
duplicate nations get deterministic clone ids (usa, usa#2, ...) derived
from the ordered match player list (
MultiplayerOwners), so money, power, production queues, and defeat detection are per-player. Matches support 2-16 players (map spawn count permitting) in FFA or teams, including mirror matchups. Remote players' economies still ride the idle AI-brain money pool — a dedicated per-player wallet is a cleanup item. - In-session drops cannot rejoin in 1.0. Authoritative snapshot/checkpoint recovery plus a real two-client checksum gate is required before this can be re-enabled; lobby and loading-state reconnect remain supported.
- Spectators and AI takeover are intentionally deferred.
- Accounts provide password-bound identity, but current peer-simulated matches remain unranked regardless of identity strength. Ranked play requires an authoritative or independently verifiable result source.