HOW IT WORKS
Opening a web page and being in a dogfight a few seconds later takes more machinery than it looks. This page explains how the world is drawn, who decides what happened, how your aircraft responds instantly when the server is far away, and what goes over the wire. The numbers come from the game's own source code.
Two halves
JET3D is two separate programs. The client is the page you open: HTML, JavaScript and the aircraft models, served as plain static files from a content delivery network (Cloudflare Pages), so it loads from somewhere near you. The server is a Node.js process on a small virtual machine in Frankfurt, running on Oracle Cloud. It keeps every match in memory and holds a secure WebSocket open to every player.
They have to be separate: a static host serves files and forgets you, while a game server has to keep running and keep talking to everyone at once.
Drawing the world
Everything you see is drawn with WebGL through Three.js, a widely used JavaScript 3D library. The style is deliberately low-poly. It reads clearly at speed, when you have a fraction of a second to tell an aircraft from a hill, and it runs on ordinary laptops and phones as well as gaming PCs.
The ground is a single mesh of about a quarter of a million triangles, sampled from the same height function the server uses for collisions (more on that below). Bright things — afterburners, fires, explosions, lights on the towers — get a glow pass over the finished frame. That pass looks after itself: if your frame rate stays under roughly 45 per second for a couple of seconds it drops to a lower resolution, and if that is not enough it turns itself off. It is never used on phones and tablets, whose graphics chips have enough to do already. The Faster graphics option in the in-game settings goes further, switching off shadows and glow, rendering at 1x resolution and thinning out the rain and street traffic.
The client runs its physics at a fixed 60 steps a second whatever your screen's refresh rate, and draws each frame at the right point between the last two steps, so a 144 Hz screen does not judder.
The server decides
JET3D is server-authoritative. Your browser never tells the server where your aircraft is. It sends your inputs — stick, buttons, triggers — and the server works out what they do. That is the main defence against cheating: a modified client can send odd inputs, but it cannot put itself somewhere it did not fly or claim a hit it did not land.
The server simulates every match at 60 steps a second. Each step it applies everyone's inputs, moves every aircraft, runs the bots, checks collisions with the ground, trees and buildings, moves every bullet, missile and flare, resolves hits, applies the ring and the ceiling, and keeps score. Measured on the development machine, a full step costs about 0.4 milliseconds with 40 aircraft and 1.4 ms with 128, against a budget of 16.7 ms.
Inputs are handled carefully, because both sides need to agree on exactly which inputs were applied. Each one is numbered, queued with a cushion of a couple of frames to absorb network jitter, and applied exactly once, in order. If a burst arrives, the server catches up a few steps at a time. It also counts real time, so no client can be stepped more than 60 times a second. Without that, sending inputs twice as fast would mean flying twice as fast.
One flight model, two places
The physics lives in one file, shared/flight.js, which
runs unchanged in your browser and on the server. It is written as
plain functions over numbers, with no rendering code. It models lift
from the wing at an angle to the airflow, up to a stall angle of about
15 degrees; drag, including induced drag that grows as you pull harder;
air that thins with height; a G limit for each aircraft; and thrust
that balances drag at each airframe's top speed. The handling numbers
were measured in test flights, not guessed.
Sharing the code matters for more than tidiness. Because both sides step the same function with the same fixed time step and the same inputs, your browser can predict exactly where the server will put you. The ballistics, missile guidance, damage rules and terrain are shared the same way.
Prediction: why your controls feel instant
If your browser waited for the server before moving your aircraft, every input would lag by a full round trip. That is unflyable. So the client predicts your aircraft: every frame, it steps your input through the shared flight model straight away, sends the input to the server, and keeps a copy.
Every snapshot from the server says which of your inputs it has applied so far. The client then reconciles: it resets your aircraft to the server's state — position, orientation, velocity, rotation rate, throttle, boost fuel and decouple state — and replays the inputs the server has not seen yet. Leave any of those out and the replay spends it twice; an early version did that with boost fuel and emptied the tank in half the time.
When prediction and server do disagree slightly, the correction is not shown as a jump. The client records the difference and fades it out over about a tenth of a second while your inputs continue to act immediately, so the aircraft slides back into line instead of snapping. Only a very large difference, such as a respawn, is applied at once.
Everyone else: drawn slightly in the past
Other aircraft cannot be predicted, because their inputs are on someone else's machine. Instead the client keeps the snapshots it receives and draws other aircraft a short time in the past, sliding smoothly between two snapshots it already has. That delay is at least 100 milliseconds and grows to as much as 260 ms if your connection is jittery, set from how irregularly snapshots have been arriving. If data stops altogether, the client continues each aircraft along its path for up to a quarter of a second and then holds it, rather than guessing further.
The client keeps about eight seconds of these snapshots. That is what makes the death replay possible: when you are shot down, the game winds the stored world back five seconds and plays it again from behind the aircraft that got you.
Hits, and the honest answer on lag compensation
Bullets are not sent over the network. There are far too many. A snapshot carries one bit saying an aircraft is firing, and each client draws the rounds itself. The server fires its own rounds using the same ballistics code, with drop and drag, and tests each round's path over every step against every aircraft near it, so a fast round cannot pass through a target between steps. Only the server's result counts. Missiles are different: they are in the air for seconds and have to be dodged, so their real positions are sent to everyone.
Many shooters add lag compensation: the server keeps a short history of the world and checks your shot against where the target was on your screen when you fired. JET3D has a place set aside for this — a half-second rewind buffer — but it has not been built. At present, hits are judged against where aircraft are on the server at that moment. On a good connection the difference is small. On a slow one, a crossing target is a little further along on the server than it looks to you, so close passes that look like hits can miss behind. The lead circle uses the target's movement as you see it, so it cannot fully correct for this.
What goes over the wire
Thirty times a second — every second simulation step — the server
sends each player a snapshot. It is compact binary,
not text, packed by hand in shared/protocol.js:
| PART | SIZE | HOW |
|---|---|---|
| Position | 6 bytes | Three 16-bit numbers over a 6 km span: about 9 cm precision. |
| Orientation | 4 bytes | A quaternion in "smallest three" form: drop the largest part, keep three at 10 bits each, rebuild the fourth. |
| State | 1 byte | Throttle in 5 bits, plus firing, boosting and on fire. |
| Damage | 2 bytes | One bit per damageable part, a dead bit, and a 2-bit smoke level. |
| Id and team | 1 byte | Seven bits of id, one of team, which is why a match tops out at 128 aircraft. |
That is 14 bytes per aircraft. Missiles take 8 bytes and flares 6. A header carries the match clock, phase and counts, plus the extra state your own prediction needs. The aircraft list is encoded once per snapshot, and only the personal header is changed for each player. Altogether a player receives around 7 to 11 kilobytes a second, less than a music stream. Going the other way, each input is 7 bytes: a step number, three control axes and two bytes of buttons.
Terrain from a formula
The map is never sent or downloaded. It is a function, in
shared/terrain.js, that returns the ground height at any
point, and every machine works it out for itself. The arena is 6 km
square. Several layers of smooth noise, at sizes around a kilometre
and a half, give the hills and mountains; a ridged layer adds crests;
and a power curve keeps most of the land low and peaks rare and tall,
topping out around 650 metres. Canyons are then cut in using distance
from wavy channel lines, up to 500 metres deep with flat floors, so
there is terrain to fight in, not just to avoid.
Trees and city buildings are placed by a hash of their grid cell, so every machine puts the same tower in the same place without being told. In the middle is an open plaza big enough to hold the ring's final size. Collision tests use the same 17-metre grid the visible mesh is built on, so what kills you is the surface you can see. The maps page covers the layout from a pilot's point of view.
Bots
Empty seats are filled by bots run on the server in
server/bots.js. They use the same flight model, aircraft
and weapons as players. A bot does not get teleported onto your tail
or given perfect aim: it produces stick and button inputs, and those
are run through the same physics as yours. Each one works through
modes such as seeking, pursuing, attacking, breaking, extending away
and avoiding the ground or the ring. To save processing, bots decide
20 times a second and fly at the full 60, with their decisions spread
across different steps.
Skill comes from four tiers. A ROOKIE aims with about four degrees of error, reacts in 0.7 seconds and notices enemies inside 900 metres; an ACE has 0.3 degrees of error, reacts in 0.14 seconds and sees out to 2.5 km. Difficulty settings change only which tiers are in the mix, never how a tier flies. MATCHED SQUADRON adjusts the mix as the humans win or lose, but only when a bot respawns, so nothing gets better in the middle of a fight.
Limits and housekeeping
- A match can hold up to 128 aircraft, at most 32 of them people.
- The server caps total players across all matches and the number of rooms, so a busy evening cannot slow everyone's game down. When it is full you can watch, and an observer is offered the next free seat.
- Every connection is pinged every ten seconds, and one that misses two in a row is closed so its seat does not stay taken.
If you came here because a match felt wrong, the FAQ covers lag and performance in practical terms. For the real-world air combat that the rules borrow from, read the history page.
BACK TO THE GAMEPilot's library · How to fly · About · What changed · Privacy · Terms