Skip to main content
Deterministic Multiplayer Architecture

Deterministic Multiplayer Architecture: Northfield's Advanced Lockstep Methods

Who Needs This and What Goes Wrong Without It If you are building a real-time strategy game, a competitive fighting game, or any multiplayer simulation where every client must see identical outcomes from the same inputs, you are in the right place. Basic lockstep—where each client broadcasts its inputs and runs the same simulation—works for small games with deterministic physics, but breaks down under real-world conditions: network jitter, floating-point differences across CPU architectures, and subtle non-determinism in third-party libraries. Without a rigorous deterministic architecture, you get desyncs. A desync occurs when two clients compute different game states from the same input sequence. The symptom is often a sudden mismatch error, forcing a rollback or disconnection. In a real-time strategy game with hundreds of units, even a single desync can ruin a 30-minute match.

Who Needs This and What Goes Wrong Without It

If you are building a real-time strategy game, a competitive fighting game, or any multiplayer simulation where every client must see identical outcomes from the same inputs, you are in the right place. Basic lockstep—where each client broadcasts its inputs and runs the same simulation—works for small games with deterministic physics, but breaks down under real-world conditions: network jitter, floating-point differences across CPU architectures, and subtle non-determinism in third-party libraries.

Without a rigorous deterministic architecture, you get desyncs. A desync occurs when two clients compute different game states from the same input sequence. The symptom is often a sudden mismatch error, forcing a rollback or disconnection. In a real-time strategy game with hundreds of units, even a single desync can ruin a 30-minute match. Many teams first encounter this when they port their game from a single-player prototype to multiplayer: the prototype runs fine locally, but as soon as two players connect, units move slightly differently, attack timings drift, and eventually the simulation diverges.

The core problem is that determinism is not a feature you can bolt on later. It must be baked into every layer: input collection, simulation update, random number generation, and network delivery. We have seen projects where the team spent months debugging desyncs caused by something as simple as iterating a hash map that produced different orderings on different platforms. Another common failure is relying on floating-point arithmetic without specifying rounding modes—Intel and ARM CPUs can produce different results for the same expression.

This guide is for engineers who already understand the basics of lockstep and need to go deeper. We assume you have shipped or at least prototyped a multiplayer game and have encountered the pain of non-determinism. We will not rehash the fundamentals of client-server versus peer-to-peer; instead, we focus on advanced methods to achieve and maintain determinism at scale, with practical trade-offs for different game types.

When Basic Lockstep Fails

The classic lockstep model works when all players have low latency and the simulation is simple. But as soon as you introduce variable frame rates, input prediction, or any form of multithreading, determinism becomes fragile. For example, if your physics engine uses a variable timestep, two clients processing the same inputs may diverge because their frame rates differ. Even with a fixed timestep, the order of entity updates matters: if you update units in a different sequence on each client, the outcome changes.

The Cost of Non-Determinism

Beyond player frustration, desyncs erode trust in your game. Competitive players will abandon a title that frequently desyncs. For esports titles, a single desync during a tournament can cause a PR disaster. Moreover, debugging desyncs is expensive: you need replay logs, checksum comparisons, and often a custom diff tool. Teams that do not invest in deterministic architecture early end up rewriting large portions of their simulation later.

Prerequisites and Context Readers Should Settle First

Before adopting advanced lockstep methods, you need a solid foundation. First, your simulation must be deterministic by design. This means using fixed-point arithmetic or carefully managed floating-point, ensuring that all random number generators produce the same sequence given the same seed, and that no external factors (system time, file I/O, user input outside the game) affect the simulation. Second, you need a network layer that can deliver inputs reliably and in order—or at least detect and handle missing inputs gracefully.

We recommend that you have already implemented a basic lockstep loop and tested it on a local network. You should understand the difference between optimistic and conservative approaches to input delay. Optimistic lockstep runs the simulation immediately with predicted inputs and corrects on mismatch; conservative lockstep waits until inputs from all players arrive before advancing. Each has trade-offs in responsiveness versus determinism guarantees.

Another prerequisite is a robust replay system. Replays are not just for spectators; they are your primary debugging tool. Every input must be recorded with a timestamp and a checksum of the resulting game state. When a desync occurs, you can replay the input sequence on a reference client and compare state checksums at each frame. Without replay, isolating a desync is nearly impossible.

Platform and Toolchain Considerations

Your choice of programming language and engine affects determinism. C++ gives you full control over memory layout and arithmetic, making it the most common choice for deterministic games. Unity and Unreal Engine have built-in non-determinism in their physics and animation systems; you must replace or disable those systems for lockstep. Many teams use Emscripten to compile C++ to WebAssembly for browser-based games, but WebAssembly's floating-point behavior is well-defined, so it can be a good target if you are careful.

Network Topology and Authority

Decide early whether you will use peer-to-peer (P2P) or an authoritative server. P2P lockstep is simpler to implement but exposes the simulation to cheating—any player can modify their client and send false inputs. An authoritative server model, where the server runs the simulation and clients send only inputs, is more secure but requires more bandwidth and introduces server-side determinism challenges. For this guide, we focus on P2P lockstep with rollback, which is common in fighting games and RTS titles, but we also discuss hybrid approaches.

Core Workflow: Building an Advanced Lockstep Loop

The heart of deterministic lockstep is a fixed-timestep simulation loop that processes inputs in the same order on every client. Here is a step-by-step workflow that we have found effective in production.

Step 1: Input Collection and Serialization

Each frame, collect all player inputs (key presses, mouse clicks, joystick movements) into a compact binary format. Include a sequence number and a checksum of the previous frame's state. Serialize the input buffer deterministically—avoid variable-length encodings that depend on platform endianness. Use a fixed-size struct with explicit padding.

Step 2: Input Exchange and Buffering

Broadcast the input packet to all peers. Each peer maintains a buffer of incoming inputs. The simulation advances only when inputs from all players for a given frame number have arrived. If inputs are delayed, the simulation stalls (conservative mode) or uses predicted inputs (optimistic mode). For advanced lockstep, we recommend a hybrid: use prediction for a few frames, then stall if inputs are still missing. This balances responsiveness with determinism.

Step 3: Simulation Update

Run the simulation with the agreed-upon inputs. Use a fixed timestep (e.g., 16.67 ms for 60 Hz). Update all entities in a deterministic order—sort entities by a unique ID or use a fixed array. Avoid dynamic memory allocation during the simulation; pre-allocate all objects. Use a deterministic random number generator (e.g., a custom LCG with a known seed) for any stochastic events.

Step 4: State Checksum and Verification

After each frame, compute a checksum of the entire game state. A common approach is to hash a serialized representation of all entities. Compare checksums with peers at regular intervals (every 10–60 frames). If a mismatch occurs, trigger a rollback to the last known good state and replay inputs from that point. This is known as rollback netcode.

Step 5: Rollback and Resynchronization

When a desync is detected, the client must revert its state to the last frame where checksums matched, then reapply inputs from that frame forward. This requires storing snapshots of the game state at each frame (or at key intervals). The snapshots must be lightweight—store only the minimal data needed to reconstruct the state. Use a ring buffer to keep the last N frames of snapshots and inputs.

Tools, Setup, and Environment Realities

Implementing advanced lockstep requires careful tooling and environment management. Here are the tools and practices we recommend.

Deterministic Math Library

Replace all floating-point operations with fixed-point arithmetic, or use a math library that guarantees identical results across platforms. For C++, consider using a fixed-point class with 16.16 or 24.8 precision. For Unity, you can use the Photon Deterministic library or write your own fixed-point struct. Avoid using the standard math functions (sin, cos, sqrt) directly; implement them using deterministic algorithms (e.g., Taylor series or lookup tables).

Memory Allocator

Use a custom allocator that does not rely on the system heap. Pre-allocate all objects in a contiguous block and use indices instead of pointers. This prevents memory fragmentation and ensures that the order of allocation is identical across clients. A stack allocator or pool allocator works well.

Network Library

For P2P lockstep, use a reliable UDP library like ENet or RakNet. Configure it to deliver packets in order and with a configurable timeout. For authoritative server models, use TCP or a reliable UDP wrapper. Ensure that the network library does not introduce non-determinism—for example, do not use timestamps from the system clock in the simulation logic.

Testing and Debugging Environment

Set up a test harness that runs multiple clients on the same machine, each with a simulated network delay and packet loss. Use a deterministic seed for all random number generators. Record all inputs and state checksums to a log file. Write a diff tool that compares two logs and highlights the first frame where checksums differ. This tool is invaluable for pinpointing the cause of a desync.

Variations for Different Constraints

Not all games have the same requirements. Here are variations of advanced lockstep for different scenarios.

Peer-to-Peer with Rollback (Fighting Games)

Fighting games demand low latency and frequent input sampling. The standard approach is GGPO-style rollback: run the simulation optimistically with predicted inputs, and when the real input arrives, roll back and correct. This requires storing snapshots every frame and being able to revert quickly. The trade-off is increased CPU usage for rollbacks and potential visual glitches if predictions are wrong.

Authoritative Server with Deterministic Simulation (RTS)

For real-time strategy games with hundreds of units, an authoritative server reduces cheating risk. The server runs the full simulation and sends state updates to clients. Clients can run a parallel simulation for prediction, but the server's state is final. This model requires the server to be deterministic, which is easier if you use a single-threaded simulation. The downside is higher bandwidth and server cost.

Hybrid: Deterministic Client Simulation with Server Validation

Some games use a hybrid: clients run the simulation deterministically and send their final state to the server. The server compares states from multiple clients and detects mismatches. This is less secure than a full server authority but reduces server load. It is suitable for cooperative games where cheating is less of a concern.

Large-Scale Simulations (MMOs)

For MMOs with thousands of players, full lockstep is impractical. Instead, use a deterministic simulation for small groups (e.g., a raid instance) and a traditional client-server model for the rest. The lockstep zone runs on a dedicated server that synchronizes inputs from all players in that zone.

Pitfalls, Debugging, and What to Check When It Fails

Even with a solid design, desyncs will occur. Here are common pitfalls and systematic debugging steps.

Common Pitfalls

  • Floating-point non-determinism: Using different compiler optimizations or math libraries can cause divergence. Always use the same compiler and flags for all platforms.
  • Hash map iteration order: Iterating over an unordered map produces different orders on different runs. Use a sorted container or a fixed-order iteration scheme.
  • Uninitialized memory: Variables that are not initialized can contain garbage values. Always initialize all variables, even if you think they will be set later.
  • Multithreading: If your simulation uses multiple threads, the order of thread execution can vary. Use a single-threaded simulation or enforce a deterministic thread schedule.
  • External dependencies: Libraries that use system time, file I/O, or network calls in the simulation loop break determinism. Avoid them or mock them during simulation.

Debugging Workflow

When a desync is reported, follow these steps:

  1. Reproduce the desync on a test machine with the same input sequence. Use replay logs to ensure identical inputs.
  2. Compare checksums frame by frame to find the first frame where they differ.
  3. Isolate the entity or system that caused the divergence. Use binary search: disable half the systems and see if the desync still occurs.
  4. Once you identify the system, examine the code for non-deterministic operations. Add logging to track variable values at each step.
  5. Fix the issue and add a unit test that verifies determinism for that system.

Preventative Measures

Build a continuous integration pipeline that runs the same simulation on multiple platforms and compares checksums. This catches non-determinism early. Also, use a deterministic assert macro that checks invariants during development.

FAQ and Checklist for Production Readiness

Here are answers to common questions and a checklist to ensure your lockstep implementation is production-ready.

FAQ

How do we handle input lag compensation? Use rollback netcode: predict inputs for a few frames, then correct when real inputs arrive. The prediction can be as simple as repeating the last input or using a more sophisticated model based on player behavior.

How do we compress state snapshots for rollback? Store only the delta between frames, or use a bit-packed representation of entity states. For many games, a snapshot of 100 entities can be compressed to a few hundred bytes using delta encoding and run-length encoding.

Can we support spectating in lockstep? Yes, spectators can receive the same input stream and run the simulation deterministically. They need to be synchronized with the game state before joining. Use a state snapshot to catch them up.

What about variable frame rates on clients? Use a fixed timestep for the simulation, independent of the rendering frame rate. Accumulate time and run multiple simulation steps per render frame if needed. This ensures determinism regardless of client performance.

Production Readiness Checklist

  • [ ] All simulation code uses deterministic math (fixed-point or controlled floating-point).
  • [ ] No dynamic memory allocation during simulation frames.
  • [ ] Input serialization is platform-independent (fixed endianness, no padding assumptions).
  • [ ] State checksums are computed every frame and compared at least every 10 frames.
  • [ ] Rollback snapshots are stored for at least 64 frames (adjust based on worst-case latency).
  • [ ] Network library is configured for reliable, in-order delivery with configurable timeout.
  • [ ] Test harness runs on multiple platforms and compares checksums automatically.
  • [ ] Replay system records all inputs and state checksums with timestamps.
  • [ ] Debugging tools (diff log, frame-by-frame replay) are ready for use.
  • [ ] Team has documented the deterministic assumptions and code conventions.

Once you have checked all items, you are ready for production testing. Start with a closed beta and monitor desync reports. Be prepared to iterate on the debugging tools—they are your most valuable asset. The next move is to set up a continuous determinism test in your CI pipeline and run it on every commit. This will catch regressions before they reach players.

Share this article:

Comments (0)

No comments yet. Be the first to comment!