Multiplayer Netcode

Netcode exists to solve one problem: the authoritative version of the game is somewhere else, and light is slow. A player in Berlin talking to a server in Virginia has an unavoidable round trip of roughly 90 ms. If pressing a movement key waits for the server to confirm it, the game feels broken — and if it doesn't wait, the client and the server disagree about reality.

Everything in this field is a technique for hiding that gap while keeping the server in charge. Client prediction hides your own latency. Entity interpolation hides everyone else's. Lag compensation makes shooting at where you saw someone actually work. Rollback makes fighting games playable across a continent. They compose into a small number of well-understood models, and choosing among them is the first architectural decision a multiplayer game makes.

TL;DR

Quick Example

Prediction and reconciliation — the core client loop of almost every action game:

Note that simulate() is the same function on both sides. Prediction only works if the client can reproduce the server's math exactly.

Core Concepts

Server authority

Clients send inputs, not outcomes. This single rule eliminates the majority of cheats: speed hacks, teleporting, infinite ammo, and damage modification all become server-side validation problems rather than possibilities. It also means the server must run the game simulation, which is the main cost of the model.

The three latency-hiding techniques

They apply to different entities. Your character is predicted. Everyone else is interpolated. Hit detection is compensated. Mixing them up — predicting other players, or interpolating your own character — produces exactly the artifacts each technique exists to prevent.

Prediction and reconciliation

Prediction alone drifts: the client and server will disagree, because of collisions with entities the client hasn't seen, or dropped packets, or float differences. Reconciliation is the correction.

The player perceives nothing if corrections are small. Large corrections need smoothing — blend toward the corrected position over a few frames rather than snapping — but smoothing large errors instead of fixing their cause just delays the visible problem.

Entity interpolation

Snapshots arrive at the tick rate (say 20 Hz, every 50 ms) while you render at 60+ FPS. Rendering the newest snapshot directly gives 20 Hz stutter. Instead, buffer snapshots and render at now - delay, interpolating between the two that bracket that time.

If the buffer runs dry (packet loss), you must extrapolate briefly or freeze. Extrapolation overshoots on direction changes; most engines extrapolate for a short window and then hold.

Lag compensation

The consequence of interpolation: when you fire at an enemy's head, the enemy is 100 ms further along than what you saw. Naive hit detection means every shot at a moving target misses.

This is why you sometimes die after reaching cover — on the shooter's screen, you were still exposed. It's not a bug; it's the deliberate choice to favor the shooter's experience. The alternative (favoring the target) means shots that visually connect don't register, which players find far worse. Cap the rewind window (typically ~200–250 ms) so high-latency clients can't exploit it.

Network Models

Deterministic lockstep

Send only inputs; every peer runs the identical simulation. Bandwidth is independent of entity count — which is why StarCraft and Age of Empires could network thousands of units over dial-up. The costs are severe: the simulation must be bit-deterministic across all machines, and every peer waits for the slowest, so input latency equals the worst ping in the session. One desync and the game is unrecoverable.

Rollback

Rollback fixes lockstep's latency by refusing to wait. Predict the remote player's input (usually "same as last frame"), simulate immediately, and when the real input arrives and differs, rewind to that frame and re-simulate everything since.

Because misprediction is only visible during the ~2–5 frames of correction, and most frames in a fighting game have no input change, rollback feels close to local play at surprising latencies. It requires a fully deterministic, cheaply re-simulatable game state — which is why it's near-universal in fighting games and rare elsewhere.

Bandwidth and Scale

Delta compression

Send only what changed since the client's last acknowledged snapshot. A player standing still costs nothing. Requires keeping a per-client history of acknowledged states, which is the standard price of admission.

Interest management

A client only needs entities it can perceive. Cull by distance, room, or visibility (a grid or spatial hash over the world) so a 100-player match doesn't send every client all 99 others. This is what makes large player counts feasible at all, and it has an anti-cheat benefit: information the client never receives cannot be extracted by a wallhack.

Quantization

Full-precision floats are wasteful. Positions compress to 16-bit fixed-point over a known world range, rotations to a smallest-three quaternion in ~29 bits, and velocities to fewer bits still. Typical savings are 50–75% of state payload for imperceptible precision loss.

Tick rate

Tick rate is a server CPU and bandwidth cost multiplied by every player in every match. Raising it improves responsiveness with diminishing returns; interpolation and prediction quality usually matter more than the last 30 Hz.

Best Practices

Design for multiplayer from the first prototype

Retrofitting authority onto a codebase that assumed local, immediate state changes generally means rewriting the gameplay layer. Even in single-player, separating "simulation state the server owns" from "presentation the client renders" costs little and makes the port tractable.

Share one simulation implementation between client and server

Prediction only works if simulate(state, input) produces identical results on both sides. Two implementations will diverge, and the divergence appears as unexplained rubber-banding. Compile the same code for both, or run JavaScript/C# on both ends.

Validate every input server-side

Range-check movement magnitude, enforce fire-rate cooldowns, verify line of sight, and reject inputs with implausible timestamps. Sanity checks on the server are the entire anti-cheat story for state manipulation.

Never send information the client shouldn't have

Position data for enemies behind a wall becomes a wallhack no client-side rendering rule can prevent. Interest management is a security control as much as a bandwidth one.

Use UDP with your own reliability layer

TCP's in-order guarantee causes head-of-line blocking: a lost packet stalls newer state that's already arrived and would have been more useful. Real-time state wants "newest wins," which is UDP semantics. Use an existing transport (ENet, Steam Networking, QUIC, WebRTC data channels in browsers) rather than writing your own.

Measure with real network conditions

Everything works on localhost. Use a network conditioner to inject 100 ms latency, 50 ms jitter, and 2% packet loss during normal development — not as a final test. Most netcode bugs are invisible at 0 ms.

Make the correction visible to you and invisible to the player

Log and graph reconciliation error magnitude. A rising error rate is the earliest signal that client and server simulation are drifting, and it's much easier to debug from a metric than from a player report that "it feels weird."

Common Mistakes

Trusting client-reported state

Waiting for the server before moving

Interpolating your own character

Sending full state every tick

Building on a non-deterministic simulation, then adding rollback

Rollback and lockstep both require bit-identical re-simulation. Discovering that your physics isn't deterministic after building the netcode on the assumption that it is means starting over. Verify determinism first — run the same inputs twice and compare state hashes.

Ignoring packet loss in testing

FAQ

Which model should I choose?

Match it to the genre. Fast action with a handful of players (FPS, action, battle royale): authoritative client-server with prediction, interpolation, and lag compensation. Fighting games: rollback. RTS with thousands of units: deterministic lockstep. Slow or turn-based: simple client-server without prediction — you don't need the complexity. Co-op with friends: a listen server is often entirely sufficient.

How much latency can I hide?

Prediction hides your own input latency essentially completely up to a few hundred milliseconds — what breaks down is correction size, since more elapsed time means more divergence to fix. Interpolation costs a fixed ~100 ms of "seeing the past," which players don't perceive. Lag compensation works well to roughly 200 ms and starts producing unfair-feeling outcomes for the target beyond that, which is why servers cap the rewind window.

Why do I die behind cover?

Lag compensation, working as designed. The shooter saw you exposed; the server rewound to that moment and confirmed a hit; by the time you see the result, you've reached cover on your screen. The alternative — favoring the target — means shots that visually hit don't register, which testing consistently shows players find more frustrating. Every competitive shooter makes this same tradeoff.

Can I do this in a browser?

Yes. WebRTC data channels give you unreliable, unordered UDP-like delivery in the browser, and WebSockets work for slower-paced games where TCP's head-of-line blocking is tolerable. WebTransport over QUIC is the emerging, cleaner option. The techniques are identical; the transport is the only thing that changes.

What about peer-to-peer?

It removes server cost and can lower latency between nearby players, at the price of cheat resistance (no trusted authority), NAT traversal complexity, and exposing players' IP addresses to each other. It's reasonable for co-op and trusted lobbies, and inappropriate for competitive play or anything with persistent progression.

Do engines handle this for me?

Partially. Unreal has the most complete built-in solution — replication, RPCs, and prediction in CharacterMovementComponent. Unity has Netcode for GameObjects plus third-party options like Mirror and Fish-Net. Godot has high-level multiplayer RPCs and synchronizers. All of them give you transport, replication, and often basic prediction; none of them make the authority and game-feel decisions for you, and those are where the difficulty actually lives.

Related Topics

References