Waiting for the server to confirm every move would feel awful. Client-side prediction makes your own actions instant, and server reconciliation corrects any divergence without a visible jerk.
With an authoritative server, if the client did nothing until the server confirmed a move, every action would lag by a full round trip — press forward, wait 100ms, then move. That feels terrible. Client-side prediction fixes it: the client applies its own input immediately using the same simulation the server runs, so movement feels instant while the server remains the source of truth in the background.
Without prediction (naive authoritative):
press W -> send to server -> server simulates -> sends state -> you move.
that's a full ROUND TRIP of delay (e.g. 100ms) before your OWN character moves.
-> feels sluggish + awful, especially for movement + aiming.
CLIENT-SIDE PREDICTION:
the client runs the SAME simulation as the server. On input, it moves you
IMMEDIATELY (predicts the result) while also sending the input to the server.
-> your own actions feel instant; the server stays authoritative underneath.
The catch: your prediction might disagree with the server -> reconciliation (next).To predict, the client stamps each input with a sequence number, applies it to its local copy immediately, and stores it in a buffer of unacknowledged inputs. The character moves right away. The client keeps sending these inputs to the server and remembers them until the server confirms it has processed up to a given input number — the data needed to correct any mistake in the next step.
class PredictingClient:
def __init__(self):
self.seq = 0
self.pending = [] # unacknowledged inputs
self.pos = Vec2(0, 0)
def on_input(self, cmd):
self.seq += 1
cmd.seq = self.seq
self.pos = simulate(self.pos, cmd) # APPLY LOCALLY NOW -> instant feel
self.pending.append(cmd) # remember it until the server acks
send_to_server(cmd) # same input goes to the authority
# the character moves immediately using the SAME simulate() the server runs.
# we keep every input until the server confirms it, so we can correct later.The server periodically tells the client "I have processed your inputs up to sequence N, and here is your authoritative position." The client snaps to that position, then replays every still-pending input (those after N) on top of it. If its prediction was right, nothing visibly changes; if it was wrong (it hit a wall the client missed), it smoothly corrects. This replay is what keeps prediction accurate without ever un-doing correct guesses.
def on_server_update(client, authoritative_pos, last_processed_seq):
# 1) trust the server: snap to its authoritative position
client.pos = authoritative_pos
# 2) drop inputs the server has already applied
client.pending = [c for c in client.pending if c.seq > last_processed_seq]
# 3) RE-APPLY the still-unacknowledged inputs on top of the server state
for cmd in client.pending:
client.pos = simulate(client.pos, cmd) # same simulate() as the server
# if prediction was correct, the replayed result == what you already showed
# (no visible change). if it was wrong, you smoothly converge to the truth.
# this is "prediction + reconciliation" — the heart of responsive netcode.When reconciliation does move the player — a misprediction — snapping instantly looks like a jitter or rubber-band. Instead you blend to the corrected position over a few frames (an error-smoothing lerp), so the player barely notices. The art is correcting toward the authoritative truth quickly enough to stay accurate but smoothly enough to feel good — the same "game feel" tuning that runs through all of game development.
def render_position(client, dt):
# don't SNAP on a misprediction — blend the visual toward the corrected pos
error = client.corrected_pos - client.visual_pos
if error.length() < 0.01:
client.visual_pos = client.corrected_pos
else:
client.visual_pos += error * min(1.0, 10.0 * dt) # smooth over a few frames
return client.visual_pos
# instant snaps read as jitter / rubber-banding. blend the ERROR out over a few
# frames so corrections are barely visible. tune the rate: fast = accurate,
# slow = smooth. (a big misprediction may still need a hard snap.)