Skip to content
Docs

Determinism: the same match, twice

Why a Green Games match repeats identically with the same seed and the same inputs, what breaks that, how it is checked and what to do if the game is not reproducible.

DescribesFormat 1, API 1 For everyone

On this page

A Green Games match is reproducible: with the same seed and the same inputs, exactly the same thing happens, frame by frame. The coins land in the same places, the ball bounces the same way and the final score is the same. That is called determinism.

It is not a technical detail: it is what allows a bot to test your game and makes the result of that test mean something.

What it is made of#

Sixty fixed frames#

The simulation advances at 60 frames per second, always. Every frame lasts the same: the time step dt is 1/60 on every computer. If your screen draws faster or slower, what changes is how many times it draws, not how the match advances.

That is why a test with no screen can run a match of several minutes in one or two seconds: it computes the same frames, one after another, without waiting for the wall clock.

The seed#

Every match has a seed: a number that all the randomness comes from. With seed 1, "chance" always gives the same list of numbers; with seed 2, another list, always the same one too.

The script reads it in juego.semilla, but it is almost never needed: using the azar service is enough.

The azar service#

azar is the way to ask for random numbers that come from the seed: azar.numero(), azar.entero(min, max), azar.elegir(lista), azar.mezclar(lista), azar.suerte(p). Reference: azar.

This is how Circuito Luz Verde scatters its coins (games/circuito-luz-verde/scripts/principal.ts). The code comes from the real file, with its comment in Spanish:

scripts/principal.ts (excerpt)
	// azar sale de la semilla de la partida: con la misma semilla, las monedas quedan en los mismos lugares.
	const elegidos = azar.mezclar(LUGARES).slice(0, AJUSTES.monedas);

Inside the room, Math.random also comes from the seed. But the validator warns if you use it: azar makes it clear, to whoever reads the script, that this randomness is the match's.

The match clock#

Time is read with reloj.ahora (seconds of match, which stop with the pause) and reloj.cuadro. To wait, reloj.despues; to repeat, reloj.cada. They all count frames of the simulation, not real time. Reference: reloj.

The room's own math#

There is a less obvious source of differences. Functions like Math.sin or Math.pow do not give exactly the same result in every JavaScript engine: they differ in the last bit between versions of Node and between Node and the browser. It is an invisible difference, until a game with physics amplifies it: a ball that bounces slightly differently ends up somewhere else, and the match changes.

That is why, inside the room, Math.sin, cos, tan, atan, atan2, asin, acos, exp, log, pow, hypot, cbrt and related functions are the room's own versions, which give the same result everywhere. They match the usual ones to about 14 digits. You do not have to do anything: you write Math.sin and you use the reproducible version.

What is not reproducible#

Two things, and nothing else:

WhatWhyWhat to use
Date (Date.now(), new Date())It reads the real date and time, which change in every match.reloj.ahora
performance.now()It measures the computer's real time.reloj.ahora, reloj.cuadro

The validator warns if the script uses them.

There is a third way to break reproducibility without using anything forbidden: depending on the order in which an object hands over its keys when you do not control that order. If you go through a collection to decide something, go through it in an order that you define.

What does change between two matches, on purpose, is the inputs: what the player does. Determinism does not mean that all matches are the same; it means that, if you repeat the same inputs, the match repeats.

Why it matters#

  • Bot tests. greengames probar plays your game with a bot script. If the match were not reproducible, a test that passes today could fail tomorrow without anyone touching anything, and a failure could not be repeated to study it. With a seed, "it fails with seed 7 on hard" is an exact instruction for seeing it again.
  • What is tested is what is played. The result of a match is the same in Node, where the tool runs, and in the browser, where the portal runs.
  • Repeating a match. The game player accepts a seed at the start, and the tool's preview accepts it in the address (semilla=7). With the bot in control, that reproduces the whole match.

The same design is what would make playing over the network possible, because the match is decided in a single place and with rules that do not depend on the computer. Today there is no network multiplayer; what is in development is listed in the Roadmap.

How it is checked#

In every test of your game#

greengames probar runs the game on each difficulty and with each seed. In addition, once per game, it repeats the first run with the same seed and compares the score and a fingerprint of the final state (the frame it ended on, how many pieces were left and the position of each player to four decimals). If anything differs, the test fails with this message, which the tool prints in Spanish:

Shape of the message
el juego no es reproducible: con la misma semilla dio <puntaje> puntos en <segundos> s y despues <puntaje> en <segundos> s. Usa azar y reloj en vez de Date

It says that the game is not reproducible, that the same seed gave one score and time and then another, and that you should use azar and reloj instead of Date.

In the output of probar you can see reproducibility at a glance. This one is real, from the example game patio-de-monedas: the two seeds give the same result because that game uses no randomness.

greengames probar patio-de-monedas (the first two lines)
  OK    facil semilla 1: victoria, puntaje 2, 3.0 s de partida en 72 ms; script 0.022 ms/cuadro (max 0.71), 12 piezas
  OK    facil semilla 2: victoria, puntaje 2, 3.0 s de partida en 71 ms; script 0.019 ms/cuadro (max 0.69), 12 piezas

The "s de partida" (seconds of match) and the score always repeat. The real "ms" and the script times change from one run to another: they measure your computer, not the match.

Between Node and the browser#

The Green Games repository has a test, pruebas/determinismo.sh, that does the following with each of the seven games in the catalog:

  1. It runs the game in Node, on each difficulty, with seeds 1 and 2, and notes the result, score, duration and fingerprint of the final state.
  2. It repeats the same runs in a headless browser, inside the isolated room.
  3. It demands that the four things be identical in each run. With a single one different, it fails.

That is 42 runs: seven games, three difficulties, two seeds.

If the game is not reproducible#

When greengames probar says "el juego no es reproducible", check in this order:

  1. Read the warnings of greengames validar. It warns about Date, performance.now and Math.random. Start there.
  2. Replace real time with the match's time. Every Date.now() becomes reloj.ahora.
  3. Replace Math.random with azar.
  4. Look for traversals with no defined order. If you decide something by going through the keys of an object, sort them yourself first.
  5. Test again with more seeds: greengames probar mi-juego --semillas 10.

It is a different matter if the bot wins with one seed and loses with another. That is not a lack of determinism: it is a game with randomness and a bot script that cannot withstand it. You fix it by improving the bot script, or by declaring in it how many runs must pass. See Testing with the bot.

Next#