Netcode
Money is on the line, so two things must hold: the client cannot be trusted with the score, the ball or the clock, and ping must not decide the match. Everything below follows from those two rules.
Authoritative server#
The client never simulates the real match. It sends inputs; the server owns the world and sends back snapshots. Peer-to-peer was rejected: either one player is the host and can cheat, or both simulate and disagree with no tiebreaker.
shared/physics.js; the server's result is the only one that counts.| Rate | What |
|---|---|
| 120 Hz | Physics step (1/120 s), identical on client and server |
| 60 Hz | Snapshot broadcast to clients |
| capped | Input frames per player are rate-limited; the excess is dropped |
| ~3 KB/s | Measured per-player bandwidth after delta compression (5.3 KB/s before) |
Making ping fair#
Three standard techniques, layered. Together they make a 140 ms player competitive with a 40 ms one.
Instant self-movement
Your input is applied immediately instead of waiting for the round trip. Only your own player is predicted. An early version predicted everything and the two simulations fought, jittering visibly.
Replay on correction
On each snapshot the client rewinds to the server state, replays inputs the server has not yet acknowledged and eases out any difference.
Smooth ball and rival
The ball and opponent are drawn about 33 ms in the past between the two bracketing snapshots, using local arrival timestamps, not server clocks.
No lag compensation, on purpose#
Shooters rewind the world to what the shooter saw. That is right for hitscan weapons and wrong here: a high-ping player could head a ball that, on the server, had already moved on, and it would read as "the ball teleported away." Contact is resolved in server-present time, so the ball behaves identically for both players. The cost is that a high-ping player's own input lands slightly later. For a 60-second match with a payout, consistency beats responsiveness. Matchmaking also prefers similar pings to shrink the gap before it matters.
Deterministic by construction#
shared/physics.jsuses noDate.now(), noMath.random()and no DOM. Randomness comes from a seeded PRNG, so loot drops agree everywhere.- Positions use different conventions: the server keeps a player's
yat the feet; the client at the head centre. Conversion is explicit. - A parity test reads the reference constants straight out of the client and fails the build if the server physics ever drifts.
- Snapshots are delta-compressed; a reconnecting player gets a fresh keyframe and their input counter restarts, so they are never blind after a drop.