How Source Engine Hides Latency: Prediction and Lag Compensation Under the Hood
Source Engine hides latency by running identical movement code on client and server. Learn how lag compensation rewinds hitboxes for hitscan and how the 100ms default lerp creates a fixed peeker's advantage for map design.
05 Mar 2026, 19:56 UTC

The Problem: Immediate Response vs. Server Authority
In a multiplayer shooter, pressing "move forward" must feel instant. But the server is the only authority on where players actually are. If the client waits for the server to acknowledge every input, 100 ms of ping turns into sluggish, unplayable movement. Source Engine solves this by letting the client predict its own movement locally while the server runs the authoritative simulation in parallel. When the server's snapshot arrives, the client reconciles any difference — ideally without the player noticing.
Two Simulation Paths, One Codebase
Source splits the simulation into two paths that share the same movement code. The client runs CInput::CreateMove each frame, builds a CUserCmd with the current tick count, and immediately applies it through the prediction system. The server receives that command later, runs the identical movement logic, and broadcasts the resulting world state in snapshots. Because both sides execute deterministic code with the same inputs, they should arrive at the same position.
When the client receives a snapshot, it compares the server's acknowledged position against its own predicted position for that tick. If they differ beyond a small tolerance, a prediction error occurs and the client re-simulates from the last acknowledged frame — the infamous "rubber-band" correction.
Lag Compensation: Rewinding Time for Hitscan
Movement prediction handles your movement. Lag compensation handles your aim. When you fire a hitscan weapon (bullet trace, melee), the server rewinds all other entities to the positions they occupied at your render time — your interpolation timestamp plus your latency. That rewind window is calculated as lerp + ping, where lerp (controlled by cl_interp/cl_interp_ratio) is the client's interpolation buffer.
At the default 66 Hz tickrate with cl_interp_ratio 1 and cl_interp 0, the minimum lerp is ~15.2 ms (one tick). Most clients run with the default 100 ms lerp (cl_interp_ratio 2 at 66 Hz), meaning the server rewinds ~100 ms + your ping. A moving player effectively sees enemies ~100 ms earlier than they are seen — the "peeker's advantage" that map designers mitigate with sightline length and angle design.
Worked Example: Visualizing Rewind and Prediction Errors
You can see both systems in action on a local listen server. Start a game (any Source title with the SDK tools — TF2, CS:S, HL2:DM) and open the developer console (~). Run these commands:
sv_cheats 1
sv_showlagcompensation 1
cl_showprediction 1
Where to run: Developer console on a listen server (hosting a local game). Requires sv_cheats 1, so this only works in non-VAC-secured environments (local, LAN, or community servers with cheats enabled).
What you'll see:
sv_showlagcompensation 1draws wireframe hitboxes at the rewound positions when you fire hitscan weapons. The color indicates hit/miss against the rewound volume.cl_showprediction 1prints a line each frame: "prediction error" followed by the position delta when the client corrects itself.
Expected check: Fire at a moving bot while standing still. The rewound hitbox should align with where the bot was on your screen, not where the server currently thinks it is. Now strafe and fire — notice the rewound box shifts further back.
Risk: These cvars flood the console and overlay. Disable with sv_showlagcompensation 0; cl_showprediction 0 when done.
The Determinism Trap
Prediction only works if client and server produce bit-identical results from the same inputs. Historically this required strict compiler flags (/fp:precise on MSVC, -ffloat-store on GCC) and banning transcendental functions (sin, exp) from predicted code. Modern builds still need audit when adding new predicted systems — a single float vs double mismatch in a physics callback causes desync every frame.
Custom movement code (surf, bhop, custom gravity) must implement both client and server sides identically and register fields in CMoveData. Missing a field or diverging logic guarantees prediction errors on every tick.
What Lag Compensation Doesn't Cover
Only hitscan and melee traces are rewound. Projectiles — rockets, grenades, arrows — spawn as independent entities on the server with no rewind. You still lead targets based on your ping. This asymmetry is intentional: rewinding every projectile's trajectory would require storing full entity histories for the entire rewind window, exploding memory and CPU cost.
Quick Verification Checklist
- On a listen server, set
sv_maxupdaterate 66then100. Withcl_interp_ratio 1; cl_interp 0, measure the minimum lerp viacl_interpreadback. At 66 Hz you'll see ~15.2 ms; at 100 Hz, ~10 ms. - Run
net_graph 1and watch "lerp" change as you adjustcl_interp_ratio. The floor iscl_interp_ratio / updaterate. - If you're modding movement, add
ConVarRef cl_showprediction("cl_showprediction");to your predicted code path and assert zero errors in a local game.
Closing: Design Around the 100 ms Window
Source's networking model is a set of deliberate trade-offs: 100 ms default lerp for visual smoothness, fixed tick rates for deterministic replay, hitscan-only rewind for performance. If you're building on Source (or a derivative like Garry's Mod, Portal 2, or a custom branch), the practical takeaway is this: design your gameplay and maps knowing that moving players have a ~100 ms + ping visibility advantage. Sightlines longer than the distance a player covers in 150 ms at max speed become "peek and shoot" corridors. Test with sv_showlagcompensation 1 early — it's the only way to verify your hit registration matches player expectation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.