"Done!" is not evidence. Subclass TddScenario, write Run(), and any player executes it inside a real Play Mode session, headless or live, host or client, and reports pass or fail.
public class HostJoinTest : TddScenario { public override IEnumerator Run() { if (IsHost) yield return StartHost(); else yield return JoinAndMove(); } }
An agent that writes game code has no natural way to check its own work. Compiling proves nothing about behaviour, and unit tests around a MonoBehaviour usually end up testing scaffolding. So the agent reports success, and you find out later whether it was right.
What closes that gap is letting the agent run the game and observe it. That is what a scenario is: a small piece of C# that plays out a situation in a real Play Mode session and produces a verdict.
There is one abstract member.
public abstract class TddScenario : MonoBehaviour
{
public static int PlayerIndex { get; }
public static int PlayerCount { get; }
public static string Role { get; }
public static bool IsHost => Role == "host";
public virtual float StallTimeoutSeconds => 30f;
public abstract IEnumerator Run();
}
Subclass it, implement Run() as a coroutine, and the runner does the rest: it finds the type, spawns it into the loaded scene, marks it DontDestroyOnLoad, and drives the coroutine to completion.
PlayerIndex, PlayerCount, Role and IsHost are set on each instance before Run() starts. That means one scenario describes the whole multiplayer situation and each client branches to its own part: the host starts a match, the clients join and move, and every side writes its own result.
Scenarios log through LogTdd, which writes per-tag files stamped with frame number and real time:
[F:412 T:6.883] client-2 position desync 0.42m
The agent reads those back with unity_tdd_logs from the Unity MCP server, alongside each side's pass/fail result. Frame stamps matter more than they look: for a desync or a race, "which frame" is usually the entire question.
The most useless test result is a scenario that hangs forever. A coroutine host watches for progress, either a completed step or a new LogTdd line, and aborts the run if nothing happens for longer than the stall limit. The default is 30 seconds, overridable per scenario with StallTimeoutSeconds for legitimately long quiet waits.
An aborted run reports more than "timeout": how long there was no progress, which step it wedged on, what it was waiting for (down to WaitForSeconds(2.0)), the frame number, and up to eight errors logged during the run. An exception thrown outside the scenario coroutine is very often what broke the condition it was waiting on.
WaitForSeconds(2.0) for 34s at frame 1902, with a NullReferenceException logged in between" will usually fix it on the first try.unity_open_scene opens a scene, enters Play Mode and runs a named scenario in one call, so the whole verification is a single tool call for the agent.
One methodSubclass TddScenario, implement Run() as a coroutine.
IsHost · PlayerIndexPlus PlayerCount and Role, set before the run starts.
LogTddPer-tag files stamped with frame and real time.
30s, overridableAborts with the wedged step, the pending yield and recent errors.
UTF runs tests in its own harness. A scenario runs inside the session you're already streaming, on every multiplayer client at once, so it can express "host starts a match, client joins and moves, both agree on the position".
No. Keep your unit tests where they are. Scenarios cover the part unit tests are bad at: behaviour, over time, in a real session, across several clients.
PlayerIndex, PlayerCount, Role and IsHost are set on each instance before Run() starts.
A stall detector aborts it and reports the wedged step, what it was waiting on, the frame, and any errors logged during the run. The 30-second default is overridable via StallTimeoutSeconds.
Each side writes its own pass/fail result, and unity_tdd_logs returns the frame-stamped LogTdd output from the run.
Yes. In a player the scenario quits the process with an exit code when it finishes, so it works in an ordinary automated run too.
unity_open_scene and unity_tdd_logs: how an agent runs and reads a scenario.
Headless UnityRun the same scenario with no rendering, thousands of ticks a second.
Coding agentsThe terminals that write the scenario and act on the verdict.
The running game reports pass or fail, so you don't have to take the agent's word for it.