Skip to content
Work in progress. These docs describe Spawnite at launch, and some parts are still being built.

How your agent works on your game

Your agent builds your game in a loop: it writes one small piece, runs the game with no window, reads what happened as data, and looks at a screenshot before it tells you a step is done. For something timed, it steps the game to a condition or tiles the frames after a key press. For a room game, it opens a page for each of several players, all in one room. When you see something wrong while you play, you press F8, and the agent opens that moment to read what happened and look at the game’s canvas as you saw it.

Each step below is a real command on the example game, with what it printed. Your agent runs each command in your game’s folder, or calls the MCP tool named beside it; Connect your agent lists the tools by task. The commands on this page need no editor open and no sign-in: your game is a folder of files, and publishing is what signs in.

Before it builds, your agent says what done looks like: a test that should pass, for a rule, or one sentence and the camera it will shoot from, for a look. Your game’s AGENTS.md asks it to.

Then it writes the piece. Where the engine has a scaffold, it starts from one. add_behaviour, or spawnite add behaviour Glow, writes src/behaviours/Glow.tsx with the behaviour’s trait and component, and a page for it at wiki/behaviour/glow.md, which the devtools’ Wiki tab shows beside it. add_entity, add_scene, add_npc, add_hud and the other add_ tools do the same for their kind. For anything else it writes the file by hand, from the engine’s components and the page for each in these docs.

Before it calls a step done, it runs pnpm check in the game: TypeScript and ESLint with the engine’s rules. A finding from those rules names the engine export to use instead of hand-written code.

spawnite simulate, or the MCP’s simulate, mounts one scene in Node with no browser and no GPU, steps it for a number of seconds of game time with keys held, and prints the world as JSON. Here it walks the example’s character forward for 5 s on the run scene, and reads only the wallet:

Terminal window
spawnite simulate --scene run --seconds 5 --keys w --fields wallet
Stepped 300 steps.
Hash 1865335933, stepped in 456 ms.
{
"1": {
"wallet": {
"coins": 3
}
}
}

The game steps 60 times a second, so 5 s is 300 steps. The dump’s keys, 1 here, are entity ids: entity 1 is the player, who holds the wallet. The three coins are in the wallet, so the walk reached all of them. The command also prints the plugins the world was made from and every system of its step, left out here. Through the MCP, the same run is simulate with { "scene": "run", "seconds": 5, "keys": ["w"], "fields": ["wallet"] }.

The hash is a hash of every entity’s traits after the last step, as the dump lists them. With the same code, the same scene, started from its own beginning, with the same keys and seconds gives the same hash every time. So when your agent changes code that should not change the game’s world, such as a tidy-up, an unchanged hash shows that every trait came out the same, for that scene, those keys and those seconds. State a game keeps outside its traits, such as a module’s variable, is not in the hash.

A call takes a few seconds, most of it loading the scene: the steps themselves take well under a second here. No model or sound loads and no UI mounts in a headless run, so the dump holds no view: your agent reads the game’s state, such as a round’s, off the dump.

A wait of a few seconds before a read lands on a different frame from one run to the next. Your agent waits for a condition on the world instead, a JavaScript expression over its entities, and the run stops on the first step where it holds:

Terminal window
spawnite simulate --scene run --keys w --until "count('pickup') === 0" --fields wallet,round.state
The condition held on step 76; it returned true.
Hash 693284281, stepped in 228 ms.
{
"1": {
"wallet": {
"coins": 3
}
},
"5": {
"round": {
"state": "won"
}
}
}

count('pickup') counts the entities that still hold a Pickup, so the condition holds once the last coin is taken. That is step 76, about 1.3 s of game time, and the round is won on the same step. The run gives up after 10 s of game time unless --budget names another number of seconds of game time, and the command then exits with code 1.

The expression sees the world’s dump, the same JSON --fields narrows, through the following names:

  • entities: every entity’s traits, by entity id, then by trait name.
  • count(trait): how many entities hold that trait.
  • has(id, trait): whether one entity holds that trait.
  • player(): the traits of the player’s character, the entity that holds controlled.
  • steps: the steps taken since the command began.

In a playtest, the expression also reads the engine’s camera, loop, cursor and room. A trait is read under the name the dump gives it, such as wallet or pickup, and a field the entity may not have yet is read with ?.. The player entity holds the wallet, not her character, in simulate and a playtest alike, so a condition reads it as Object.values(entities).some((e) => e.wallet?.coins >= 2), not through player().

Two more conditions on the example’s run scene, each with W held. The first stops once the player holds two coins, and the second once the character has walked 5 m forward, where forward is toward negative z:

Terminal window
spawnite simulate --scene run --keys w --until "Object.values(entities).some((e) => e.wallet?.coins >= 2)" --fields wallet
spawnite simulate --scene run --keys w --until "player().transform.position[2] < -5" --fields transform.position

The first line each printed, with the dumps left out:

The condition held on step 52; it returned true.
The condition held on step 63; it returned true.

--keys holds keys through the game’s own key table, so a headless run reads each key a plugin declares: the character’s walk and jump, and a game’s own, such as the example’s F to kick. A key that only the page acts on is refused, with the tool to use instead. The engine’s interact key, E, is one: the page runs the interaction, not the step, so a headless run never sees it:

Terminal window
spawnite simulate --scene run --keys e --seconds 1
--keys e is the engine's interact, which a page runs and the step never reads: spawnite play press it on a page.

To check a door or any other Interact, your agent opens a playtest, presses the key there with spawnite play press KeyE, or send_input through the MCP, and reads the result with a screenshot or the dump.

A headless run proves the rules, not the look. For the look, your agent opens the game in a playtest, a real page in Chromium, and takes a screenshot at a condition:

Terminal window
spawnite play start --scene run
spawnite play screenshot --keys w --until "count('pickup') === 0"

The screenshot command prints a line that names the PNG it saved, under the game’s .spawnite/shots folder, then the following line:

The condition held on step 76; it returned true.

The page reached the condition on step 76, as Node did, because both step the same world. The picture shows the round’s “You won” dialog and “Won with 19 s left” over the meadow, with 3 coins in the wallet. Through the MCP, the same is start_playtest, then screenshot with until and keys.

Between commands, the playtest’s world holds still: only a command that steps it, such as a screenshot with --until, moves it. However long your agent takes to think, a 20-second round has not run out. A shader is text in your game’s source and runs on the world’s clock, so a screenshot at a given step shows the same moment every run. Starting a playtest takes up to half a minute, and each command after that a second or two.

To check more than one frame, your agent has the following tools:

  • timeline, or spawnite play timeline, tiles the frames after a key press into one image, to see an effect land.
  • describe_entity names what an entity on screen is, its behaviours and their source files, and read_console reads the page’s errors, when a shot shows something wrong.
  • compare_shots, or spawnite play compare, keeps a set of shots once a look is right, and fails when a later change undoes it.
  • send_input clicks a button or an entity by name, holds keys, or walks the character to a point, and set_trait sets a value, such as 50 coins, to start a check from the state it needs, in a game with no room.

A playtest opens a Chromium page of its own with no window, from the CLI’s spawnite play start and from the MCP’s start_playtest alike, so nothing changes on your screen while your agent works. For a room game, spawnite play start --pages 2 opens a second player’s page in the same room, and --page 2 on screenshot, dump or eval reads what that player sees, as Directing a moment shows.

Your agent can instead work in the tab where you have the game open from pnpm dev: start_playtest with tab: true joins that tab. Then its tools act on the page you see: the devtools’ spawnite button at the top left glows green, its tooltip names each call, and your world runs on between calls, paused only for the steps a call takes. stop_playtest lets your tab go, and it plays on. The dev server page says how the agent reaches your tab.

While you play a development build of your game, from pnpm dev or on the address spawnite play start prints, the engine keeps a replay of what happens, in a game played alone and in a room game alike, with nothing to turn on. A replay is data, not a video: your keys and mouse, what the game decided on each step, each frame’s length, a log of what happened, and a small screenshot every 2 s where the view changed. The Escape menu reads “Replay is on · F8 marks this moment”. A published game keeps no replay.

When something looks wrong, press F8, or Mark this moment in the Escape menu. The game marks the moment as m1, then m2 and on, and keeps a screenshot of the canvas at its own size. The screenshot is the canvas alone: HTML drawn over it, such as a HUD, is not in it. To see the HUD at that moment, your agent plays the replay back to it and shoots the page with spawnite replay shot --render. Then tell your agent what you saw, in your own words: “the ball went through the tree just before I pressed F8.”

Your agent then reads the replay’s log with spawnite replay log latest, where latest is the newest session the game kept. Here are the lines that carry the mark, from a longer log of loading and warnings:

6.29 s marked m1
The shot at m1, 6.30 s, 960 by 540: `spawnite replay shot 20261004-201409-24hg --at m1`.

The times are seconds since the page opened, and 20261004-201409-24hg is the session’s name, the date and time it began. --around m1 prints every line from 20 s before the mark to 5 s after it. A log holds the marks, the page’s errors and warnings, frames over 50 ms, scene changes, loads, and the game’s spawns and removals. A room game’s log also holds the room’s joins, leaves and hits on a character.

The command the log names writes the screenshot kept at the mark, so your agent sees the game as you saw it. The small screenshots the replay keeps every 2 s are 854 by 480 on a wide screen, and each costs your agent about 550 image tokens to look at. Replay says how to pick a shot’s width.

From the mark, your agent works on the moment itself, through the CLI or the MCP’s replay and director tools:

  1. spawnite play start --replay latest --at m1 opens the moment paused, on the game’s page, with the code as it stands now.
  2. spawnite play step moves it forward a number of steps or seconds, or until a condition holds, and spawnite play seek moves it back or forward to any moment, such as m1-2, two seconds before the mark.
  3. spawnite play screenshot shoots it from the player’s view or from any camera, such as one framed on an entity from a chosen side, and spawnite play dump reads the world at that moment.
  4. After a fix, spawnite replay rerun latest --at m1 runs the moment again against the changed code, from the game’s own state just before it, and says where the new run differs from the old one. With the code unchanged, it matches the old run update for update.
  5. spawnite replay check reruns a whole session and says whether it comes back exactly. When it does not, it names the trait and the step where the rerun first parts from the original, and whether the game’s state or the engine’s is at fault. The usual cause is state the game keeps outside its world, such as a module variable or Math.random, which a rerun cannot carry.

This holding a moment still and moving it a command at a time is director mode. spawnite play stop ends it. Directing a moment lists every command and camera, and Replay covers marks, budgets and reruns.

Once you publish, your agent reads how players take the game, from its first player. It reads players, sessions and how long they last, how many came back the next day and the next week, rounds finished and purchases, for the game and for each version you published. It reads them through the CLI or the MCP, signed in with your account, so it can tell you whether players stayed longer after a change. What you can see about your game lists the numbers.