Skip to content
Ayotl

← All notes

September 11, 2026 · In Daily Challenge

The client never sends a number

A browser game with a leaderboard is an open form for anyone who can open the console. The fix wasn't obfuscation: it was to stop believing the client.

Daily Challenge posts a different challenge every day and a top 10 at midnight. If the browser sent “I scored 24,680”, anyone could change that number in two seconds, and a leaderboard you can fake is worthless: the whole game stops making sense.

What the browser uploads isn't the score, it's the input log: which key was pressed on which tick. The server replays the run from that log and computes the score itself. For that to work the simulation has to be deterministic: integer physics, no unseeded randomness, and the same code running on both sides.

// public/arcade/verify.js — el mismo módulo que usa el navegador
export function verify(juego, mapa, semilla, log) {
  const estado = crear(juego, mapa, semilla);   // misma semilla, mismo mundo
  for (const tick of log) avanzar(estado, tick);
  return estado.puntaje;                        // esto es lo que vale
}
The /api/score endpoint runs this and stores its result, not the player's.

Determinism doesn't keep itself, so a test watches it: a random bot plays all twenty-four mini-games and checks that replaying the log yields exactly the same score. If someone slips in a loose Math.random() or floating-point physics, the test fails before it ships.

The same idea paid for two more things. The ghost camera — watching someone else's run while you wait — doesn't stream video: it broadcasts the same inputs over a realtime channel and each viewer re-simulates. And versus runs in lockstep: the host hands out everyone's inputs every six ticks, so nobody diverges, and at the end the server replays the joint match. One mechanism, done properly, paid for three features.

All notes