Learn

Architecture & pattern guides

Read the idea, build it on a Grid, prove it in a challenge. Short, practical guides — illustrated with RodGrid's own nodes — on system design, design patterns, flow modeling, and how to get the most out of the app.

All articles

Multiplayer game state: authoritative servers & tick rates

Keeping one fast-changing world the same for every player over a laggy network: a server that runs the only simulation that counts, inputs instead of results, fixed ticks, client-side prediction and reconciliation, entity interpolation, lag compensation, deltas and interest management, the match held by one process, and lockstep as the alternative.

Ten copies of one world

Ten machines draw the same arena 60 times a second, each sees the others' moves tens of milliseconds late, and any of them may be running modified code. The obvious first design lets each client simulate its own player and report the results, {x: 410, y: 220} every frame and {hit: 7, damage: 40} when a bullet lands, while the server relays them to everyone else.

A modified client then reports {x: 610, y: 455} 16 ms after {x: 12, y: 30}, and the server, whose only truth is what clients say, cannot refuse it. Honest clients disagree too: two players who fire at each other at almost the same moment each see their own shot land first, and both report the kill.

The server plays, the clients ask

An authoritative server runs the one simulation that counts: positions, health, ammo, who hit whom. Clients send inputs, what the player asks to do, such as {seq: 1043, move: 'left', aim: 212, fire: true}, and the server decides what each one produces. A modified client can still press left, but it moves only as fast as the server's code allows, and a hit is whatever the server's hit test says. Unlike a collaborative editor, which merges concurrent edits (Collaborative text editing: OT vs CRDTs), the game merges nothing: one process has the final word.

Inputs still need checking. The server applies at most one movement input per player per tick and drops any beyond a short queue, so sending three to run faster gains nothing, and it refuses a shot during the weapon's cooldown or with an empty magazine. That is server-side validation, and with the server computing every outcome it closes off cheats that claim what the rules forbid, such as a teleport or infinite ammo. It does not stop an aimbot, whose inputs are all valid: it presses the buttons a very good player would.

Sixty steps a second

The server advances the world in fixed steps called ticks. At 60 a second, one every 16.7 ms, each tick takes each player's next queued input, applies it, moves projectiles, resolves hits and records the new state. This match sends an update every third tick, 20 a second. Both rates are trade-offs: a higher tick rate applies inputs sooner and samples fast movement more finely, at a CPU cost per match, and a higher send rate costs bandwidth for every player. Each tick must also finish inside its 16.7 ms, or the match falls behind real time.

Eighty milliseconds before anything moves

Authority costs the player. Over a connection with an 80 ms round trip, a key press travels about 40 ms to the server, waits up to 50 ms for the tick that applies it and the update that carries it, and travels 40 ms back: roughly 80 to 130 ms before the player's own avatar moves, a delay players notice.

So the client does not wait. It runs the same movement code as the server and applies its player's inputs to its own copy at once: client-side prediction. Usually the server, given the same inputs, computes the same position. When it does not, the client gives way, which is reconciliation:

  1. The client applies input 1043, move left, to its own avatar, draws it, sends 1043 to the server and keeps it in a list of unacknowledged inputs, then does the same with 1044 and 1045.
  2. The server applies 1043 at its next tick, together with a rocket blast the client has not heard about yet, which pushes the player 2 metres right.
  3. The server's next update carries the player's position and the number of the last input it applied, 1043.
  4. The client resets its avatar to that position, drops 1043 from the list, and re-applies 1044 and 1045 on top of it.
  5. The client draws the result, easing the avatar there over a few frames when the correction is small.

Step 4 is the one that matters: snapping to the server's position alone would pull the player back about a round trip on every update.

Everyone else, a little in the past

The client cannot predict the other nine players reliably, because it does not know their inputs, and drawing each where the latest update put it would make them jump every 50 ms. It uses entity interpolation instead: it keeps a render clock running steadily about 100 ms behind the server time the updates carry, and draws each of the others between the two updates on either side of that clock, so one missing update still leaves a pair. The smoothness is paid for in delay, on purpose: a player sees an opponent where they were about 140 ms ago, 100 ms of interpolation plus 40 ms of travel.

The shot that should have hit

A player puts the crosshair on an opponent crossing a doorway and fires a weapon whose shot lands instantly; on screen it is a clean hit. On the server, by the time the input arrives, the opponent is behind the wall. Judged against the server's present, the shooter would have to aim ahead of every moving target by the target's display delay plus the input's trip to the server, about 140 + 40 = 180 ms here. Many shooters answer with lag compensation, judging a shot against the world as the shooter saw it:

  1. The server keeps every player's position for each of its last 15 ticks, about a quarter of a second of history.
  2. The shooter's fire input carries the time of the moment its screen was showing, which the client knows from the updates it was interpolating between.
  3. The server rewinds the other players to that time, but never more than 200 ms back.
  4. The server tests the shot against the rewound positions and finds the opponent in the doorway.
  5. The server restores the present positions and applies the damage.

The cost lands on the target, who on their own screen had made it behind the wall: shot through cover. The cap bounds that, and how far back a lying client can reach; a server can also check the claimed time against the latency it measures for that client. Lag compensation sides with the shooter, and not every game makes that choice.

What each client is sent

A full snapshot of ten players, 40 projectiles and the pickups, say 2 KB, sent 20 times a second to ten players is 400 KB/s out of one match. A delta sends only what changed since a state the client is known to hold. The updates usually ride UDP in native games and WebSocket or WebRTC data channels in browsers; what UDP gives up next to TCP is in Network protocols: TCP, UDP, HTTP/2 & HTTP/3, and why realtime media accepts it in Realtime media: WebRTC, SFUs & NAT traversal.

Interest management sends each player only what it can see or affect: entities within view distance, plus a margin so they do not pop in. It also limits what a cheating client can learn, since a wallhack cannot draw an enemy whose position never arrived. Anyone inside the margin still arrives, behind a wall or not, unless the server also checks line of sight.

One process holds the match

Every tick reads and changes the whole world, 60 times a second, which is simplest when that world sits in the memory of the process running it. So in this arena one process holds each match, and every player's connection must reach it; how a gateway finds that process and routes each socket there is WebSockets at scale: gateways, routing & draining.

Ten players, one match process

Inputs go up through the gateway and each player's deltas come back down. The match lives in the server's memory; only the result is stored.

Two consequences follow for the match. A player who drops and reconnects within a grace period, say 60 seconds, finds their avatar still in the match and gets a full snapshot first, since the server cannot know which state the new connection holds, then deltas again. And if the process crashes, the match usually ends with it: what survives is what was written outside, such as the result recorded at the end.

The other road: lockstep

Strategy games with thousands of units, too much state to send 20 times a second, often use deterministic lockstep instead: every machine runs the full simulation, players exchange only inputs, and a turn advances once every player's inputs for it have arrived. It needs little bandwidth, but the simulation must give bit-identical results on every machine, the slowest connection sets everyone's pace, and every machine holds the whole world, hidden parts included, where a cheater can read them.

Twenty nodes for a whole game

Multiplayer Game State Sync asks for this design in up to 20 nodes, with matchmaking and an autoscaled fleet (Load balancing & horizontal scaling) around it. Draw the game server as a Go Service per match with its tick rate on it, label the input path and the per-player delta path, and place validation on the game server, not the gateway. Then test the finished Grid against the opening: which node refuses {x: 610, y: 455}, and which decides whose shot landed first?

Practice this