Introduction
Zentra is a multiplayer third-person shooter that runs in a browser tab. No install, no download. You click a link and you’re in a forest with a gun.
The hardest part of building it wasn’t the graphics. It was answering one question fairly, for every player at once: did that shot hit?
This post walks through what happens between the moment you pull the trigger and the moment someone respawns.
The core rule: the server decides
Every player’s browser sends its shots to a central server, and the server makes the call. Your browser never decides whether you hit someone, and it never decides how much health anyone has.
This matters for two reasons. The first is fairness: if each player’s computer decided its own hits, two players could see two different versions of the same fight. The second is trust: a game that believes the client is a game that’s easy to cheat.
So the server keeps every player’s health, and every screen shows a copy of the server’s number.
Step 1: You pull the trigger
When you fire, your browser builds a ray, an invisible straight line from the camera through the centre of your crosshair. The camera, the crosshair and the ray are all the same line, so the thing you’re aiming at is exactly what gets tested.
Your browser plays the gunshot, the muzzle flash and the recoil straight away, so the game feels instant. Then it sends the shot to the server.
Step 2: The server rewinds time
There’s a gap between what you see and what the server knows. By the time your shot arrives, your target has already moved. If the server tested your shot against where they are now, you’d miss shots that were dead-on from your point of view.
So the server keeps a short memory of where every player has been. When your shot arrives, it rewinds your target to where they were a fraction of a second earlier, closer to what you actually saw. This is called lag compensation, and it’s the reason shots feel fair even on an average connection.
Step 3: Did it actually hit?
The server models each player’s body as an upright capsule, a cylinder with rounded ends. It then checks whether your ray passes through it.
This sounds simple, and our first version got it wrong in an interesting way. In a third-person game, the camera sits above and behind your character, so most shots travel slightly downward. A shot on a perfect collision course often enters the body’s footprint above the head and only reaches the torso further along. The original check only looked at the single point where the ray first entered, so it threw away most real hits. Only perfectly centred shots counted.
The fix was to treat the ray as a range instead of a point. The server works out the stretch of the ray inside the body’s footprint, and the stretch inside the body’s height, and counts a hit if those two stretches overlap. That one change is the difference between shooting feeling broken and feeling right.
Step 4: Was anything in the way?
A hit only counts if nothing was blocking it. The server checks the line between you and your target against:
- Hills. The terrain is generated from the same formula on the server and in every browser, so both sides agree on where the ground is without ever sending it over the network.
- Tree canopies. The wide lower branches act as real cover, and the crowns on top block shots too.
- Tree trunks. You can’t shoot through a tree.
There are hundreds of trees on the map. Their positions live in one shared file, read by both the server and the browser, so the trees you see are exactly the trees the server tests against.
One bug here is worth a story of its own. Trunk detection once treated trees behind the shooter as obstacles. A shot with nothing between you and your target could be blocked by a tree you’d walked past. The fix came down to keeping a minus sign in a single calculation.
Step 5: Damage
If the shot hits and nothing blocks it, the target loses 10 health. Then the server sends out four messages:
The person hit always receives their real health from the server. Their browser never calculates it. If a message is ever lost, the next hit puts their screen back in sync.
The hit marker only appears when the server confirms the hit, so it doesn’t lie to you.
Step 7: Respawn
Five seconds later, the player comes back at full health, somewhere away from the action.
Health also recovers on its own. If you go ten seconds without being hit, it climbs back by ten points a second until you’re full. Getting hit again restarts the clock, which keeps fights decisive and gives players a reason to break off and regroup.
A shield ability that can’t be cheated
Pressing Q makes you invincible for five seconds, with a ten-second cooldown. The server enforces both. It keeps the time each player’s cooldown ends, and refuses to activate the ability early, no matter what the browser asks for. A modified client can’t spam it.
Where everything lives
- During a match, every player’s health, kills and deaths sit in a fast in-memory database built for rapid updates.
- When the ten-minute match ends, each real player’s kills and deaths are added to their permanent profile, and the match data is cleared.
Match data is disposable and career stats are durable, and the two only meet once per match.
The takeaway
Getting a shot to feel right in a browser comes from a handful of decisions:
- One authority for health and life.
- A rewind for the delay players can’t avoid.
- A hit test that understands third-person aim.
- Terrain and trees that are identical on both sides without being sent over the network.
- A kill record that can’t be split or doubled.
None of it is visible when you play. You just notice that when you aim at someone and pull the trigger, they go down.
