ROBINHEAD DOCS Play ↗
Fair play

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.

CLIENT (browser) reads keys → input frame predicts YOUR player only interpolates ball + rival SERVER (authority) validates + rate-limits input simulates at 120 Hz delta snapshots at 60 Hz { seq, left, right, jump, kick, super } snapshot + last processed seq
Both sides run the same shared/physics.js; the server's result is the only one that counts.
RateWhat
120 HzPhysics step (1/120 s), identical on client and server
60 HzSnapshot broadcast to clients
cappedInput frames per player are rate-limited; the excess is dropped
~3 KB/sMeasured 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.

CLIENTSERVER 1 press + apply now2 keep inputs by seq4 rewind + replay 3 simulate input input seq=n (½ RTT) snapshot + ack n (½ RTT) error? smooth it, never snap your own character never waits for the round trip
Each input carries a sequence number; every snapshot returns the last one the server processed.
1 · Prediction

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.

2 · Reconciliation

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.

3 · Interpolation

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.js uses no Date.now(), no Math.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 y at 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.
RobinHead Ball is a game of skill. Staking tokens carries risk — never stake more than you can afford to lose. You are responsible for complying with the regulations in your jurisdiction.