"Done!" is not evidence. Mark small C# methods as handles, and the agent plays the running game through them one step at a time and checks each result. Then it shows you a report.
// the agent calls your handles one at a time unity_exec(code: 'Print(SaturaHandles.Call("StartPulse"));') // {"status": "done", "value": 3} unity_exec(code: 'Print(SaturaHandles.Call("WaitForBeats", 3));') // {"run": 2, "status": "running"} unity_exec(code: 'Print(SaturaHandles.Result(2));') // {"status": "done", "value": 5.16} // and shows you what it found satura_report(path: ".sspace/reports/arena-pulse/report.tsx")
Compiling proves nothing about behaviour. So the agent runs the game and drives it through handles: small public static methods in your runtime code that set up a moment, do one action or read a state.
public static class ArenaPulseHandles
{
[SaturaHandle("Adds ArenaPulse to the Monolith and returns the lamp count")]
public static int StartPulse() { ... }
[SaturaHandle("Waits for the given number of beats and yields the seconds they took")]
public static IEnumerator WaitForBeats(int count) { ... }
[SaturaHandle("Every point light: intensity and color")]
public static string LampState() => ...;
}
The agent calls them through unity_exec on the Unity MCP server and checks the result after every call. SaturaHandles.List() gives it every handle with its signature and description.
Keep each handle to one job. A failure then names the step that broke and what came back, not "failed at line 212" of a long script.
A handle that returns IEnumerator can wait for things to happen. Call starts it, and SaturaHandles.Result(run) reads it later. Its value is the last thing it yielded.
For multiplayer, unity_exec(player="all") with SaturaHandles.StartAt(SameMoment(3), "Jump") starts a handle on every player, and each one runs it on its first frame after a shared moment. SaturaHandles.PlayerIndex is 0 on the primary.
SaturaLog.Log(tag, message) writes one file per tag, each line stamped with the frame number and the time. The files are cleared every time Play Mode starts. This line came from the run in the clips below:
[F:159 T:9.377] beat 1: 3 lamps up to x3, 40 motes
The agent reads them with unity_logs(source="tags", tag=...). Unity's console lands in the same place as _log, _warn and _error, so after a red run the agent reads _error first.
The agent records the run with unity_video, takes before and after shots with unity_screenshot, and puts everything in a report. satura_report opens it next to the agent's terminal.
A report is report.tsx, a React component the agent writes from the app's own parts:
satura_report compiles and type-checks the file and sends the errors back to the agent before you see anything.
A file name with a line number, like ArenaPulse.cs:29, opens the code inside the report at that line. A frame link scrolls to the video and seeks it. Images enlarge in place, and web links are copied rather than opened. A tab switch keeps your scroll position, the video frames and the checks filter.
[SaturaHandle]On any public static method in your runtime code, coroutines included.
StartAt · PlayerIndexOne handle on every player, started at the same moment.
SaturaLogOne file per tag, stamped with frame and time.
report.tsxType-checked, and rendered in a panel next to the agent's terminal.
Handles run inside the session you're streaming, on every multiplayer player at once, and the agent checks after each step instead of waiting for one verdict at the end.
SaturaHandles.Call returns JSON with the status, the value, any error with its stack, and the log lines written during the call. unity_logs with source="tags" reads the SaturaLog files.
In a report next to the agent's terminal. The agent writes one at the end of every feature and for every failure.
Every check runs in the game itself, and the report shows you what happened.