jongkwan.dev
Development · Essay №100

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

Jongkwan Lee2026년 6월 29일11 min read
Contents

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 applies local input immediately with zero delay and, for opponent input that has not arrived, assumes the previous input simply repeats, so the game never stops. 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. The standard prediction is "hold whatever they were doing".
  • State snapshot: record the entire game state on every frame. This is what the save_game_state callback 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. NetherRealm supports 7 frames and therefore uses a 7-slot ring buffer (Stallone, GDC 2018).
  • 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.

python
NEUTRAL = 0
 
 
def step(pos, p1, p2):
    """Deterministic single-frame advance. The same (pos, p1, p2) always yields the same result."""
    return pos + p1 + p2
 
 
def run(frames, p1, p2, delay):
    snap = [0] * frames          # state right before entering each frame = rollback record (snapshot)
    used_p2 = [None] * frames    # P2 input applied on each frame = input ring buffer
    pos = 0
    confirmed = NEUTRAL          # most recently arrived P2 input = basis for prediction
    rollbacks = 0
 
    for f in range(frames):
        arrived = f - delay      # the P2 input from `delay` frames ago arrives now
        if arrived >= 0 and used_p2[arrived] != p2[arrived]:
            rollbacks += 1
            pos = snap[arrived]                       # restore the snapshot
            confirmed = p2[arrived]
            for g in range(arrived, f):               # re-simulate from the confirmed frame up to the previous one
                snap[g] = pos
                pg = p2[g] if g == arrived else confirmed
                used_p2[g] = pg
                pos = step(pos, p1[g], pg)
        elif arrived >= 0:
            confirmed = p2[arrived]
 
        snap[f] = pos                                 # the current frame advances on prediction (zero delay)
        used_p2[f] = confirmed
        pos = step(pos, p1[f], confirmed)
 
    return pos, rollbacks

Running it with p1 = [+1,+1,+1,+1,+1,0,0], p2 = [0,0,+1,+1,0,0,0], and delay = 2 produces the following output.

text
f4: P2[f2] arrived=+1 != predicted=+0 -> roll back to f2 and re-simulate 2 frames
f6: P2[f4] arrived=+0 != predicted=+1 -> roll back to f4 and re-simulate 2 frames
rollback count = 2
rollback final pos = 7
correct (no delay) pos = 7

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.

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.

From the player's side, input responsiveness is nearly identical to offline play because local delay is zero. The cost is a momentary correction (a pop) when a prediction misses badly. In practice most matches stay within one or two frames of rollback, so visual artifacts are rare (Stallone). Non-deterministic elements such as GPU particles and sound produce different results when replayed eight times. As long as they do not affect the game outcome, they are excluded from re-simulation and cached.

Comparing the two approaches

Axisdelay-basedrollback
Waiting for remote inputWait until it arrives (local input delayed by D frames too)Predict and advance immediately, re-simulate when wrong
Local responsivenessDegrades in proportion to pingAlways zero delay
State on the two clientsAlways identical every frameMay differ while predicting
Visual artifactsNoneA pop when the prediction misses (usually minor)
Under packet delay or lossThe whole screen freezesOnly the remote character glitches slightly
Required data structuresInput buffer onlyState snapshots plus input ring buffer
CPU costLowHigh (re-simulation, risk of a spiral)
Best fitRTS, low-latency LANFighting 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 are two strategies on the same foundation of fixed frame rate, determinism, and input transmission. They diverge on whether to wait for remote input or predict it. Delay-based keeps both sides in identical state at the price of delaying input by the ping. Rollback keeps local responsiveness at zero delay at the price of rewinding and replaying whenever a prediction misses. The "rollback record" people refer to is the state snapshot plus input ring buffer that rollback maintains, and delay-based has no such record.

It also matters that different genres solve the same real-time synchronization problem differently. Fighting games use rollback, RTS games use lockstep, and FPS and MOBA games layer prediction, reconciliation, and lag compensation on an authoritative server. Player count, state size, responsiveness requirements, and the cheating model decide the choice. Ultimately, whether to use rollback reduces to an architectural question rather than a genre one. Is the state small, and can determinism and serialization be built in from the beginning?