Frontend
The front end is the instance run on the client. It keeps the same ring of ticks, runs the same pure tick function, and works over its own view of the world. The server owns the truth. The front end reconstructs as much of it as the information it holds allows, and shows each thing at the moment that information supports. This page is the whole client half: the ring it keeps, the commands and state it receives, how it runs the world forward, and how it displays and reconciles the result.
The ring
The front end holds the same ring the server does: a
circular buffer of tick objects, each a number, a snapshot
and the inputs for that tick. It is not a single latest view.
It is a stretch of recent history the front end can replay, exactly as the server can. That is what lets
prediction and reconciliation be the ordinary tick function, run over the ring, rather than a system of their
own.
The ring has two regions. The confirmed region runs up to the last tick the server has made authoritative. The predicted region runs from there to the present, the tick the player is on now, and the front end fills it by running the tick function forward.
confirmed region predicted region
├───────────────────────────┤ ├───────────────┤
oldest confirmed through present
the server has spoken reconstructed forward from there
Commands and state
Everything the server sends is one of two kinds, and each has a home in the ring. The split is the whole idea: commands reconstruct, state is chased.
- A command is a timestamped input: a relayed public command of a player
relevant to you, or one of your own. It files into the
inputsof the tick it was made on. Commands drive reconstruction: the tick function reads them and reproduces the exact trajectory they cause. - A piece of state is an authoritative value the front end cannot reconstruct: a
sentfield, an occasional keyframe that re-anchors asharedfield, or anenterorleaveas something crosses your view. It patches thesnapshotof the tick it belongs to and moves the confirmed frontier forward.
onCommand(tick, player, command) { // a relayed command, or your own
ring.tick(tick).inputs.push({ player, command })
}
onState(tick, patch) { // sent fields, a keyframe, enter or leave
apply(ring.tick(tick).snapshot, patch)
confirmedThrough = tick // the server has spoken up to here
}
Your own commands enter the instant the player makes them, filed at the tick they were made on, long before the server confirms them. That is what lets the front end reconstruct your own player with no delay. The exact wire shape of both kinds is the protocol.
Reconstructing forward
From the last confirmed snapshot the front end runs the tick function forward to the present, feeding each tick its inputs. The snapshot it lands on is the reconstructed present, and that is what the display draws from.
function reconstruct(from, to) {
let snapshot = ring.tick(from).snapshot
for (let t = from + 1; t <= to; t++) {
snapshot = tick({ number: t, snapshot, inputs: ring.tick(t).inputs })
}
return snapshot
}
How far reconstruction is exact depends on which inputs the front end holds.
- Your own player. You hold every command up to the present, so your own state reconstructs all the way to the present exactly. No delay.
- A deterministic thing, like a rock. Its motion reads no command at all, so it reconstructs
to the present for free. Nothing about it needs to cross the wire but its
enterandleave. - Another player. You hold their commands only up to the last one that arrived. Past that frontier the next step would need a command you do not have, so reconstruction stops being exact there.
What reconstructs, what is chased
Which of the two kinds carries a field is fixed by its kind of state. A
shared field is reconstructed by re-running the commands. A sent field is chased as an
authoritative value. A server field never reaches the client at all.
The line is kept honest by one rule: a field may be shared only when every viewer holds all of its
inputs. Anything whose inputs could be hidden is sent, and arrives as a known value rather than a
guess. And when the server's own private logic touches a shared field, the server sends its
authoritative value too, because it knows the client could not have reconstructed that tick. So the front end
is corrected exactly where, and only where, it could not have known. This is the same authoritative state as
any other, folded in the same way.
Display timing
The ring is the simulation. The display is the separate thing the player actually sees, and it shows each object at the time that object's information supports, not all at one instant.
You and deterministic things are shown at the present. Reconstruction reaches the present for them without a guess, so there is no reason to hold them back, and your own input shows with no delay. Other players are shown a little behind, at the frontier of the commands you hold, so you play their reconstructed motion back rather than guess past it. Sent state is shown behind too, eased toward each authoritative value as it lands.
The display never jumps. Each frame it eases every continuous value toward its target, and enter
and leave fire on the display's own clock, so a thing appears at the moment it really appeared in
the world rather than the moment its packet arrived.
How far behind the world is shown is one knob, the display delay
(gamehoster-game-displayDelay). A larger delay keeps the display inside the confirmed region, so it
interpolates between known states and never guesses. This suits a game whose motion is hard to predict, or where
a little latency is easy to hide. A delay near zero shows the reconstructed present. This suits smooth,
predictable motion where immediacy matters. Whatever the world's delay, your own player stays pinned to the
present. Chasing what you know and extrapolating to the present are the same ring sampled at a different delay,
so a game picks a point on that dial rather than a different system.
Reconciling
Every authoritative message moves the confirmed frontier forward, and its value may disagree with what the front end reconstructed for that tick. The front end does not patch the old reconstruction. It replays the ring from the changed tick forward, which is the same rewind the server runs when a late command lands.
Replaying your own commands over the authoritative snapshot reproduces exactly what you already predicted for yourself, because it is the same tick function over the same commands on the same ticks. So your own player lands where the prediction already had it, with nothing to correct. Other players can shift, because their predicted tail was the front end's guess and the real commands have now arrived. That shift is handed to the display, which eases it in over a few frames rather than snapping.
Your own player reconciling to itself is not luck. The front end tags each command with the tick it applied it on, and the server, by rewinding, applies it on that same tick, so the two build your player from the same command on the same tick and agree.
The server closes the time gap
Showing yourself at the present while the world trails means you act against a world drawn slightly in the past. You do not need special code to make that fair. The command you send carries the tick you acted on, and the server rewinds to that tick to apply it, so the authority judges your action against the world you actually saw. The lag compensation falls out of the command's tick and the rewind that the model already has.
One machine, run twice
That is the whole of the client. One tick function, one ring, one snapshot shape, the same as the server's. The front end runs it to reconstruct the present from the commands it holds, chases authoritative state for the parts it cannot reconstruct, and shows each thing at the time its information allows. Prediction is not a system bolted on. It is the instance, run on the client and displayed at the right moment.