Deterministic lockstep and rollback are usually presented as rivals. They aren't, really — rollback is lockstep with one extra move bolted on. Both send inputs instead of world state, both demand that every client's simulation produce bit-identical results, both fall apart the same way when it doesn't. The only question that actually separates them is what your game does during the milliseconds it is waiting for the other player's input. This is a practical guide to choosing between them, and to the determinism work that decides whether either one survives contact with real players.
The shared foundation: send inputs, not state
In an input-synchronised model the network carries a few bytes per player per tick — a bitfield of buttons, maybe an analogue axis. Every client runs the identical simulation on those inputs and arrives at the identical world. Bandwidth is essentially independent of how much is happening on screen, which is why an RTS with 800 units can run over a modem-era connection while a state-replicating shooter with 20 players needs constant delta compression.
The price is absolute: the simulation must be deterministic. Not "close", not "converges" — the same inputs must produce the same bits on every machine, forever. One divergent bit at tick 400 doesn't cause a small glitch; it causes two players to be playing different games, and the difference compounds until someone dies on one screen and not the other. Everything hard about both models flows from that one requirement.
Lockstep: wait for everyone
Classic deterministic lockstep advances tick N only when the inputs of every player for tick N have arrived. Because it never guesses, it is never wrong — there is no correction, no snapping, no re-simulation. The implementation is small enough to hold in your head, which matters more than people admit.
The cost is input delay. If your opponent is 80 ms away, your own button press cannot be simulated until roughly a round trip has passed, so the game reacts around 80–160 ms after you press. Two mitigations are standard: buffer inputs a fixed number of ticks ahead (schedule input read at tick N for execution at tick N+3), and hide the remainder in animation — unit acknowledgement barks, wind-up frames, a click sound the instant you press. RTS games have been laundering latency through animation for thirty years, and it works.
Lockstep's other weak spot is that one lagging player stalls everyone. Real implementations add a timeout that either drops the straggler or lets the session "slow down" briefly, and that policy needs to exist before launch, not after the first bad connection.
Rollback: guess, then fix
Rollback refuses to wait. At tick N it simulates immediately, using a prediction of remote inputs — almost always "whatever they pressed last frame", which is right most of the time because humans hold buttons for many frames. When the real inputs for tick N arrive at tick N+4, the client compares them with what it predicted. If they match, nothing happens at all. If they don't, it restores the saved state from tick N, applies the true inputs, and re-simulates the four intervening frames before rendering the current one.
That gives local input a zero-latency feel: your character responds on the frame you pressed, because your own inputs are never predicted. The trade is consistency. A mispredicted remote input means the world visibly corrects. Prediction horizons beyond roughly 100–150 ms tend to feel bad, which is why practical rollback systems cap the prediction window (GGPO stops advancing when it hits its limit rather than guessing further ahead) and often reintroduce one or two frames of deliberate input delay — enough to cut misprediction rate substantially at a cost most players cannot perceive.
The three prerequisites rollback adds
Everything lockstep demands, rollback demands too — plus:
- Save and restore of the entire simulation state, cheaply. You will do it
many times a second. If a "save" means allocating objects and deep-cloning a scene graph,
rollback is dead on arrival. State wants to live in flat typed arrays you can copy in one
set()call. - Simulation decoupled from rendering. You must be able to advance a tick without drawing it. If update and draw are tangled, re-simulating four frames means drawing four frames.
- CPU headroom. A worst-case frame is one normal tick plus N re-simulated ticks. If a tick costs 4 ms and your rollback window is 7 frames, your bad frame is ~32 ms — over budget at 60 FPS. Measure the re-simulation cost, not the average.
Those three constraints, not the network code, are what actually decide the answer for most projects.
Determinism in JavaScript and WebAssembly specifically
The classic advice — "floating point isn't deterministic across platforms" — comes from native C++, where compiler flags, x87 vs SSE and fused multiply-add all change results. On the web the picture is better than its reputation, and it pays to know exactly where the line is.
- Basic arithmetic is safe. ECMAScript numbers are IEEE-754 doubles, and
+ - * /andMath.sqrtare exactly specified with round-to-nearest semantics. Every engine produces identical bits. - The
Mathlibrary is not. The spec only recommends fdlibm-quality implementations forsin,cos,tan,exp,pow,logand friends — accuracy is implementation-approximated. Engines have changed these implementations over time. Assume any transcendental is a desync source and replace it with your own table, polynomial or fixed-point routine that ships with the game. - Wasm is deterministic with two footnotes. The core spec pins float behaviour, but NaN bit patterns are explicitly non-deterministic, and relaxed SIMD instructions are allowed to differ between engines. Don't hash raw state that can contain NaN, and keep relaxed SIMD out of simulation code.
- Iteration order and randomness. Seed your own PRNG (and re-seed
deterministically per tick if any subsystem consumes it); never iterate a collection whose
order depends on insertion timing or object identity; never let
Date.now(), frame time or actual delta-time leak into gameplay logic. Fixed tick rate, always. - Third-party physics. Engines legitimately randomise constraint solve order for stability; that alone will desync you. Either fix the seed per tick or use a physics library with a documented determinism mode — and re-test after every upgrade.
The pragmatic escape hatch is fixed-point maths: represent positions and velocities as integers scaled by, say, 216, and you sidestep the entire class of problem. It costs precision and developer comfort, and for cross-platform titles it is still the most common answer.
Build the desync detector first
Whichever model you choose, the single highest-value piece of infrastructure is a per-tick state checksum. Hash the simulation state at the end of every tick (or every eighth tick), exchange the hashes, and halt loudly with the tick number when they diverge. Without it, "the games drifted apart somewhere in the last four minutes" is a week of work. With it, you get "tick 1,842" and a replay you can bisect. Store the input stream too — an input log plus a starting state is a replay file, for free, which is one of the nicer side effects of input-based netcode.
So which one?
A rough decision rule that holds up in practice:
- Rollback when the state is small, the player count is two (or a handful), and precise timing is the game: fighting games, platform fighters, small arena shooters, rhythm-adjacent PvP.
- Lockstep when the state is large, the player count is higher, or actions have natural wind-up: RTS, simulation, tactics, most co-op-with-many-entities designs. Buy back responsiveness with animation and local UI feedback instead of prediction.
- Neither when you have an authoritative server and untrusted clients. Input-based determinism assumes peers you can trust to run the real simulation. For anything where cheating or money is involved, use server-authoritative state replication with client-side prediction and reconciliation — the same prediction idea, but with a server as the single source of truth.
That last bullet is the one people skip. Lockstep and rollback are peer-to-peer trust models wearing a networking hat; they are excellent at bandwidth and terrible at adversaries. Pick the model that matches your threat model first, and the latency question second.