# Editor Play Mode (Stage 8.7) Play mode lets the editor **run the open scene in place** — play, pause, single- step, and stop — driving the same fixed-timestep [`Schedule`](modules.md) a shipped game uses, so what you see while playing behaves like the real runtime. Mutations made while playing (physics moving bodies, scripts spawning entities) are reverted on **Stop**, so the authored scene is never corrupted. This page covers the play-state model, the snapshot/restore that makes Stop safe, and how the host runner drives the engine. The standalone **"Launch"** button (running the *real* exported runtime in a separate process) is a Stage 16 follow-up and is **not** part of play mode. ## The play states `oxide_editor::state::PlayState` is a three-state machine held on `EditorState`: | State | Meaning | Schedule ticked? | |-------|---------|------------------| | `Editing` | Normal authoring | no | | `Playing` | Running | every frame (`App::update`) | | `Paused` | Frozen, still live | only on **Step** (one fixed tick) | Transitions are methods on `EditorState`: - `enter_play()` — snapshots the scene and switches to `Playing`. No-op if already running (re-entering must not clobber the original snapshot). - `toggle_pause()` — `Playing` ⇄ `Paused`; no-op while `Editing`. - `stop()` — restores the snapshot, clears the selection and any in-flight gizmo drag (entity handles change on restore), and returns to `Editing`. The shell wraps these with status messages and undo-history clearing (see [Controls](#controls)); `is_in_play()` is the "running or paused" predicate the host uses to gate the runtime. ## Snapshot / restore Pressing Play captures a [`SceneSnapshot`](scene.md) of the current scene; pressing Stop restores it. Unlike `Scene::to_ron` (which records only the node- baked `Node`/`Transform`/hierarchy), a snapshot is **registry-aware**: it also serializes every reflected component on each entity, plus the engine-intrinsic non-reflected components (`Tags` and `DisabledComponents`). That makes Stop a **bit-for-bit** revert across the full component set, not just transforms. ```rust use oxide_engine::scene::SceneSnapshot; let snap = scene.snapshot(®istry); // capture // … play-mode mutations … let scene = snap.restore(®istry)?; // revert (fresh entity handles) ``` Fidelity is bounded by what the [`TypeRegistry`](reflection.md) knows: a component that is neither registered nor one of the two intrinsics is invisible to capture. Modules register their components anyway (that is what makes them editable), so they survive play mode automatically. `SceneSnapshot` also exposes `to_ron`/`from_ron`, so the same capture format seeds full scene files in a later stage. ## Driving the schedule The editor holds the live scene in `EditorState.scene`, not in an [`App`](modules.md). On Play the host runner (`oxide_editor::main`) builds a play `App` (currently just `DefaultModules`; Stage 9's physics module and the project's modules will register here too) and, each frame: 1. **swaps** `state.scene` into `app.scene` (an O(1) move), 2. advances the engine, then 3. **swaps** the scene back out. So the `App` only "holds" the editor scene for the duration of a tick, and `state.scene` stays the single source of truth the inspector, hierarchy, and viewport read between frames. The `App` persists its `Time` and resources across frames (so a physics world accumulates correctly) and is dropped on Stop. How far to advance is decided by a small pure function, `oxide_editor::play::tick_for`, kept separate from the (un-testable) GUI runner so the contract is pinned in a unit test: ```rust use oxide_editor::play::{tick_for, Tick}; match tick_for(state.play, step_requested) { Tick::Frame => app.update(dt), // Playing Tick::FixedStep => app.step(), // Paused + Step Tick::Idle => {} // Editing, or Paused with no Step } ``` `App::step()` (engine) advances **exactly one fixed timestep** — one `FixedUpdate` plus the per-frame phases once, bypassing the accumulator — which is the Step primitive. `App::update(dt)` runs a normal frame, fixed steps driven by the accumulator as usual (see [modules.md](modules.md)). ## Controls A toolbar below the menu bar exposes **Play / Pause / Step / Stop**, gated by the play state (Pause/Step/Stop enable only while running) with an `Editing`/`PLAYING`/`PAUSED` badge. The viewport additionally draws a coloured border + corner label while running (green = playing, amber = paused) so edit-vs-play is unmistakable. Shortcuts: - **Ctrl+P** — Play when editing; Pause ⇄ Resume while running. - **Ctrl+.** — Step one fixed tick (while paused). Entering Play and pressing Stop both **clear the undo history**: the scene is restored wholesale on Stop, so play-mode edits are deliberately not part of edit-mode undo. The reflection inspector and gizmos stay live while paused (and playing), so a field can be tweaked and the result observed immediately — the payoff of the Stage-8.5 reflection work. ## Not in play mode (Stage 16) A **"Launch standalone"** button that runs the real exported runtime in a separate window/process — the truest-to-ship check — is deferred to Stage 16, where it reuses the export builder rather than the in-editor loop.