The server only sends what you could see
Wallhacks and radar cheats work because the game hands your PC players you can't see. Here is how the Dead Drop server decides, many times a second, which players each client is allowed to know about.
Dead Drop is a 100-player, first-person battle royale for PC, built for competitive play. This devlog is where we write about how it works: long, technical posts about systems that already exist in the game, with what they cost and where they fall short. The first one is about the system the rest of the game is built around.
The client knows too much
In most online shooters, your game client knows where the enemies are, including the ones you can’t see. The player waiting in the next room, the team crossing the field behind the hill: their positions arrive on your machine many times a second, sit in memory, and simply aren’t drawn.
There are reasons it’s done that way. It’s simple: the server sends everyone nearby and lets the renderer sort out what’s on screen. It also hides latency, because a player the client already knows about can be drawn the instant he steps out from cover.
Every wallhack, ESP overlay and radar cheat lives in the gap between what the client knows and what it shows. A wallhack draws the players the walls would have hidden, ESP labels them, a radar plots them on a map. None of them needs to talk to the server. They read data the game already handed over.
The usual answer is client-side anti-cheat, software on the player’s machine that looks for programs touching the game’s memory. It stops a lot of cheats, and we will run one. But the most valuable cheats have moved out of its reach: a card in an expansion slot reads the game’s memory directly, and a second computer draws the radar on its own screen. Nothing running on the gaming PC can see that happen.
Only one defense doesn’t care where the cheat runs: don’t send the data. A position the client never received can’t be read out of memory, drawn through a wall or shown on a second screen. That is the idea behind the fog of war in Dead Drop. For every player, many times a second, the server decides which other players that client could plausibly see right now, and sends only those.
This isn’t a new idea. Server-side visibility culling exists in other shooters, sometimes from the start, sometimes added later. What we’re doing is treating it as a foundation rather than a patch: the rendering rules, the smoke, the doors, spectating and the network protocol were all designed around the fact that a client is only ever told what it could see. Here is how the server decides.
One player’s view of the match, from above. Filled dots are sent to his client, hollow ones are not. Illustrative, not to scale.
The base band: distance and update rate
The first filter is distance. Every player has a band around him, and enemies outside it aren’t sent at all.
A battle royale map is several kilometers across and most of the hundred people on it are nowhere near you, so a cutoff has to exist. Where to put it is a question about the screen. A person standing a few hundred meters away is a handful of pixels tall at 1080p. Somewhere past that, an unaided eye isn’t going to pick out a person, so the server doesn’t send one.
Inside the band, the update rate falls with distance. Up close, an enemy is in every update the server sends, because at that range a fight is decided by fine movement. In the middle distances he’s updated several times a second, and the client interpolates between updates; someone walking across your view a couple of hundred meters away moves a pixel or two between them. Near the edge of the band, updates come every so often, and the client fills in the gaps. This falloff is most of the bandwidth budget.
The scope cone
The band is sized for the naked eye. A scope changes what an eye can see: an 8x optic makes a person 400 meters away look like one at 50. If the server stopped at the band, a sniper would glass a distant hillside and find it empty.
So while you aim down sights with a magnified optic, the server adds a cone along your aim. Inside the cone, players are sent out to a range that depends on the optic, well past the base band for the strongest ones. Their update rate comes from their effective distance, the real distance divided by the magnification: through an 8x scope, someone 800 meters away is updated as if he stood at 100, because that’s how big he looks. Our renderer uses the same rule to choose detail levels, so a scoped view of a far hillside gets the same treatment as a nearer view without the scope.
Which optic you have is the server’s own knowledge, taken from your inventory. A client can’t claim a stronger scope than it carries.
On the input, not the animation
Raising a scope takes a moment. If the server waited until the glass was fully up, your first look through it would show an empty landscape that filled in a beat later. Instead, the cone starts as soon as the server receives your aiming input. On a typical connection, the round trip plus the client’s interpolation delay is about as long as the raise animation, so the data arrives as the glass comes up rather than a beat after it.
Couldn’t a cheat send the aiming input all the time? It could, but aiming is decided by the server, so that client moves at aiming speed like anyone else who is aiming. It gets what any sniper gets, in the direction it’s looking, and moves like a sniper to get it.
Padding and lingering
The cone is wider than the scope’s field of view. Your aim reaches the server half a round trip after you move the mouse, and what it sends back takes the other half plus the interpolation delay to show up. If you turn while scoped, the edge of your screen sweeps into ground the server hasn’t sent yet.
So the cone is padded by how far you could plausibly turn in that time: the fastest realistic turn rate for that optic (lower for stronger optics, which are harder to swing), times your own measured round trip and interpolation delay, plus a small constant. A player on a worse connection gets a wider cone, because he needs one to see the same thing.
The cone also lingers briefly after you leave the scope, so dropping it to reposition and raising it again doesn’t empty and refill the hillside.
Occlusion: walls on the server
Distance and the cone produce candidates. For each one, the server then checks whether there is actually a line of sight, and drops those hidden behind terrain or buildings. This is the step that makes the fog of war worth having.
Collision shapes, not render meshes
The server doesn’t test against the meshes you see. It tests sight lines against the terrain’s height field and the simplified collision shapes that the simulation already uses for movement and bullets: boxes and simple solids for walls, roofs, rocks and tree trunks.
That keeps the test cheap, and it means the walls that hide a player from the server are the same walls that stop his bullets.
The shapes also know what isn’t a wall. A window hides nobody, so glass doesn’t block a sight line. Buildings can be destroyed, and the fog of war follows the damage: a wall that has been blown out hides nothing, and a hole knocked through a wall by a grenade is a hole in the fog too, within moments of being made.
Several rays per pair
A person isn’t a point. For each pair of players, the server casts several rays, starting and ending at the real eye, so a lean around a corner moves them with the head:
- eye to eye;
- eye to the middle of the body, both ways, which catches a body showing through a gap or under an overhang when the line between the heads is blocked;
- eye to eye with both players moved a short way along their current movement, a guess at where they’re about to be.
Close in, it adds more: rays shifted a little to either side of the direct line, and rays from one player’s current position to the other’s predicted one. If any ray is clear, the pair is visible.
The test is symmetric. If I can see you, you can see me, so one result serves both players, and the answer can never differ between them.
Sending a player before he rounds the corner
The extra rays are there for corners. Picture someone sprinting toward the corner of a building, about to come around it. On the direct line he’s still hidden. If the server waited for that line to clear, it would start sending him the moment he became visible, and he would reach your screen a round trip later: a player who pops into existence already halfway around the corner.
The predicted and shifted rays look a little way around the edge. A player who is about to clear a corner, or standing close enough to its edge, is sent shortly before he becomes visible, so by the time he steps out your client already has him and draws him smoothly.
The direct sight line is blocked, but the shifted and predicted rays clear the corner, so the runner is sent a moment before he becomes visible. Illustrative, not to scale.
That is a deliberate trade: a patched client may see someone a step before an honest player would, which is a much smaller gift than players appearing out of thin air mid-fight.
A cache, a budget, and nearest first
A hundred players make nearly five thousand pairs, and testing each with several rays on every tick would be wasteful. Each pair’s result is cached and retested on an interval that grows with distance, and retests are staggered so the work spreads evenly across ticks.
Each tick, the due pairs are tested nearest first, under a cap on how many rays the server will cast; pairs that don’t fit keep their previous answer one more tick. A leak through a wall at twenty meters wins fights, while a leak across open ground at five hundred is worth far less, so the budget goes where a mistake costs the most. When the server is behind, it errs toward sending: a pair it has never tested is sent rather than hidden. In our benchmark the cap has never been reached.
Holding against flicker
A player walking through a stand of trees can be visible on one test and hidden on the next. If the server dropped him every time, he would blink in and out on your screen, and every reappearance would cost a round trip. So a visible result is held for a fraction of a second after the last clear test. The cost: a patched client keeps someone for that moment after he ducks out of sight.
Finally, inside a tiny radius, about arm’s reach, everyone is sent regardless; at that range the risk of someone materializing in your face isn’t worth it. The radius is deliberately small, because the worst wallhack is the one that shows a player through a wall up close.
Smoke and closed doors are walls too
A smoke grenade is easy to build as a purely visual effect: each client draws its own particles, the server neither knows nor cares where the smoke is, and players on the other side are sent as usual. A cheat that hides the particles, or just reads positions, sees straight through it.
In Dead Drop, the throw runs in the shared simulation that the server and every client execute identically, so the server knows exactly where the smoke landed and how large it has grown. Each smoke has an opaque core, a sphere, and every ray of the occlusion test is blocked by it as by a wall. Clients draw exactly that core as solid, the same on every machine, with no quality setting that thins it; the soft edge around it is cosmetic. A player on the far side of a smoke is not sent, and a patched client gets nothing through it.
A smoke grows for a few seconds after it pops, and because it acts through the same cache as walls, it takes effect within the pair’s next retest plus the hold: a fraction of a second up close, a little longer at range. Inside arm’s reach, everyone is sent, smoke or not.
To the server, a smoke’s opaque core and a fully closed door are walls. A door left ajar hides nothing. Illustrative, not to scale.
Doors work the same way. A fully closed door is a wall for every ray of the test, so a player in a shut room is not sent to the people outside it. When the door opens, the fog lifts within the pair’s next retest, a small fraction of a second for players near each other.
An ajar door hides nothing, by choice. A crack is enough to see through, and if the server treated a door open by a few degrees as a wall, someone watching through the gap would see an empty room until the player inside popped in. A door on its hinges isn’t cover worth that, so only a fully shut door counts.
The doors’ own state is sent without a sight test, for the doors around you and those inside your scope cone: a door swinging open across a valley is information you earned by looking. Which parts of which buildings have been destroyed goes to everyone, because a house that comes down is world geometry.
The exceptions, on purpose
A few things are sent outside these rules, by design.
Teammates are always sent, at any distance, through walls and smoke, with no rays spent on them and often enough that their markers move smoothly. The fog of war hides enemies, never your own team, whose positions your interface shows anyway.
A spectator gets exactly the spectated player’s view. Everything above is computed from his position and his aim, never the union of his view and wherever your own body lies, so a dead player can’t become a radar for his team. It costs no extra rays, and the stream runs a short delay behind the live match to blunt real-time call-outs.
Vehicles are sent by distance alone. A running engine can be heard from far away and a parked car is part of the landscape. The people inside follow the same rules as everyone else.
Sound is sent on purpose. This is the big one, and it is a deliberate leak. When a gun fires, everyone within earshot gets an event saying who fired, from where and which way, because the client needs that to play the report and the crack and draw the tracer. For a rifle, earshot is much farther than the base band; a suppressor shrinks it. Footsteps go to listeners within their much shorter radius who aren’t already being sent that player.
That is how you hear a sniper you were never sent. You turn toward the report and raise your scope, and if he’s in the open, he’s now inside your cone and the server sends him. Finding a shooter by ear is a skill we want in the game. The cost is that a patched client can mark every gunshot and footstep it hears on a map. That is less than a wallhack gives, because a sound is a single moment the player chose to make, not a stream of positions. But it is a real leak, and we chose it.
What it costs
We measure the server with a benchmark: 100 bot clients against one match over real UDP, on a 2 km test map with buildings and thousands of trees, through a simulated internet link with 80 ms round trips, jitter, 2% packet loss, duplication and reordering. The bots roam, scan the horizon through their scopes and fight what they find, and all 100 stay alive for the whole measured segment, a worst case no real match reaches. The bots only know what the server sent them, so every run is a test of the fog of war too.
On a desktop CPU, with the match on one thread:
- Server tick: a median of 1.3 to 2.0 ms for everything the server does in a tick, depending on the update rate. The relevance work, band, cones and sight tests for all 100 players, is 0.4 to 0.7 ms of that.
- What each client knows: in one round of measurements, a client knew about 11 other players on average, out of 99. About 35 were candidates by distance or a scope cone, and the sight test dropped two thirds of those; on that map, terrain and trees hide most of the band.
- Rays: roughly 500 to 600 sight-line rays per update for the whole match, never reaching the cap.
- Bandwidth: 20 to 35 kbps per player on average, depending on the update rate, or 0.6 to 1.05 GB of outgoing data for a 40-minute match.
The breakdown is the interesting part. In the same round, at about 21 kbps per player, other players’ movement made up about 3 kbps, and gunshot events nearly 5, because every shot on a 2 km map is audible to almost everyone on it. Most of the rest is packet headers and your own player’s state. Once the server stops sending what you can’t see, other players become one of the cheaper things in your stream, and the deliberate sound leak costs more than they do.
The limits
Here is what this does not do.
Pop-in on a fast scoped flick. The cone’s padding covers realistic turns. Flick faster than that and a far target outside the cone isn’t sent until your new aim reaches the server and an update comes back; a distant target is also updated less often than a near one. In the worst case that is a few hundred milliseconds of a far player appearing after your crosshair is already on him. We think that’s the right trade at those distances, but you can run into it.
The edges and the sounds leak by design. The corner rays, the prediction, the hold and the arm’s-reach radius all lean toward sending a player slightly early or keeping him slightly late, and gunshots and footsteps tell a patched client where they came from, the same as they tell your ears.
Grass doesn’t hide you from the server. Sight is tested against terrain and solid shapes, not grass or bushes, so a player lying in long grass in plain line of sight is sent. An honest client draws the same grass over him on every machine; a client modified to skip the grass doesn’t. There, client integrity checks and anti-cheat have to do the work.
The engine is open source. That makes a patched client easier to build than on a closed engine. Client anti-cheat and integrity checks will stop the common case. The fog of war is the ceiling on what a patched client can learn, not a complete anti-cheat: it does nothing against an aimbot aiming at a player you can legitimately see. Other layers, like the server deciding recoil and when a shot happened, will get their own posts.
There is one more consequence, and we’ll keep it brief. Because the server decides what each client is sent, it also knows exactly what each client was sent, and when. That record is useful. How we use it is the one part of this system we’re not going to describe.
Why it matters
Competitive play rests on a simple rule: what you know about your opponent should be what you earned by looking and listening. A wallhack breaks it, so does a radar on a second screen, and so does a settings menu that lets one player turn off the grass another is hiding in. Client-side detection catches some of these some of the time. Not sending the information closes the most damaging ones for good.
When the fog of war works, you don’t notice it. Players appear when they come around corners, smoke hides the people behind it, a shut door means nobody outside knows you’re there, and a gunshot across the valley tells you where to look. The difference is that a cheater doesn’t get anything more out of the network than you do.