Time-Travel Fairness: How Servers Rewind for Lag Compensation
Why lag compensation rewinds the game world to when a shot was fired, and why victims experience the cost.
On this page
You aimed directly at the enemy. Your crosshair was dead center on their chest. You fired. And nothing happened — or worse, you died to their return shot, fired from a position where they weren’t even visible to you. Welcome to playing on 80ms latency without lag compensation.
The fairness gap: shooter aim vs. server ground truth, and how rewind closes it.
The Laggy Player’s Dilemma
Here’s the core problem. At 80ms latency, a round trip is 160ms. If you aim at an enemy who appears to be standing still on your screen, you fire a shot. That shot packet spends 80ms traveling to the server. By the time it arrives, the enemy has moved roughly 2 meters away (at typical game speed). The server tests whether your bullet ray intersected with the enemy’s hitbox at the moment it tests the collision — which is 80ms after you fired. The enemy has moved. Your shot misses, and you’re told to “aim ahead.”
But “aim ahead into empty space” breaks the game. A player who can only see at most 16ms into the future (one frame at 60Hz) cannot accurately predict where an enemy will be 80ms from now. The feedback loop is broken: you see something, you aim at it, you fire, and you’re told you aimed wrong — even though your aim was perfect given the information you had.
The solution: don’t test the hit at server-present-time. Test it at the time the shot was fired, using a snapshot of the world from 80ms ago.
How Lag Compensation Works
When you fire a shot, your client includes a timestamp in the packet. This isn’t “the time you sent the packet” — it’s the time you fired the weapon. On Counter-Strike 2, Apex Legends, or any modern competitive shooter, that timestamp is part of the player’s game tick or frame number, captured at the moment the fire input was processed locally.
The shot packet travels to the server, arrives 80ms later, and still carries that original timestamp. The server now has two pieces of information:
- The bullet’s trajectory (direction, starting position, identity of the shooter)
- The exact time the shot was fired on the client
The server then asks: “At that timestamp, was there an enemy in the bullet’s path?” To answer, it doesn’t look at the current world state. Instead, it seeks backward into its rolling buffer of historical world snapshots.
The Rolling Ring Buffer
Every modern game server keeps a circular buffer of world state snapshots — positions, rotations, and hitbox sizes of every player — timestamped at regular intervals. For a server ticking at 64Hz or 128Hz, a new snapshot is stored every 8–16ms. A 1-second buffer holds roughly 64–128 snapshots.
When the server receives a shot packet with a timestamp, it:
- Looks up the snapshot closest to that timestamp
- Re-creates the enemy’s hitbox at that moment
- Tests whether the shot ray intersects the hitbox
- Returns the result (hit or miss) to both players
The server doesn’t “move” anything. It doesn’t rerun physics or recalculate positions. It just tests collision against a frozen world state from the past.
Ring buffer rollback: the server keeps ~1 second of history and seeks to the shot’s timestamp to validate it.
Why This Feels Unfair to the Victim
Here’s where the honesty comes in. Lag compensation is unfair — but to the defender, not the attacker.
When you fire on your screen, the enemy is standing in the open. By the time the server receives your shot (80ms later), they’ve moved behind cover on the server. The server rewinds to your timestamp, finds them exposed, and awards the hit.
From the victim’s perspective, they saw themselves safely behind cover for the entire time. Then they died — to a shot fired from a direction where they weren’t visible. This is the shot-behind-cover experience that every competitive player has felt. It’s not a bug. It’s the direct, unavoidable cost of fairness to the shooter.
Both fairness models are impossible:
- No lag compensation: Shooters must aim ahead into empty space. Laggy players are permanently disadvantaged. The game is unplayable above ~40ms.
- With lag compensation: Defenders die to shots they didn’t see coming from their own perspective. But aim feels consistent for everyone.
Modern competitive games choose the second model because it makes aim feel fair across all latencies — and feeling fair is what keeps players in the game. The trade-off is that defenders pay the price, especially against laggy opponents.
The Anti-Cheat Boundary: Why Servers Cap Rewind
If rewind history is good, wouldn’t unlimited history be better? The answer is no, and the reason is abuse.
A malicious player could claim timestamps from 500ms, or even 1 second, in the past. In that ancient snapshot, the defender was still running through a chokepoint, fully exposed. The shot registers, and the victim doesn’t understand why they died to invisible fire. Worse, the attacker could artificially enlarge their “effective latency” by always sending shots with old timestamps, getting shots through that should have been blocked by present-time validation.
To prevent this, servers set a maximum rewind distance — typically 1000ms (1 second). Any packet claiming a timestamp older than the buffer’s oldest snapshot is either rejected, treated as a present-time hit test, or flagged as a potential cheat. The ring buffer size is an explicit security boundary.
Some competitive games use tighter bounds: Counter-Strike 2 typically allows up to ~100ms of backtrack, which covers normal RTT variance while preventing excessive abuse. The exact cap is a design choice that balances shooter fairness against defender protection and cheat prevention.
Clock Drift and Invalid Timestamps
There’s one more edge case: what if the client’s clock is wrong?
A player’s computer might have its clock set 2 seconds in the future. When they fire a shot with that bogus timestamp, the server receives a packet claiming to be from the distant past — or impossibly from the future. Modern game servers validate that client timestamps fall within an acceptable range, typically +/- 200ms of the server’s current time. If a timestamp is outside that window, the shot is rejected or clamped to a valid snapshot.
This is both a fairness and a cheat-prevention measure. A broken client clock is treated as a broken client.
The Honest Trade-Off
Lag compensation is a philosophical choice: make the shooter’s experience fair, even if it means some defenders will have an unfair experience. It works because:
- Most players fire more often than they’re shot from behind cover
- The player doing the shooting feels satisfied
- Defenders accept occasional “unlucky” deaths as the cost of smooth, responsive gameplay
The alternative — no lag compensation — makes the game literally unplayable for anyone not on single-digit latency.
Every time a defender dies to a shot they couldn’t see coming, they’re experiencing the cost of making everyone’s aim fair. It’s the honest trade-off that keeps competitive networked games alive.
Implementation Checkpoints
If you’re building a multiplayer game:
- Timestamp every input. The client’s game tick is your source of truth.
- Store world snapshots regularly. A 128-tick server needs 128 snapshots for a 1-second buffer.
- Validate timestamps. Reject or clamp timestamps outside your rewind window.
- Test at the snapshot, not the present. Raycast against the historical world state.
- Confirm to both players. The shooter needs confirmation; the victim needs honesty.
The hard part isn’t the mechanism — it’s accepting that no fairness model is perfect, and choosing which player bears the cost.
Frequently asked questions
Why does lag compensation exist at all? Can't players just aim for where the enemy is now?
At 80ms latency, the enemy moves ~1 meter ahead during your shot's round trip. If servers only tested hits against present-time positions, laggy players would have to aim into empty space to hit stationary enemies — making the game unplayable for anyone not on fiber. Lag compensation tests the hit against where the enemy WAS when you fired, giving everyone fair aim.
How does the server know what time the client fired the shot at?
The client includes a timestamp in every shot packet — the client's local game tick when the fire button was pressed. When the server receives that packet (delayed by network latency), the packet still carries the original timestamp. The server uses that timestamp to rewind to the corresponding world snapshot and test collision there.
What happens to the player who gets shot from behind cover? Is that not unfair to them?
It absolutely feels unfair. The victim sees themselves safely behind cover on their screen, but from the shooter's perspective (80ms in the past on their screen), they were still exposed when the shot was fired. The server validates the shooter's perspective, which means victims experience deaths that feel impossible. This is the honest cost of fairness to shooters — it shifts the unfairness burden to defenders.
How far back can the server rewind? Can it go back 500ms or 1 second?
Most servers cap rewind history to 1000ms (1 second) as a fixed ring buffer. This is an anti-cheat and abuse boundary: rewinding further would let exploits artificially extend effective latency, and servers must balance shooter fairness against preventing attacks. The buffer size is chosen to cover typical internet latencies; anything beyond that is treated as a missed shot.
What happens if the ring buffer doesn't have a snapshot for the timestamp on the packet?
If a packet arrives claiming a timestamp older than the buffer's oldest snapshot — or if there's clock drift between client and server — the server either rejects the shot as invalid, treats it as a present-time hit test, or uses the oldest available snapshot. Clients with broken clocks or extreme packet delay can have their shots invalidated, which is a safeguard against cheating and connection abuse.
/* Comments */
Comments are offline right now — we reconnect automatically, nothing is lost.