Skip to content
Docs

The greengames tool

The commands crear, validar, probar, empaquetar, previsualizar, capturar and limites, with their syntax, options, exit codes, JSON output and real console output.

DescribesFormat 1, API 1 For those who write code

On this page

greengames is the command-line tool for making games: it creates the folder, validates it, tests it with the bot, packages it and shows it in the browser.

The commands and their options are in Spanish, and the tool prints in Spanish by default. The console output on this page is real and is shown as it is printed, not translated. The JSON and code examples also come from real runs and files.

How to run it#

The tool lives in the Green Games repository. It runs with Node 22.18 or later, from the root of the repository, after npm install. There is no package published in a registry and no installer.

sh
node packages/toolkit/bin/greengames.js validar mi-juego
npm run greengames -- validar mi-juego     # lo mismo

On this page, greengames is short for node packages/toolkit/bin/greengames.js. Nothing needs to be compiled: the tool is TypeScript that Node runs directly.

Almost every command takes a game, which you can give in three ways:

FormExampleWhat it takes
A sluggreengames validar mi-juegoThe folder games/mi-juego of the project.
A pathgreengames validar /ruta/a/mi-juegoThat folder, wherever it is.
--todosgreengames validar --todosAll the games in games/.

The project is the nearest directory upward, from where you are standing, that has a games/ folder and a package.json. If there is none, it is the Green Games repository itself.

Summary#

CommandWhat it does
greengames crear <slug> [--tipo <tipo>] [--nombre "Nombre"]Creates games/<slug> from a template.
greengames validar <slug | ruta | --todos>Checks the manifest, scenes, script, bot script and limits.
greengames probar <slug | ruta | --todos>Plays the match with the bot, with no screen, on each difficulty.
greengames empaquetar <slug | ruta | --todos> [-o archivo.ggx]Writes the .ggx file. It validates first.
greengames previsualizar <slug | ruta>Local server with the game player; it reloads when you save.
greengames capturar <slug | ruta>Screenshots of the game played by its bot. It needs Docker.
greengames limitesShows the limits in force.
greengames importar <archivo | carpeta>... [--juego <carpeta>]Converts models, textures, sounds and packages from other tools.

Common options:

OptionCommandsWhat it does
--jsonall except crear and previsualizarWrites a single JSON object instead of text.
--sin-tiposvalidar, probar, empaquetarDoes not check the script's types with TypeScript. It is faster and checks less.
--idioma envalidarValidator messages and suggestions in English.

Exit codes#

Every command ends with code 0 if there are no errors and 1 if there are: an invalid game, a failed test, a game that does not exist or a badly written command. Warnings do not change the code: a game that is "valid with 2 warnings" ends with 0.

crear#

sh
greengames crear <slug> [--tipo <tipo>] [--nombre "Nombre"]
greengames crear torre-de-cajas --nombre "Torre de cajas"
greengames crear salto-de-rana --tipo plataformas-2d --nombre "Salto de rana"

It copies a template to games/<slug> of the project, sets the slug as the id of the manifest and, if you gave --nombre, uses it as the name in both languages (change the English one by hand afterward). Without --nombre, the third-person template takes the name from the slug (torre-de-cajas gives "Torre De Cajas") and the others keep their own.

  • The slug has only lowercase letters, numbers and hyphens.
  • If the folder already exists, it does not touch it and ends with an error.

--tipo picks the template. Without the option, it is tercera-persona.

--tipoWhat it creates
tercera-personaThe usual game: collect six coins against the clock, with the camera behind the avatar.
primera-personaA game seen through the avatar's eyes. Made with pieces.
plataformas-2dA side-on level, with the lateral view, a tile map and sprites.
cenitalA game seen from above, with a ground tile map and sprites.
isometricoA game in the isometric view. Made with pieces.
hibridoThird person with one zone played from the side on a tile map and another one in isometric view.
multijugadorA coin race for 1 to 8 players, which declares multijugador in the manifest.
mundo-abiertoA world of cubes the engine generates, with biomes, where blocks are broken and placed. See Open world.

What each view is, sprites and maps: Scenes. With a type that does not exist, the command ends with an error and lists the ones there are:

greengames crear x --tipo nada
No existe el tipo de juego "nada". Tipos: tercera-persona, cenital, hibrido, isometrico, multijugador, mundo-abierto, plataformas-2d, primera-persona

Real output, run in a test project:

Output of greengames crear
Juego creado en games/torre-de-cajas

  juego.json              manifiesto: nombre, descripcion, dificultades, textos
  escenas/principal.json  el mundo: piezas, apariciones, zonas, luces
  scripts/principal.ts    la logica, con la API de @greengames/sdk
  recursos/portada.svg    portada 16:9
  pruebas/bot.json        guion del bot que juega y gana la partida

Siguientes pasos:
  greengames validar torre-de-cajas
  greengames probar torre-de-cajas
  greengames previsualizar torre-de-cajas

Cambia el nombre en ingles y la descripcion en juego.json. Guia completa: docs/GUIA-AUTOR.md

The newly created game is a small, complete game that already passes validar and probar. The third-person one is collecting six coins against the clock, with lava on two difficulties. The templates with sprites also bring the laminas/ folder, which that list does not name.

validar#

sh
greengames validar <slug | ruta | --todos> [--json] [--sin-tipos] [--idioma en]

It checks, in this order: that the files exist and are JSON; that the script can be joined into a single file; the script's types with TypeScript (which only knows standard JavaScript and the API: fetch or setTimeout are type errors); the schemas; and the rules of the contract.

Each problem is an error (it blocks packaging and testing) or a warning, printed as AVISO (worth reading; it blocks nothing). What each code means: The validator.

Real output on a copy of patio-de-monedas, the example game made with the editor:

greengames validar patio-de-monedas
patio-de-monedas: valido

And on another copy to which three defects were added on purpose: a call to fetch, a piece that does not exist and the spawn point moved off the floor.

greengames validar patio-de-monedas (with defects)
  ERROR GB330 scripts/principal.ts > linea 78, columna 2: TS2304: No se encuentra el nombre 'fetch'. "fetch" no existe dentro de la sala. Los juegos no tienen pagina ni red: usa los servicios de @greengames/sdk.
  ERROR GB310 scripts/principal.ts > linea 78: el script usa fetch (`fetch(`), que no existe dentro de la sala. Los juegos no tienen red. Empaqueta los datos en la escena o en `datos` de las piezas.
  ERROR GB321 scripts/principal.ts > linea 79: el script nombra la pieza "puerta", que no esta en ninguna escena. Ids disponibles: suelo, meta-poste, meta-poste-2, meta-cinta, moneda, moneda-2, moneda-3, moneda-4, moneda-5, caja, caja-2, peligro.
  AVISO GB207 escenas/principal.json > apariciones[0].posicion: no hay ninguna pieza solida bajo la aparicion "inicio" [0, 0, -40]: el jugador caeria al vacio. Pon la aparicion sobre una plataforma: su "posicion" es donde quedan los pies.
patio-de-monedas: 3 error(es) y 1 aviso(s)

How to read a line: level, code, file, place inside the file, message and, at the end, the suggestion for fixing it. Script problems carry the file and the line of your source code, not of the joined script.

The tool's messages are printed without accents.

probar#

sh
greengames probar <slug | ruta | --todos> [--json] [--dificultad ids] [--semilla n,...] [--semillas N] [--ganar K] [--guion nombres] [--jugadores N] [--registro]

It validates the game, packages it into a .ggx, opens it again and plays the match with no screen with the bot, on each difficulty and with two seeds (1 and 2), or the ones the bot script says. What a run must meet to pass: Testing with the bot.

OptionWhat it does
--dificultad <ids>Only those difficulties, separated by commas.
--semilla <n,...>Only those seeds: --semilla 3,7.
--semillas <N>Seeds 1 to N: --semillas 10.
--ganar <K>It is enough for K runs of each difficulty to pass, as long as none has game errors. For games with a lot of randomness.
--guion <nombres>Only those bot scripts: principal (pruebas/bot.json) or the <nombre> of pruebas/bot-<nombre>.json.
--jugadores <N>Puts N players in the room, all following the bot script. For multiplayer games.
--registroShows everything the game writes with juego.registro. Without the option, only the last lines of the failing runs are shown.

Real output on the copy of patio-de-monedas:

greengames probar patio-de-monedas
  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
  OK    normal semilla 1: victoria, puntaje 2, 3.0 s de partida en 31 ms; script 0.018 ms/cuadro (max 0.41), 12 piezas
  OK    normal semilla 2: victoria, puntaje 2, 3.0 s de partida en 66 ms; script 0.022 ms/cuadro (max 0.65), 12 piezas
  OK    dificil semilla 1: victoria, puntaje 2, 3.0 s de partida en 80 ms; script 0.024 ms/cuadro (max 0.67), 12 piezas
  OK    dificil semilla 2: victoria, puntaje 2, 3.0 s de partida en 73 ms; script 0.020 ms/cuadro (max 0.81), 12 piezas
patio-de-monedas: prueba aprobada (6 de 6 corridas, .ggx de 24.2 KB)

Each line says: difficulty and seed; how it ended and with what score; how many seconds of match were simulated and in how many real milliseconds; the average script time per frame and the maximum of one frame; and the maximum number of live pieces. The real milliseconds depend on your computer; the rest does not.

A failed test says why. Here the bot script of that copy was changed to send the bot to a point off the floor:

greengames probar patio-de-monedas --dificultad normal (bot script with an impossible destination)
  FALLA normal semilla 1: no termino, 16.0 s de partida en 106 ms; script 0.008 ms/cuadro (max 0.71), 12 piezas
        - el bot se atasco en el paso 2 de 3 {"ir":[20,0,-6],"tiempoMax":5}: no llego al destino (faltan 16.1 unidades en horizontal y -0.0 en vertical). El bot quedo en [4.04, 0.02, -8.39] a los 6.0 s.
  FALLA normal semilla 2: no termino, 16.0 s de partida en 103 ms; script 0.007 ms/cuadro (max 0.64), 12 piezas
        - el bot se atasco en el paso 2 de 3 {"ir":[20,0,-6],"tiempoMax":5}: no llego al destino (faltan 16.1 unidades en horizontal y -0.0 en vertical). El bot quedo en [4.04, 0.02, -8.39] a los 6.0 s.
  FALLA normal: aprobaron 0 de 2 corridas (se piden 2)
patio-de-monedas: prueba FALLIDA (0 de 2 corridas, .ggx de 24.2 KB)

And here the bot script expected a defeat and the bot won:

greengames probar patio-de-monedas --dificultad normal --semilla 1 (bot script that expects to lose)
  FALLA normal semilla 1: victoria, puntaje 1, 3.0 s de partida en 71 ms; script 0.019 ms/cuadro (max 0.70), 12 piezas
        - la partida termino en "victoria" y el guion esperaba "derrota"
  FALLA normal: aprobaron 0 de 1 corridas (se piden 1)
patio-de-monedas: prueba FALLIDA (0 de 1 corridas, .ggx de 24.2 KB)

Two other cases:

  • If the game does not pass validation, probar prints the validator's report and ends with "no se probo porque no pasa la validacion" (it was not tested because it does not pass validation).
  • If pruebas/bot.json is missing, it says so and ends with an error.

With several bot scripts, each line carries the name of the bot script in square brackets: [principal], [derrota].

With --jugadores, the match runs with that many players. Real output on a game just created with greengames crear arena-de-monedas --tipo multijugador:

greengames probar arena-de-monedas --jugadores 4 --dificultad normal
  OK    normal semilla 1: victoria, puntaje 1600, 9.5 s de partida en 3884 ms; script 0.048 ms/cuadro (max 0.98), 16 piezas
  OK    normal semilla 2: victoria, puntaje 1600, 9.7 s de partida en 4021 ms; script 0.049 ms/cuadro (max 1.26), 16 piezas
arena-de-monedas: prueba aprobada (2 de 2 corridas, .ggx de 3.1 KB)

empaquetar#

sh
greengames empaquetar <slug | ruta | --todos> [-o archivo.ggx] [--json] [--sin-tipos]

It validates and, if there are no errors, writes the .ggx. Without -o, it leaves it in dist/juegos/<slug>.ggx of the project. -o is valid when you package a single game.

greengames empaquetar patio-de-monedas -o patio-de-monedas.ggx
patio-de-monedas: patio-de-monedas.ggx (24.2 KB)

With errors it writes nothing: it prints the validator's report and ends with

consola
patio-de-monedas: no se empaqueto. Corrige los errores y vuelve a intentar.

It does not write a .ggx heavier than 5 MB either. What is inside the file: The .ggx format.

previsualizar#

sh
greengames previsualizar <slug | ruta> [--puerto N] [--anfitrion 0.0.0.0]

It starts a local server with the game player and your game, and shows the address. When you save a file of the game, the page reloads by itself.

  • Port. By default it uses a port of its own for each game, between 4400 and 4899, derived from the slug; if it is taken it tries the next 20. With --puerto N it uses that one and, if it is taken, ends with an error.
  • Who can see it. It listens on 127.0.0.1: only your computer. With --anfitrion 0.0.0.0 it accepts connections from other devices on your network, for example to test on a phone.

The page accepts parameters in the address:

ParameterExampleWhat it does
autoauto=1Starts the match without going through the start screen. With auto=1 the page does not reload when you save, so as not to cut the match short.
pilotopiloto=botThe game's bot script drives the player.
dificultaddificultad=facilDifficulty of the match.
semillasemilla=7Seed of the match, to repeat it identically.
modomodo=sala-localMatch with bots.
idiomaidioma=enLanguage of the game player and of the game's texts.
localesauto=1&locales=2With auto=1: how many people play on this computer, from 1 to 4. It opens the split or shared screen. Without auto, you choose on the start screen if the game declares multijugador.local.
recargarecarga=0Turns automatic reload off always.

capturar#

sh
greengames capturar <slug | ruta> [--dificultad id] [--semilla n] [--segundos 3,12,30] [--tamano 1280x720] [--movil] [--locales N] [--sin-fin] [-o dir]

It takes screenshots of one game played by its bot. It starts the preview, opens a headless browser inside a container (that is why it needs Docker) and leaves, by default in capturas/<slug>/ of the project:

  • one image for each second requested (by default, at 3, 12 and 30 seconds);
  • another of the end panel, except with --sin-fin;
  • a JSON report with the result, the game's log, the console errors and the frames per second.
OptionWhat it does
--segundos 3,15,40At which seconds of the match to capture.
--tamano 1280x720Size of the window. By default 1280 x 720.
--movilCaptures as a phone in portrait (390 x 844).
--locales NCaptures with N people on the same computer (split or shared screen).
--sin-finDoes not wait for the end panel.
-o dirOutput folder.

The frames per second in that report come from a browser with no graphics card: they are only indicative. Figures measured with and without a graphics card: Performance.

limites#

sh
greengames limites [--json]

It prints the limits in force, one per line, with the name they have in the code. Beginning of the real output:

greengames limites
paqueteBytes                 5242880
paqueteDescomprimidoBytes    20971520
scriptBytes                  524288
recursoBytes                 2097152
escenas                      16
piezasPorEscena              2000
piezasVivas                  2500
piezasDinamicas              150

The full table, explained: Limits.

importar#

sh
greengames importar <archivo | carpeta>... [--juego <carpeta del juego>] [opciones]
greengames importar --ayuda

Converts models, textures, sounds and packages from other tools into game assets, like the editor's importer. It only reads files that are already on your computer: it downloads nothing.

Without --juego it only shows the report of what it would do. With --juego it writes modelos/, texturas/ and sonidos/, and adds the credits to creditos.json; if an id already exists, the new asset is renamed (arbol-2). With --escenas it also writes each converted scene as escenas/<id>.json. If the author or the license is missing and is not detected in the files, it writes nothing and exits with code 2.

OptionWhat it does
--juego <carpeta>The game folder to write into.
--escenasAlso writes the converted scenes.
--jsonThe report as JSON.
--triangulos <n>Maximum triangles per model, up to 20000.
--textura <n>Maximum texture side in pixels: 512 by default, up to 1024.
--estilo, --colores <n>Green Games style: flat shading, pixelated textures and a reduced palette (16 colors by default).
--cubos <n>Converts to cubes: how many along the longest side, from 4 to 64.
--escala <x>, --alto <x>Scale factor, or final height in world units (the avatar is 1.8 tall).
--arriba y|z, --girar-frenteThe file's vertical axis, and a half turn for models that face -Z.
--colision <tipo>caja (default), capsula, envolvente or ninguna.
--autor, --licencia, --origenWho made the files, the license you got them under (CC0-1.0, CC-BY-4.0, propia…) and where they came from.
sh
greengames importar descargas/kit --juego games/mi-juego --colision envolvente --origen "Kit de plataformas"
greengames importar heroe.fbx --juego games/mi-juego --alto 1.8 --colision capsula --autor "Ana" --licencia CC-BY-4.0
greengames importar roca.glb --estilo --colores 8

The formats it accepts and what it does with each: Import assets. The format of what it writes: Models, textures and sounds.

JSON output#

With --json, each command writes a single object with one key per game. It is useful for your own scripts and for agents.

validar --json gives, per game, { valido, errores, avisos }. Each problem always has the same shape. Real output of the game with defects above, trimmed to one error and the warning:

greengames validar patio-de-monedas --json (trimmed)
{
  "patio-de-monedas": {
    "valido": false,
    "errores": [
      {
        "nivel": "error",
        "codigo": "GB321",
        "archivo": "scripts/principal.ts",
        "ruta": "linea 79",
        "mensaje": "el script nombra la pieza \"puerta\", que no esta en ninguna escena",
        "sugerencia": "Ids disponibles: suelo, meta-poste, meta-poste-2, meta-cinta, moneda, moneda-2, moneda-3, moneda-4, moneda-5, caja, caja-2, peligro."
      }
    ],
    "avisos": [
      {
        "nivel": "aviso",
        "codigo": "GB207",
        "archivo": "escenas/principal.json",
        "ruta": "apariciones[0].posicion",
        "mensaje": "no hay ninguna pieza solida bajo la aparicion \"inicio\" [0, 0, -40]: el jugador caeria al vacio",
        "sugerencia": "Pon la aparicion sobre una plataforma: su \"posicion\" es donde quedan los pies."
      }
    ]
  }
}
FieldWhat it is
nivelerror or aviso (warning).
codigoStable between versions: GB001, GB210...
archivojuego.json, escenas/principal.json, scripts/principal.ts, pruebas/bot.json.
rutaPlace inside the file: piezas[3].tamano or linea 40, columna 16.
mensajeWhat is wrong.
sugerenciaOptional: how to fix it.

With --idioma en, mensaje and sugerencia change; codigo, archivo, ruta and nivel stay the same.

probar --json gives, per game, { aprobada, corridas, grupos, bytesGbx }. Real output of one run of patio-de-monedas, trimmed to the most used fields:

greengames probar patio-de-monedas --dificultad normal --semilla 1 --json (trimmed)
{
  "patio-de-monedas": {
    "aprobada": true,
    "corridas": [
      {
        "guion": "principal",
        "dificultad": "normal",
        "semilla": 1,
        "aprobada": true,
        "grave": false,
        "fallas": [],
        "resultado": {
          "termino": true,
          "resultado": "victoria",
          "puntaje": 2,
          "segundos": 3.1166666666666667,
          "errores": [],
          "avisos": [],
          "bot": { "pasos": 3, "hechos": 2, "atascado": null },
          "registro": []
        }
      }
    ],
    "grupos": [
      { "guion": "principal", "dificultad": "normal", "aprobadas": 1, "total": 1, "necesarias": 1, "aprobado": true }
    ],
    "bytesGbx": 24718
  }
}

fallas is the list of reasons, as text, why a run did not pass: the script error with its line, the exceeded limit or where the bot got stuck. grave is true when there were game errors or exceeded limits: those runs make the test fail even if --ganar forgives others.

empaquetar --json gives, per game, { empaquetado, archivo, bytes, avisos }, or { empaquetado: false, ... } with the validator's report.

As a library#

What the tool does can be called from your own Node code (22.18 or later), inside the repository:

ts
import { revisarJuego, probarPaquete } from '@greengames/toolkit';

const { informe, lectura } = await revisarJuego('games/mi-juego');   // informe: { valido, errores, avisos }
if (informe.valido) {
  const prueba = await probarPaquete(lectura.paquete);               // { aprobada, corridas, grupos, bytesGbx }
}
FunctionFromWhat it does
revisarJuego(dir)@greengames/toolkitReads the folder and validates it, types included. Returns the report and the built package.
leerJuego(dir)@greengames/toolkitOnly reads the folder and joins the script. Returns the Paquete.
probarPaquete(paquete, opciones)@greengames/toolkitThe same test as greengames probar.
validarPaquete(paquete, { idioma })@greengames/sdk/contratoThe validator, without types. It also runs in the browser.
empaquetarGbx(paquete), desempaquetarGbx(bytes)@greengames/sdk/contratoThey go from a Paquete to the bytes of the .ggx and back.
armarPaquete(archivos)@greengames/sdk/contratoBuilds a Paquete from a map ruta -> contenido (path to content), with no tools. It requires the script to be a single .js file.

These modules are not published in a registry either: you import them inside the repository.

Next#