Skip to main content
Glama

playtest_start

Destructive

Runs a full in-game playtest from the official UI: imports a candidate save, starts the level, sends scripted keys/clicks, captures screenshots and logs, then returns a run ID for status.

Instructions

Run one full playtest through the official UI without further calls: optionally import a candidate .gil as a new save, subscribe the debug log for graphName (or for every graph in graphNames when the level is several graphs: only ticked graphs show their prints, and the log window lets at most 10 be ticked, so leave out the graphs that print nothing), start the playtest, take screenshots and press keys at the given seconds, leave after durationSeconds and collect the official log. screenshotsAt takes fractions of a second (a screenshot costs about a third of a second, so a quarter second apart is as close as it gets): a run of them over an attack, put side by side by playtest_frames, shows the motion frame by frame; screenshotWidth (default 1600) keeps them larger for that. An input is a key ({at, key, holdMs}: a tap of 120 ms unless holdMs says otherwise; hold a movement key for as long as the walk takes, up to a minute; keys overlap, so a character walks with one key held while another strikes; repeat {every, until} presses the key again every so many seconds until that second, which is how a fight of three minutes is scripted: {at: 5, key: "e", repeat: {every: 1.2, until: 170}}; up to 400 inputs; keys that were sent more than 0.3 s after their moment are listed in inputsLate of playtest_status, which is where to look first when a scripted walk ended in the wrong place), a mouse click at a place of the game window ({at, click: [x, y]} as fractions from the top left), held down for holdMs when that is given above 150 (a screenshot taken in between shows what a button looks like while pressed) or a click on a word that is on the screen ({at, clickText: "购买"}; words not found are listed in inputsMissed): the clicks are for screens with a free cursor, such as a shop. A place can be reached without walking: {at, teleport: [x, y, z], yaw} puts the player there at that second of the play (no earlier than 2.5), which is how a script stands exactly at a stall or in a doorway instead of guessing how long to hold a key; {at, look: [right, down]} moves the mouse that many pixels with no button held, as a player turns the camera (the way to check that a locked view stays locked); walking is still what a real player does, so walk where the walk itself is being tested. The level itself does the teleports, on a timer, so none is lost and none takes the place of another; half a second after each the probe looks where the player is, and probe.teleports of playtest_status says for every one whether it arrived (a teleport does not take while the character is down). Two teleports are at least a second apart, and a playtest takes up to 200 of them. trackPlayer (seconds between samples, 0.5 is a good value) records where the player is: playerTrack in playtest_status lists [playSeconds, x, y, z], from which the speed of a walk and the place of every event can be read. Both add a small probe to a copy of the level (three graphs, a skill on skill slot 4, which is the Z key on a PC, the player variables PT_step, PT_tp, PT_t0 and PT_turn; two more graphs in the log window, so name at most eight of your own); with it playtest_status also dates every print in play seconds (playSeconds beside t), which is the clock of the screenshots and the inputs: the level clock t starts a few seconds earlier, a different few every run, and falls behind when the level is busy (two seconds in three minutes on a level of 585 nodes): the runner presses the key of the probe at second 1 and every half minute after, and each press ties the two clocks together anew; probe.levelClockFellBehind says by how much they parted. A timer of the platform also runs slow by itself, about 4.5 % at 60 frames a second (a moment set at 50 s came at 52): the teleports of a script are set earlier by that much and counted from the latest press, and probe.teleports gives the play second each really happened; leave a second of margin around them. A graph of your own that counts seconds with a timer is slow in the same way: a round of 180 timer seconds lasts about 188 on the wall. startValues gives custom variables other start values for this one run ([{prefab: "player", variables: [{name: "wave", value: 5}]}], or entity for the placed things of one name; the type is taken from the variable that is there): the way to try the last minute of a level of three minutes without playing the first two, when the level keeps its clock or its stage in a variable. The values go into a copy; playtest_status says startValues were used, and such a run shows how that part plays, not that the level as built can be won. The track shows where the player stands, not a jump: the height it records stays the same while the character is in the air for an ordinary jump (look at screenshots for that). The probe uses skill slot 4 (Z) and timers named PT_ on the character and the stage entity: a level with a skill of its own on slot 4 cannot be probed. A character that is in the middle of another skill does not hear the key of the probe: keep the keys of your script that use a skill (an attack on E, say) away from seconds 1, 31, 61, 91 and so on by a second on either side, or the teleports of the half minute that follows do not happen (probe.teleports then says so for each). readTextAt reads the text on the screen at those seconds (screenTexts in playtest_status), which tells what a shop or a dialog shows without looking at an image. recordVideo {from, to} records the game between two seconds of the play with the Xbox Game Bar of Windows (it has to be switched on: 设置 > 游戏 > Xbox Game Bar); playtest_status then has video.file, an .mp4 for the listing of a published level (the official advice is ten to thirty seconds of the best moment). recordAudio records what the machine plays during the level: playtest_status then has audio with the .wav, how loud each second was and the seconds at which a sound began, which is how a script learns that its sounds and music play (nobody is listening). bakeNavigation bakes the navigation mesh of the level in the editor before playing (关卡设置 > 地形导航 > 开始烘焙; a minute or two, more on a big scene): a level with complex creatures needs it, or the tactics that choose their own points (wandering, pursuit, fleeing) find none and the creature stands still. bursts photographs stretches of the play several times a second ({from, to, everyMs}, 125 ms apart unless said, at most 600 frames a run; a 4K screen cannot be read faster than about eight times a second, and a frame whose turn comes too late is left out so that keys stay on time): what is needed to see when a stroke lands, when a hand opens or how far an attack carries a creature, which two screenshots a second cannot show. The frames are not returned as images: playtest_status says how many there are and how far apart they really came (burstFrames), and playtest_frames {runId, fromSeconds, toSeconds, crop} puts a stretch of them side by side, each labelled with its play second to a hundredth. Needs the game running (main world, Miliastra lobby, save list or editor) and the monitor of the computer switched on (with no display attached nothing the game draws is shown: the run then fails at once and says so), and takes over mouse and keyboard until it finishes. A failure that says the game window does not answer means the game has to be closed and started again. A level that reaches its result screen before durationSeconds ends the run six seconds later (endedEarly), so durationSeconds can be generous. Returns at once with a runId; read the outcome with playtest_status. Omit mapPath to replay the save already open in the editor. The first playtest of a workspace imports the candidate as a new save into the save list of the game; later ones write the candidate over that same save and open it again (replaceSave, on unless switched off), so that a night of playtests leaves one save in the list and not thirty. The save editor_open_map opened (the one the scene was imported into) is never written over: the playtest after an editor_open_map imports a save of its own. A candidate that differs from the save still open only in the text of its client scripts (Lua) is not imported either: the scripts are written into the files the editor keeps beside the save, which are what the game runs, and the play starts (a round on the Lua is about 70 seconds instead of five minutes; playtest_status says shortWay). expect {prints: [...], absent: [...]} says what the run has to print, in order: playtest_status then answers with expectations {passed, firstNotMet}, and a round ends in a yes or in the one line that is missing. When the save holds client scripts, what they name is checked before anything is started (scene_check_client): a script that does not compile stops the call at once, the rest comes back as clientCheck. A playtest of the very same candidate as the last one (same file, same teleports and trackPlayer; other keys, screenshots or bursts) while the editor still has it open skips importing and opening altogether and starts about two minutes sooner (step same-save-still-open): the way to look at one moment again from closer. Read miliastra://platform-rules before designing a test: creatures far from every player do not act and report stale positions.

Input Schema

NameRequiredDescriptionDefault
burstsNo
expectNo
inputsNo
mapPathNo
graphNameNo
burstWidthNo
graphNamesNo
readTextAtNo
recordAudioNo
recordVideoNo
replaceSaveNo
startValuesNo
trackPlayerNo
screenshotsAtNo
bakeNavigationNo
clientScriptLogNo
durationSecondsNo
screenshotWidthNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observedv0.1.0

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses far more than the annotations: it takes over mouse and keyboard, requires a monitor, imports or overwrites saves (replaceSave), injects a probe into a copy of the level, uses skill slot 4 (Z), has timing quirks in the editor and platform timers, limits teleports to 200, and describes several failure modes. This is consistent with destructiveHint=true and readOnlyHint=false.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely long and poorly structured, with a massive first sentence and nested parentheticals that bury key details. While much of the content is useful, it is not front-loaded or scannable, and the density makes it hard for an agent to extract the most important guidance quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the absence of an output schema, and the minimal annotations, the description is remarkably complete. It covers prerequisites, save import behavior, probe side effects, timing and clock differences, failure handling, output consumption through playtest_status, and many edge cases an agent must know before calling.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must carry parameter meaning, and it does so for most of the 18 parameters: inputs, teleport, look, click, clickText, holdMs, repeat, screenshotsAt, screenshotWidth, trackPlayer, startValues, expect, bursts, recordVideo, recordAudio, bakeNavigation, durationSeconds, mapPath, replaceSave, graphName(s), and readTextAt are all explained. However, a few parameters such as burstWidth, clientScriptLog, and noScriptErrors inside expect are not mentioned, leaving some gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening clause 'Run one full playtest through the official UI without further calls' gives a specific verb and resource, and the description distinguishes the tool from playtest_status and playtest_frames by explaining that it returns a runId immediately and that outcomes are read separately. An agent can identify the core operation despite the sentence's length.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives extensive when-to-use guidance: teleport for precise positioning versus walking when movement itself is under test, bursts for fast motion versus ordinary screenshots, readTextAt for reading UI, startValues for jumping into a late section, and expect for print assertions. It also names alternatives like playtest_status and playtest_frames and points to platform rules before designing a test.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Deploy Server

Other Tools