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
The ring: confirmed up to the last authoritative tick, predicted from there to the present.

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.

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
}
Commands file into a tick's inputs; authoritative state patches a tick's snapshot.

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
}
Reconstruction is the shared tick function, run from the last confirmed snapshot forward.

How far reconstruction is exact depends on which inputs the front end holds.

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.

Display timing A timeline split into a confirmed region up to the last authoritative tick and a predicted region up to the present. Your own player is displayed at the present, other players a little behind at the frontier of the commands you hold, and sent state at the last authoritative value. CONFIRMED PREDICTED confirmed tick present you at the present other players behind, at the last command heard sent state chased, at the last authoritative value
Each thing is displayed at the time its information supports: you at the present, others and sent state behind.

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.