Ir al contenido
Ayotl

← Todas las notas

11 de septiembre de 2026 · En Daily Challenge

El cliente nunca manda un número

Un juego con tabla de puntuaciones en el navegador es un formulario abierto a quien sepa abrir la consola. La salida no fue ofuscar: fue dejar de creerle al cliente.

Daily Challenge publica un reto distinto cada día y un top 10 a medianoche. Si el navegador mandara «hice 24 680 puntos», cualquiera cambiaría ese número en dos segundos, y una tabla que se puede falsear no vale nada: el juego entero deja de tener sentido.

Lo que sube el navegador no es el puntaje, es el registro de teclas: qué se pulsó y en qué tick. El servidor vuelve a correr la partida con ese registro y calcula el puntaje él mismo. Para que eso funcione, la simulación tiene que ser determinista: física en enteros, nada de números al azar sin semilla, y el mismo código corriendo en los dos lados.

// 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
}
El endpoint /api/score corre esto y guarda su resultado, no el del jugador.

El determinismo no se cuida solo, así que hay una prueba que lo vigila: un bot aleatorio juega los veinticuatro minijuegos y comprueba que reproducir el registro da exactamente el mismo puntaje. Si alguien mete un `Math.random()` suelto o una física con decimales, la prueba se cae antes de llegar a producción.

La misma idea sirvió para dos cosas más. La cámara fantasma —ver la partida de otro mientras esperas— no manda vídeo: manda los mismos inputs por un canal en tiempo real y cada espectador re-simula. Y el versus se juega en lockstep: el anfitrión reparte los inputs de todos cada seis ticks, así que nadie diverge, y al final el servidor re-simula la partida conjunta. Un solo mecanismo bien hecho pagó tres funciones.

Todas las notas