Quick answer: Rollback netcode predicts remote inputs and resimulates from a confirmed frame when the real inputs arrive, which demands a perfectly deterministic simulation. A bug is usually a desync: two clients resimulate the same inputs and reach different states because something was non-deterministic or unsynchronized. To debug it you need per-frame state hashes, the full input log, and the confirmed frame the rollback started from on each client.

Rollback netcode makes fast games feel offline-smooth by predicting remote inputs and resimulating instantly when the real inputs differ. It is the gold standard for fighting games and other latency-sensitive titles, and it is utterly unforgiving: the entire scheme rests on every client simulating the same inputs to bit-identical results. The moment determinism breaks, clients desync, and the symptom can be anything from a small visual glitch to two players watching completely different matches. These bugs are notoriously hard because the divergence often happens frames before anyone notices. This post covers the state hashes and input logs you need to capture so a desync becomes traceable instead of mythical.

Why rollback demands determinism

Rollback works by saving a confirmed game state, predicting the inputs of remote players, and simulating forward. When the real remote inputs arrive and differ from the prediction, the client rolls back to the last confirmed frame and resimulates every frame since using the corrected inputs. For this to be invisible, the resimulation must produce exactly the same result that the original simulation would have, given the same inputs. Any source of non-determinism, a floating-point difference across CPUs, an uninitialized variable, an iteration over an unordered container, breaks that guarantee and the clients drift apart.

The cruelty of rollback bugs is that the inputs are identical on both clients but the outcome is not, so the usual instinct to look for a different input is a dead end. The divergence is internal: the same code fed the same data produced two answers. This is why a desync report that only contains what the player saw is nearly worthless. You need evidence of where the two simulations stopped agreeing, and that evidence has to come from inside the simulation, not from the screen.

State hashes pinpoint the divergent frame

The single most powerful tool for rollback debugging is a per-frame hash of the simulation state. Each client computes a checksum of all gameplay state every frame and the clients exchange or log these hashes. When two clients disagree, you binary-search the hashes to find the exact frame where they first differed. That frame is the scene of the crime, and narrowing the search from an entire match to one frame is the difference between a fixable bug and a permanent mystery in your tracker.

Make the hash cover everything that affects the simulation and nothing that does not. Include positions, velocities, timers, random number generator state, and entity flags; exclude purely cosmetic state like particle systems unless they feed back into gameplay. When a report captures the local hash stream alongside the remote one, you can immediately see the divergence frame without needing both players present. Granular per-subsystem hashes go further, telling you whether the physics, the RNG, or the game logic was the first to disagree, which often points straight at the offending code.

The input log is the reproduction recipe

If your simulation is truly deterministic, the complete input log plus the initial state is a perfect reproduction: replay those inputs from that state and you must get the same desync every time. This is the most valuable thing a rollback bug report can contain, because it turns an intermittent online failure into a deterministic offline test you can run in a debugger. Capture every player's inputs with their frame numbers, plus the confirmed frame each client had reached and the seed for any randomness.

The input log also exposes rollback-specific bugs that are not determinism failures at all, like an input applied on the wrong frame, a dropped input that left a hole, or a prediction window that grew too large under latency. Seeing the frames where predictions were wrong and how many frames each rollback spanned tells you whether the system is operating within its design envelope. A rollback that routinely spans twenty frames is a connection-quality problem masquerading as a netcode bug, and the log makes that obvious at a glance.

Confirmed frames, latency, and rollback depth

Each client tracks the most recent frame for which it has all real inputs, the confirmed frame, and everything after it is predicted. The gap between the confirmed frame and the current frame is the rollback depth, and it grows with latency. Reports should capture this depth at the time of the bug, because many issues only appear under deep rollback: a state-save that misses a field is harmless at depth one but corrupts the resim at depth ten when that field finally matters. The depth tells you how stressed the system was.

Latency and packet loss drive rollback depth, so include the round-trip time and recent loss alongside it. A desync that only happens at high depth under loss is a different bug from one that happens at depth two on a clean connection, and they live in different parts of the code. Capturing the confirmed frames from both clients also reveals frame-advantage problems, where one client is consistently ahead of the other and the time-sync logic, not the simulation, is the culprit. These distinctions save you from fixing the wrong layer.

Setting it up with Bugnet

Bugnet's in-game report button captures game state at the moment of failure, which for rollback means you can serialize the desync evidence while it is still fresh: the recent per-frame state hashes, the full input log with frame numbers, the confirmed frame on each side, the current rollback depth, and the RNG seed. Add player attributes for build version and platform, since determinism bugs are often platform-specific floating-point differences. One report then contains everything needed to replay the divergence offline instead of a clip of two characters glitching.

Determinism bugs reproduce across many matches once they exist, so Bugnet's occurrence grouping folds duplicate desyncs into one issue with a count, letting you see that a specific cross-platform pairing desyncs repeatedly rather than logging each as a separate ghost. Use custom fields for divergence frame, rollback depth, and platform pair, then filter to find whether desyncs cluster between two specific platforms, the classic sign of a floating-point or container-ordering bug. One dashboard turns the dreaded intermittent desync into a ranked, reproducible list.

Building determinism into your process

The teams that ship reliable rollback bake determinism checking into their workflow rather than bolting it on after a desync. Run automated tests that simulate the same input log on multiple platforms and assert the state hashes match frame for frame; this catches a non-deterministic change the day it lands instead of weeks later in a player report. A fixed-point or carefully controlled floating-point math layer, deterministic container iteration, and a single seeded RNG are the foundations that make these tests pass.

Treat any hash mismatch, in testing or in the field, as a release blocker, because a desync is not a degraded experience but a broken one. Keep the captured input logs as a growing regression corpus and replay them on every build. Over time this corpus becomes your strongest defense: a single deterministic replay that reproduces a historical desync will catch the day someone reintroduces the same class of bug. Rollback rewards rigor more than almost any other system, and the rigor pays back as matches that simply stay in sync.

Rollback desyncs are determinism failures. Hash every frame, log every input, and the divergence becomes a deterministic replay instead of a ghost.