public sealed class PlayModeControllerPlay, pause, step and stop, with the scene put back exactly as it was.
No guide page documents this yet — the page shows what the code says about itself.
Remarks
In-process, which is the default of doc 11's two topologies. The game runs in the viewport against the world the editor was editing, and a snapshot taken on entry is restored on exit. What makes that affordable is the ECS layout — WorldSnapshot says how — and what makes it correct is that the restore clears first, so nothing a script created survives.
The hazard is state that is not in the world. A static field, a native allocation, a subscription to an event that outlives the session: none of them are in a snapshot and all of them make the second play-through behave differently from the first. Doc 11's answer is that a play-stop which leaks should fail rather than degrade silently, so the tracked-object count is compared across the session and Leaks is what it found. A test asserts it is empty; the editor shows it as a notification.
⚠ The selection is translated, not kept. Every entity gets a new handle on restore. The controller does the translation for whatever it was handed, because a caller that forgot would have a selection naming whatever landed in those slots — which looks like a rendering fault and is not one.
And it steps a real EngineLoop, which until 2026-08-21 it did not. ShouldTick had no caller outside its own tests: Play snapshotted the world, maximised the viewport, said so in a notification, and nothing advanced. Tick is the frame, and it is deliberately the *engine's* loop rather than a schedule written here — see Loop for what that runs and, more importantly, for what it does not.
⚠ What a session runs is stated rather than assumed, because the honest set is small. An EngineLoop's default graph is behaviours, coroutines and transforms, and every other system a game runs — physics, audio, input, navigation, the render extractions — is registered by that game's own OnInitialise against host services an editor does not have. So this runs a *whole* graph of a *named* set, and Unsupported plus the caller's own inventory are what stop the difference being mistaken for a gameplay bug. [11](../../../docs/plan/11-editor.md) § "Play mode runs a system graph" is the reasoning.
Fields and properties (11)
public PlayState StateWhat the editor is doing.
public bool IsPlayingWhether the game is running, paused or not.
public int PendingStepsHow many frames Step still owes.
public IReadOnlyList<PlayLeak> LeaksWhat was still live after the last session that was not live before it.
public EngineLoop? LoopThe graph this session steps, or when nothing is running.
public PlaySession? SessionThis session's contributions and their teardown, or null when nothing is running.
public IReadOnlyList<string> RefusedThe contributions that threw while attaching, by type name.
public IReadOnlyList<string> OrderingEvery ordering declaration this session could not honour, one readable line each.
public FrameActivation DeclaredWhat the project's own declared systems did when this session started.
public IReadOnlyList<string> UnsupportedBehaviours on the world that this session could not take over, by type name.
public Func<IEnumerable<BehaviorStore>>? StoresEvery other store whose authored behaviours this session takes over.
Events (2)
public Action<PlayModeController, PlayState>? StateChangedRaised when the state changes.
public Action<PlayModeController, IReadOnlyDictionary<Entity, Entity>>? RestoredRaised after a stop, with the table that translates old entities into new ones.
Methods (9)
public PlayModeController(World world, BehaviorStore? authored = null, IEditorRegistry? extensions = null)Drives play mode over a world.
public bool Play()Enters play mode, taking a snapshot first.
public bool Tick(TimeSpan delta)Runs one frame of the game, if this is a frame the game should have.
public IReadOnlyList<Entity> Stop(IEnumerable<Entity>? selection = null)Stops the game and puts the scene back.
public bool Pause()Stops stepping without leaving play mode.
public bool Resume()Starts stepping again.
public void Step(int frames = 1)Runs a number of frames while paused.
public bool ShouldTick()Whether the game loop should run this frame, and consumes a step if it does.
public void Dispose()Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
Used by (9)
- AnimationWiringTestsVixen.Editor.App.Tests
- EditorApplicationVixen.Editor.App
- PlayDeclaredSystemsTestsVixen.Editor.SceneView.Tests
- PlayGraphTestsVixen.Editor.SceneView.Tests
- PlayOverlayTestsVixen.Editor.App.Tests
- PlaySystemsTestsVixen.Editor.SceneView.Tests
- SceneDocumentTestsVixen.Editor.SceneView.Tests
- SceneTestsVixen.Editor.SceneView.Tests
- TerrainSessionTestsVixen.Editor.App.Tests