Authoritative Simulation
Last updated: 15 September 2026
Once players are in a room, the server decides what actually happens — where everyone is, who hit whom, what the score is. Doing that on the server, not the client, is what makes a game authoritative and hard to cheat. The room SDK gives you a fixed-step tick loop, a way to compose game logic as sim systems, and snapshots that stream the resulting state out to clients efficiently.
Authoritative simulation is optional. A turn-based or lightly-synchronised game can ignore the tick loop entirely and just use the room roster and messaging. Reach for this when the server needs to advance a continuous world — movement, physics, real-time combat.
The tick loop
A room advances in fixed steps, which keeps behaviour deterministic regardless of how network timing jitters.
OnSimStep(float dt)runs one fixed simulation step.SimStepSecondssets the step size (default 1/30 s).OnTick(float dt)runs once per host tick and drives the stepping, catching up missed steps up toMaxCatchUpSteps(default 6) so a momentary stall doesn't let the world fall permanently behind.
protected override float SimStepSeconds => 1f / 60f; // 60 Hz sim
protected override void OnSimStep(float dt)
{
// advance movement, resolve combat, tick timers — all server-authoritative
}
Catch-up is bounded on purpose: if the server can't keep up, it runs at most a few extra steps per tick and then accepts a slower world rather than spiralling into an ever-growing backlog.
Sim systems
Instead of one giant OnSimStep, you can compose behaviour from sim systems — small units that each do one thing (movement, then collision, then respawn) and run in a defined order. Two shapes exist: a system that runs once for the whole room (ISimSystem) and one that runs per player (IPlayerSimSystem), each tied to a SimPhase so ordering is explicit. Register a per-player system with AddPlayerSystem(...).
This keeps a large game readable: each rule is isolated and testable, and the order they run in is data, not a tangle of calls. The default OnSimStep simply runs your registered systems.
Snapshots
The server holds the truth; clients need a steady, cheap view of it. A snapshot is a projection of room state you declare once and the SDK streams to clients on a schedule.
protected override void Setup()
{
DeclareSnapshot<MySnapshot>(plan =>
{
plan.Group = "room";
plan.Cadence = SnapshotCadence.EveryN(2); // every 2nd tick
plan.Assemble = ctx => BuildSnapshot(ctx); // build from current state
});
}
Cadence controls how often a snapshot goes out — SnapshotCadence.EveryTick for fast action, EveryN(n) to send less often for calmer games or to save bandwidth. Snapshots are separate from the simulation rate, so you can simulate at 60 Hz and broadcast at 20. A snapshot is delivered to clients over a group, and by default also publishes immediately when a relevant event fires (ForcePublishOnEvent).
Adaptive cadence
For rooms whose intensity varies, an AdaptiveCadencePolicy lets the broadcast rate rise and fall with activity — stream frequently during a firefight, back off when the room is quiet — bounded by a WsOpsBudget and clamped between MinHz (default 1) and MaxHz (default 15). A fixed rate can be pinned per environment via PLAYSERV_BROADCAST_HZ. The SnapshotContext gives the plan the tick and any drained events it needs to assemble each frame.
Snapshots describe what to send and how often, not how the client draws it. Interpolation, prediction, and reconciliation live in the client; the server's job is to emit an authoritative, well-paced stream.
Gameplay building blocks
For common mechanics you compose your player or entity type from small role interfaces (a position, a body with velocity, health, an input sequence, and so on), so the built-in systems can operate on any game that opts into them. You don't have to use them — a game with different mechanics defines its own — they're just the vocabulary the shipped systems speak.
Persisting results
When a match ends you usually want something to survive it — a score, a stat, an inventory change. Two declarations bridge the room to your game data: DeclareDataMirror(...) keeps room state mirrored to a table as it changes, and DeclarePersistedCounter(...) accumulates a running value (like a score) and writes it through. Because the server SDK acts as a server subject, these writes aren't subject to client table permissions — see Access Control.
Next steps
- Game Server Hosting — the room and host these simulations run inside
- Working with Game Data — where mirrored state and persisted counters land
- Data Subscriptions — how clients observe data that changes