Delay-based and rollback netcode
How real-time multiplayer hides network latency: delay-based input buffering versus predict-and-rewind rollback, compared from first principles
Delay-based and rollback are not different technologies. They are two strategies that sit on the same deterministic foundation and diverge on one question: wait for remote input, or predict it.
Why netcode exists
The central difficulty in real-time multiplayer is state synchronization, making several machines see the same game world. It is hard because the network is unreliable. Information that is sent can arrive late, arrive out of order, or disappear.
Separating three defects makes the rest of the discussion easier. Latency is the time a packet takes to travel from one machine to another, loss is a packet vanishing under UDP, and jitter is the variance in latency. Glenn Fiedler puts it this way: the internet does not guarantee that packets sent 60 times per second arrive neatly spaced 1/60 of a second apart (gafferongames). Even with identical average ping, uneven arrival intervals make the screen look choppy.
Lockstep, which sends only input, and determinism
Early peer-to-peer games exchanged each player's input rather than the full game state. That is lockstep. Doom in 1993 sent input to every peer once per tic (roughly 28.57ms) and advanced the tic only after every input had arrived.
Sending only input still requires one precondition for every machine to see the same screen. Given the same starting state and the same inputs, every machine must produce bit-for-bit identical results. That is determinism. Break this principle and a tiny divergence on one machine grows over time into a full desync.
With determinism in place, the network only has to carry input, so bandwidth scales with input size rather than object count. That is why real-time strategy (RTS) games with thousands of units still use this model.
Everything up to this point is common ground for both approaches. The game runs at a fixed frame rate (fighting games at 60fps, one frame is about 16.67ms), the simulation is deterministic, and only input travels over the network. There is exactly one fork in the road: what to do while remote input has not arrived yet.
The diagram shows the decision point of both strategies at a glance. Delay-based waits; rollback predicts and rewinds only when the prediction was wrong.
Delay-based netcode
Delay-based netcode is deterministic lockstep with an input delay buffer on top. The key move is deliberately delaying even your own input by a few frames. Committing to execute local input D frames later gives the input packet those D frames to reach the opponent.
The result is that both simulations advance one frame at a time only when both inputs are present. There is no guessing and no rewinding, so both sides always hold identical state. The number of delay frames comes from dividing one-way latency by the frame time.
At 90ms ping, one way averages 45ms, and 45ms divided by 16.67ms gives roughly 3 frames of input delay (jy-h example). One or two extra frames are often added to absorb jitter.
The advantages are clear. No state saving is needed, so the implementation is simple, determinism is the only precondition, and there are no visual artifacts. Age of Empires synchronized 1500 units over a 28.8k modem by transmitting commands instead of unit state (Bettner and Terrano, GDC 2001).
Every drawback follows from one synchronous constraint: nothing advances until all input has arrived. Higher ping translates directly into felt input lag, a late or lost packet freezes the entire screen (stutter), and overall pace is bound to the slowest peer. In fighting games, where combos live and die on frame-level timing, that input lag is fatal.
Rollback netcode
Rollback advances the simulation by predicting remote input that has not arrived. This example uses zero local input delay and repeats the last remote input. Fighting game input does not change every frame, which makes this simple assumption correct most of the time. Four pieces work together.
- Input prediction: before opponent input arrives, advance by assuming the previous input. It predicts no action only when the previous input was neutral.
- State snapshot: record the entire game state on every frame. This is what the
save_game_statecallback in GGPO (Good Game Peace Out) copies wholesale. It is the concrete thing people mean by a "rollback record". - Input ring buffer: keep past inputs and snapshots in a circular buffer fixed to the rollback window size. Capacity must cover the rollback window and inputs that remain unconfirmed.
- Re-simulation: when the real input differs from the prediction, rewind to the snapshot of the most recent frame that still matched and replay forward to the current frame with the real input. When the prediction was correct, nothing happens.
Implementation
The following deterministic simulation, whose entire state is a single integer position, reproduces prediction, rollback, and re-simulation. Opponent input arrives delay frames late, and until then the last confirmed input serves as the prediction. When the prediction misses, the code rewinds to the snapshot and replays.
NEUTRAL = 0
def step(pos, p1, p2):
return pos + p1 + p2
def run(frames, p1, p2, delay):
if frames < 0 or delay < 0 or len(p1) != frames or len(p2) != frames:
raise ValueError("invalid frame count, input length, or delay")
snap = [0] * frames
used_p2 = [None] * frames
pos = 0
confirmed = NEUTRAL
rollbacks = 0
# Finish receiving inputs even after simulation stops advancing.
for f in range(frames + delay):
arrived = f - delay
simulated = min(f, frames)
if 0 <= arrived < frames:
confirmed = p2[arrived]
if arrived < simulated and used_p2[arrived] != confirmed:
rollbacks += 1
pos = snap[arrived]
for g in range(arrived, simulated):
snap[g] = pos
used_p2[g] = confirmed
pos = step(pos, p1[g], confirmed)
if f < frames:
snap[f] = pos
used_p2[f] = confirmed
pos = step(pos, p1[f], confirmed)
return pos, rollbacks
p1 = [1, 1, 1, 1, 1, 0, 0]
p2 = [0, 0, 1, 1, 0, 0, 0]
assert run(7, p1, p2, 0) == (7, 0)
assert run(3, [0, 0, 0], [0, 0, 1], 2)[0] == 1
print(run(7, p1, p2, 2))frames is the number of simulation frames; p1 and p2 contain their inputs. delay is the arrival delay of remote input, with no loss or reordering in this example.
The simulation must receive pending inputs after the last frame before its state is final. Running the Python code prints (final position, rollback count) as follows.
(7, 2)Opponent input arrives two frames late, so the prediction misses twice, and each time the simulation rewinds to that frame and replays two frames. Once every input is confirmed, the final state (7) matches bit for bit the correct answer with no delay at all (7). That convergence under determinism is the correctness argument for rollback.
| Record | Meaning | Storage in this example |
|---|---|---|
snap[f] | State immediately before frame f | Every simulation frame |
used_p2[f] | Remote input actually applied | Every simulation frame |
confirmed | Most recently arrived remote input | One value |
The example uses fixed-length arrays to show the structure. A real-time implementation discards confirmed records that are no longer needed and reuses bounded buffers.
Cost and how it feels
Every re-simulation must finish inside one frame of display budget (60Hz means 16.66ms). NetherRealm's talk title "8 Frames in 16ms" refers to this budget. Rewinding 7 frames means handling 7 lightweight simulations and 1 render inside a single frame (Stallone, GDC 2018). Exceeding the budget leads to a spiral of death, where the next frame has even more to rewind.
Low configured input delay can bring responsiveness close to offline play. However, the GGPO API provides ggpo_set_frame_delay to configure input delay. Rollback does not necessarily mean zero delay.
A failed prediction can change every part of state that depended on that input. Input arriving beyond the stored rollback window can force a stall, and correction size depends on the game and network conditions.
GPU particles and sound can produce duplicate effects during replay. Presentation that does not affect game results should be separated from simulation, with replay tracked explicitly.
Comparing the two approaches
| Axis | delay-based | rollback |
|---|---|---|
| Waiting for remote input | Wait until it arrives (local input delayed by D frames too) | Predict and advance immediately, re-simulate when wrong |
| Local responsiveness | Degrades in proportion to ping | Configurable input delay |
| State on the two clients | Always identical every frame | May differ while predicting |
| Visual artifacts | None | A pop when the prediction misses (usually minor) |
| Under packet delay or loss | The whole screen freezes | State correction; may stall beyond the rollback window |
| Required data structures | Input buffer only | State snapshots plus input ring buffer |
| CPU cost | Low | High (re-simulation, risk of a spiral) |
| Best fit | RTS, low-latency LAN | Fighting games, internet play with variable ping |
The tradeoff fits in one line. Delay-based sacrifices responsiveness for consistency; rollback sacrifices momentary consistency for responsiveness.
Where these are used
Netcode is not a fighting game concern alone. The field splits into roughly three families according to who owns the truth.
The peer-to-peer deterministic family (rollback in fighting games, lockstep in RTS) uses determinism itself as the synchronization mechanism, with no central server. In fighting games, rollback became the de facto standard when COVID-19 moved tournaments online in 2020. Guilty Gear Strive (2021), Street Fighter 6 (2023), and Tekken 8 (2024) all shipped with rollback. RTS games keep lockstep because thousands of units make state snapshots impractical.
The authoritative server family (FPS, MOBA) works differently. A first-person shooter (FPS) keeps the single source of truth on the server, does not trust clients, and needs no determinism across machines. It layers client-side prediction, server reconciliation, lag compensation, and entity interpolation instead (Valve Source documentation). This prediction resembles rollback, but an FPS predicts only the local player and simply corrects toward whatever the server confirms.
The variables that decide the choice are player count, the size of the synchronized unit, twitch responsiveness requirements, and the cheating model. Fighting games pick rollback because they have two players and a small state, and RTS games pick lockstep because they have thousands of units. FPS games pick an authoritative server because of prize-money competition and anti-cheat.
The walls in a hand-rolled implementation
GGPO's three preconditions, full determinism, serializable state, and save/load/advance of one frame without rendering, each split into a separate hard problem.
Determinism is the deepest wall. Floating-point arithmetic varies subtly across compilers and CPUs and produces desyncs. That is why many deterministic games drop floating point and rewrite the simulation in fixed point. The internal state of the random number generator (RNG) also has to be part of the saved state, so that rewinding the same frame several times reproduces the same random sequence.
State serialization is a data structure design problem. The ideal shape places the entire game state contiguously in one large struct, so saving and restoring is a single copy. Any dynamic allocation means pointers have to become indices. When a full snapshot every tick is too expensive, incremental rollback that stores only the delta reduces the cost.
Because these preconditions permeate the whole codebase, retrofitting them later is extremely hard. NetherRealm back-ported rollback into Mortal Kombat X over roughly 10 months at a cost of 8 man-years, 2 of them on serialization alone. In contrast, Mike Zaimont of Skullgirls, which was designed around GGPO from the start, integrated it in about two weeks (Stallone, GDC 2018). The same feature costs either two weeks or eight man-years depending on when it entered the design.
Summary
Delay-based and rollback netcode wait for or predict remote input in a deterministic simulation. Rollback replays stored state and inputs when a prediction fails, and the final state agrees only after every input is confirmed. Input-delay settings and the rollback window affect responsiveness and replay cost. Choosing an implementation requires considering player count, state size, determinism, and anti-cheat requirements together.