Directing a moment
A moment of a replay opens on the game’s dev page and holds there while each command moves it.
To look at a moment the way a director does, open it once and hold it, then move its time and its camera with a command each. spawnite play start opens the session and every command after it acts on the page it holds, most in about a second, and a room session’s first seek or a free camera’s first shot in 5 to 9 s, as the last paragraph of this page measures:
spawnite play start --replay <replay> --at <moment>plays a replay back on the game’s dev page, with the code as it stands, and stands on the moment.spawnite play starton a room game holds the game’s own room and the page in it once the page has joined.--bots <n>adds the room’s bots, which play until the session stops, and--invulnerablespares every character, so a live fight runs as long as you need it. The page is a player too, so a game that waits on every player waits on it.--page-bothas the page’s own character play the part the scene’sbotplays for a room bot, the words a player’s keys and menus send, such as ready, while its walk and its aim stay yours.--pages 2opens a second player’s page beside it, below, and--page-bothas each page’s character play the bot’s part.
The MCP’s director tool runs start, stop, pause, resume, step, seek, mark, screenshot, look, see, aim, eval and set on the same session. It takes each command’s name as action and its options as fields, such as action: "seek" with at: "m1", and replies as the command does, a shot’s image beside its lines. start takes replay, at, scene, bots, invulnerable, pageBot and replace, and screenshot takes its camera as JSON.
Every session opens held. The following commands move it:
| Command | What it does |
|---|---|
spawnite play pause |
Holds the session where it stands: the replay’s playback, or the room with its page. |
spawnite play resume --speed 0.25 |
Runs it on at a speed, 1 by default, until pause or the replay’s end. |
spawnite play step --steps 30 |
Runs 30 fixed steps, half a second, and holds again. --seconds counts in seconds, and --until <condition> runs until a condition over the world holds, as a playtest’s step does. |
spawnite play seek m1 |
Stands on a moment: seconds, a mark, a mark and seconds such as m1-2, or now and seconds from where the page stands, such as now-5. It goes back as well as on. |
spawnite play mark |
Marks the moment on a room page’s own replay, as F8 does, so a later seek finds it. |
spawnite play screenshot |
Shoots what the player sees, the HUD among it, or the world from a camera of its own. |
spawnite play dump, state, ai and stats |
Read the page as it stands, as they read a playtest. dump --where '[monster.kind=colossus]' prints only the entities a selector picks, and --where '[round.playing]' those in a machine’s state. |
spawnite play eval '<expression>' |
Reads a value on the page as it stands, as a browser’s console does, such as a HUD’s width after a seek, over the engine’s scope; --file runs a script of your own, Playing a held session. |
spawnite play console |
Prints the page’s console errors and warnings and its uncaught errors since the session started, or since the last console. |
spawnite play actions, action '<name>' |
List the game’s playtest actions, the steps its devtools panels run, and run one by name, Playtest actions. |
spawnite play set '[controlled]' warden.health 0 |
Writes a trait’s value onto the entities a subject names, below. |
spawnite play look --yaw 90 --pitch -10 |
Turns the page’s own camera to a yaw and a pitch, as state reads them, or by --turn degrees from where it looks. |
spawnite play press Digit1, hold, release and move |
Play the page as a player does: any key, each mouse button, the mouse’s motion and its wheel, a pad and a finger, Playing a held session. |
spawnite play see and aim |
Read what the camera sees of an entity, and where each shot the page sent passed the crosshair’s point. |
spawnite play stop |
Closes the page, the dev server, the room and its bots. An unused session stops itself after --idle minutes, 60 by default. |
step also takes --press, which lands inputs on moments of the step, and --sample, which reads a value on each frame, as Playing a held session shows. The engine reads no pad, so a pad command tests a game that reads one itself. What the page sends a held room lands on its next step.
A replay’s page plays on to a moment up to 3 s ahead, and opens afresh on the keyframe before any other, a second at most before it, in place, without loading the game again: a seek back takes about as long as one ahead. It stands her character where the keyframe had her. When her character stands more than a centimetre from where the recording’s keyframes had her since, the line each command prints says how far, because a shot of her then shows a place she never stood. A room’s page keeps a replay of its own, with no idle stop while the session holds it. So seek in a room session holds the room where it stands and opens that replay in a second page beside the live one, which stays in the room and keeps recording. spawnite play seek live goes back to the room. While the page shows the past, pause, resume, step and screenshot act on the past.
A replay’s seek, and its step of fixed steps, play the last half second before the moment a frame for each frame the browser draws. So what the page animates in its DOM, which runs on the browser’s frames, stands as it stood then: a health bar drains a hit that landed just before the moment. Every animation the DOM runs keeps the replay’s clock, whichever the browser runs it on. Motion’s own reads the page’s clock, which is the replay’s. A CSS animation, a transition, or one motion hands the browser, such as an opacity’s fade, is held at its age on the replay’s clock from the frame it began at, and finished once past its end.
So a moment reads the same after any seek or step that lands on it, however fast the browser drew: on Holdfast, the wave bar’s lost piece read a width of 3.21 % and an opacity of 0.67 in three seeks to one frame. A seek that opens afresh first runs the page’s clock a second on, with the world held, so what the jump itself starts in the DOM, as a bar draining all the health the seek skipped, is over by the moment.
In a replay, a condition also reads at, the moment the page stands on in seconds, as a seek names one, so step --until 'at >= 58.55 || count("monster") === 0' stops at a moment or sooner. step --until plays as fast as it can. Then, where it played a frame, it opens afresh on the keyframe before the frame it stopped on and plays its last half second settled, as a seek does, so what the fast play started in the DOM stands as it stood then too. That costs the step about a second more.
In a replay, time is the game’s, not the page’s clock’s. resume plays at the pace the game ran: a stretch a tool stepped, 3 s of the game in the 0.7 s of the page’s clock its steps took, plays at speed 1 in 3 s, and a stretch the room ran at speed 4 plays at 1 as the game ran it. A step counts the game’s steps the same way, and so does step --until. Its condition’s steps, its --budget and the step it says the condition held on count the game’s steps the frames it played ran, so a frame the room ran at speed 4 counts about four, as a room session’s step does. Over frames that ran none of the game’s time, as while a tool held the room, the budget runs out on the page’s clock instead. The page keeps each frame’s own seconds of the game where they differ from its clock’s, and spawnite replay export writes them as gameSeconds.
Holding a room holds its clock: the room runs no step, its time stands still, and its players stay joined. Each player’s budgets, of moves, shots, walks and the game’s messages, fill on the room’s time, so a bot that fires at a held colossus spends its burst and is refused after that, rather than landing a minute of shots as the room runs again.
A step runs the page’s own steps and then the room’s. So the moves the page sends, as --keys walks its character, wait in its queue for the room’s steps, and the page’s views, a monster’s walk and the fire’s flames, move with the room and stand still with it. step --until lets the room run on its own clock, at the speed it last ran at, and holds it again on the first frame the page sees the condition hold, or once the budget’s steps have run. The page draws the stream a send or two behind the room, so the room stands up to a fifth of a second past that frame.
The page steps at the room’s speed and sends its moves as fast, and the room’s bots send a move for each of the room’s moves, so a room’s bots fight on at speed 4 as they do at 1, four times as fast, and stand while it is held. The room tells each page its pace in the answer to each ping, and again at once as a tool pauses, resumes or steps it. So a shot the page fires names the room’s step it drew at any speed, from the moment the speed changes, and stands on the room’s step while it is held.
More than one player
Section titled “More than one player”spawnite play start --pages 2 opens a second player’s page in the session’s browser, in a tab of its own, so it joins the room as a player of its own before the bots do. Every page holds, steps and runs with the room, and each keeps a replay of its own. --page 2 on screenshot, dump, eval, state and look reads or shoots the second page; the rest act on the room, or on the session’s own page. So what one player sees and another does not, such as the screen each warden reads at dawn, is read on that player’s page with no script of your own:
spawnite play start --bots 2 --pages 2 --page-bot --invulnerablespawnite play step --until "Object.values(entities).some((e) => e['phase.dawn'])" --budget 1800spawnite play eval --page 2 "[...document.querySelectorAll('dialog')].map((dialog) => [dialog.getAttribute('aria-label'), dialog.open])"spawnite play screenshot --page 2Unreal’s play-in-editor opens more clients beside the listen server, each in a window of its own, and Roblox Studio’s local server test opens a window for each player; here each is a page a command names.
Setting the world
Section titled “Setting the world”spawnite play set <subject> <path> <value> writes a trait’s value onto the entities a subject names, so a check starts from the state it needs rather than from play that reaches it. Unreal’s set console command and its cheat manager write a property the same way, and Roblox Studio’s command bar runs a line on the server. The subject is a dump key or a selector such as '[monster.kind=colossus]'. The path names the trait as spawnite play dump files it, such as warden, or a field of it, such as warden.health or transform.position. The value is JSON, such as true, 5 or [4.5,2.1,-12], null takes the trait off, and anything else is text.
A room takes the write between two steps, the room’s own traits among them, which no page receives, and a recording room records it, so spawnite replay rerun and check take it again on its step and still come back exactly. A held room then steps one move, three steps, so its page draws the write. A walker whose transform it writes stands there at once, her body built where the transform says, so give the ground’s height there, which spawnite map query --at x,z reads, with its slope. A game with no room takes the write on its page, where a subject may also be a row’s name. A replay plays what it recorded and refuses one: continue its moment in a room with rerun.
spawnite play set '[controlled]' transform.position '[-18.4,3.2,21.7]'spawnite play set '[controlled]' warden.health 0spawnite play step --seconds 1spawnite play screenshot --framing shoulderHoldfast’s warden at no health goes down on the room’s next step, and the dump files her as life.down.
A trait no dump shows, such as the state a room keeps for itself and draws its streamed traits from, takes a write by the name its module exports it under. Holdfast’s room rewrites the siege trait from its own SiegeStateTrait on every step, so a write to siege does not hold; a write to SiegeStateTrait does. Its night reaches dawn once the last wave’s monsters are gone:
spawnite play set '[siege]' SiegeStateTrait.wave 15spawnite play set '[siege]' SiegeStateTrait.toSpawn 0spawnite play set '[monster]' monster nullspawnite play step --steps 30Cameras
Section titled “Cameras”Without a camera flag, screenshot shoots what the player sees, the page’s panels among it, and --no-hud shoots one render of the game’s camera without them. Each other camera renders the world from where it stands, at the page’s shape, with no panel, and the command names the eye and the target it resolved to:
--eye 0,14,10 --target 0,0,0places a free camera anywhere.--around <subject> --angle <azimuth,elevation> --zoom <factor>frames a subject from an angle. The subject is anything a map shot frames, or an entity. An entity is named by the keyspawnite play dumpfiles it under, or by its row’s name inspawnite play start’s tree, such ashearth. It can also be named by a selector of what it is, as CSS’s attribute selectors pick an element, such as'[monster.kind=colossus]'for the entity whosemonstertrait’skindiscolossus.'[controlled]'picks one that holds thecontrolledtrait,ControlledTrait, which any entity a player controls holds, a character or a vehicle, and several groups pick all of them. A subject that picks more than one entity fails, naming each one’s key. The shot frames what the page draws of the entity where it stands at that moment, its model as posed then, so the same flags after a step follow it.--orbit 4with--aroundtakes four shots round the subject, a quarter turn apart, from--angle’s azimuth.--framing shoulderstands behind and over the right shoulder of the player’s character, or of the entity--aroundnames, and looks where it faces.--framing toplooks straight down, and--framing wideis an establishing shot from high to one side; both frame every entity in play where no--aroundnames a subject.
A shot from any camera but the player’s own leaves out the screen overlays her view draws in the canvas, such as Holdfast’s red hurt vignette, which would cover what the camera frames; so does --no-hud, and --overlays keeps them. The page as it shows, the shot with no camera flag, keeps them, since they are what she sees. spawnite play look --yaw 90 --pitch -10 turns the page’s own camera instead, to a yaw and a pitch as spawnite play state reads them, or --turn 30 by degrees from where it looks, so the player’s own shot looks where a check needs it to. A character that faces where the camera looks turns with the room’s next step.
--size 1920x1080 sets the page’s size for the shot, and --width 1280 writes each file at most that wide, since an image costs an agent about its width times its height over 750 of its tokens. A list, --size 1920x1080,1280x720,960x540, shoots at each size in turn in one call, with the world held between them; a HUD that animates on the page’s own clock moves on a few hundred milliseconds from one size to the next. --out after/offer.png writes the shot there rather than under .spawnite/shots, and several shots add their size to the name, such as after/offer-1280x720.png.
On Holdfast, a live fight with three bots, run four times as fast until its colossus rises, then shot from the player, round the colossus, picked by what it is, and from over the fire, and then back at the moment it rose, from the page’s own past. Holdfast starts a run once every warden is ready, the session’s page among them, so --page-bot has the page say it is ready as the bots do:
$ spawnite play start --bots 3 --invulnerable --page-botThe room is held at step 306; its page keeps replay 20260928-025215-8z5t, at 5.35 s.$ spawnite play resume --speed 4The room is running at speed 4 at step 453; its page keeps replay 20260928-025215-8z5t, at 7.01 s.$ spawnite play step --until "Object.values(entities).some((e) => e.monster?.kind === 'colossus')" --budget 30000The condition held on step 14317; it returned true.The room is held at step 15040; its page keeps replay 20260928-025215-8z5t, at 68.14 s.$ spawnite play markMarked m1 of 20260928-025215-8z5t at 69.25 s: `spawnite play seek m1` comes back to it.$ spawnite play step --seconds 6Stepped 360 steps.$ spawnite play screenshot --size 1280x720$ spawnite play screenshot --size 1280x720 --around '[monster.kind=colossus]' --orbit 4azimuth 90°, elevation 25°, around [monster.kind=colossus]: perspective fov 50, eye (14.2, 8.1, -9.8) → target (1.9, 2.4, -9.8); screen up = +y, right = -z (north).$ spawnite play screenshot --size 1280x720 --eye 0,16,12 --target 0,0,0Camera: perspective fov 50, eye (0.0, 16.0, 12.0) → target (0.0, 0.0, 0.0); screen up = +y -z (north), right = +x (east).$ spawnite play seek m1The room's page shows its own past: 20260928-025215-8z5t held at 69.24 s. `spawnite play seek live` goes back to the room.$ spawnite play seek liveThe room is held at step 15400; its page keeps replay 20260928-025215-8z5t, at 85.90 s.The wait for the colossus, 14,317 of the room’s steps, took 61 s at speed 4. A condition waits for any moment the same way: step --until runs the room at its own pace until the page’s world holds one.
On Holdfast on a Ryzen 9800X3D with an RTX 4070 SUPER, spawnite play start --bots 3 opened in 9 to 10 s, a room with no bots in 7 to 8 s, and spawnite play start --replay in 7 to 8 s. The room, the dev server, the browser and the bots start side by side, the page loads while the room starts, and the browser keeps the shaders it compiled for the last session, which saves about 5 s of a replay’s first frames. Most of what is left is the game’s own load: its modules, its models and its shaders, 3 to 4 s, and the room’s start, about 4 s beside it.
After the session opened, pause, resume, mark and a shot of the player’s camera each took 0.4 to 1.1 s, and step --steps 30 0.8 to 1.1 s. A seek in a replay took 0.9 to 1.6 s back or on, and 2.4 s to play 2 s on. A room session’s first seek took 6 to 9 s, since it loads the game in a second page, and each after 1 to 1.6 s. A free camera’s first shot took about 5 s, as the page compiled the shaders the camera’s view needed, and each after under a second, or 1.4 to 1.6 s for four orbit shots together.