diff --git a/Architecture.md b/Architecture.md index d5c6c14..d400826 100644 --- a/Architecture.md +++ b/Architecture.md @@ -177,6 +177,31 @@ browser the way it is free of Pixi, lets a test run a world that plays into a re a device, and is the reason a run started before anything wired a sink in plays silently rather than crashing. +**A voice has an owner, so nobody has to remember to stop it.** `AudioSink.play` takes the id of +the behavior that asked, and destroying that behavior silences its voices — the reaping pass in +`BehaviorSystem` does it right after `On destroy`, so a graph still gets its last word. That single +rule is also what ends the sounds of a scene a `Load scene` swaps away, without the swap knowing +anything about audio. `detached` gives the ownership up on purpose, for the death sound of an object +that destroys itself; an empty owner is deliberately *nobody*, never a group `stopOwner('')` could +silence by accident. Cancellation is counted rather than flagged (per file, per owner, and for +"everything"), because a play whose bytes are still in flight has to know whether a stop happened +*after it was asked for* — a flag cannot tell that from one that happened long before. + +**A scene swap is a new world inside the same run.** `Load scene` sets a flag on the world +(`loadSceneRequested`) the way `Stop game` and `Restart game` do, and the editor acts on it once +the tick is over: half a frame in each of two worlds is undebuggable. What survives is one object — +the `SharedState` handed to the next `BehaviorSystem` through `carryOver` — which is exactly the +line between "belongs to the run" and "belongs to the scene". Carrying it over also suppresses the +entry event of the global machines, because standing where you already stand is not a move. + +**Where a run begins is a project field, which of the two questions Play asks is a browser +setting.** `project.startSceneId` is content — it travels with the project, and a dangling id reads +as none rather than being cleaned up, so undoing a deletion brings it back. The switch next to Play +lives in `editor/settings.ts` (`localStorage`, best effort: a browser that refuses it gets the +defaults and keeps working), because "I am working on level 4 this afternoon" is not something the +project should carry into anyone else's checkout. The new field needed no format bump: a document +without it reads as "none named", which is precisely what older projects meant. + **A predicate named after what a thing looks like will be asked what a thing *is*.** `isSymbolType` reads as "draws an icon", but four callers use it for "is editor decoration": the running world, the Game tab, the draw order and the upright placement all key off it. Giving a particle source an icon diff --git a/Behaviors.md b/Behaviors.md index 8bdc257..0fac3c5 100644 --- a/Behaviors.md +++ b/Behaviors.md @@ -99,7 +99,7 @@ shown on the node and editable in the inspector. - **Node set** (`src/core/graph/nodes/`, one file per node): events `On start`, `On enable`, `On update`, `On disable`, `On destroy` (see *Lifecycle*), `On key down/up/held`, `On timer`, `On collision enter/stay/exit`, `On click` (the mouse pressed on the owner — anywhere when it has none), `On animation finished`, `On state entered`, `On state exited` (one output per state, see *State machines*), `Start` · flow `Branch`, `Sequence`, `Repeat`, `Run behavior`, `End`, and the three that move or follow a state machine, `State branch`, `Set state`, `State transition` (see *State machines*) · data `Self`, `Owner`, `Parent`, `Object by name`, `Name of`, `Is asset` (is this object an instance of asset X — how you tell what you collided with), - `Variables`, `Properties` (every property row of the target's type, the transform rows `scale`, `rotation`, `mirrorX`, `mirrorY` included — how a graph turns a sprite), `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`/`State machine`/`Sound`, `Add/Subtract/Multiply/Divide`, + `Variables`, `Properties` (every property row of the target's type, the transform rows `scale`, `rotation`, `mirrorX`, `mirrorY` included — how a graph turns a sprite), `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`/`State machine`/`Sound`/`Scene`, `Add/Subtract/Multiply/Divide`, `Min/Max/Abs/Sqrt/Clamp`, `Floor/Ceil/Round`, `Modulo`, `Random`, `Time`, `To text`, `To number`, `Join text`, `Substring`, `Find text`, `Equals`, lists `List` (a constant), `Fill` (one value repeated — how an empty board is made), `Count`, @@ -112,7 +112,9 @@ shown on the node and editable in the inspector. (asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Emit` (starts a particle source over, see *Particles*), `Play sound` / `Stop sound` (see [Sounds](Sounds): the asset says how loud and whether it loops, the node's `volume` multiplies that and its `loop` either leaves it alone or overrules it in either - direction; every play is a voice of its own, so two in a row overlap), `Log`, `Stop game`, `Restart game`. A node type is one file exporting a `NodeDef` + direction; every play is a voice of its own, so two in a row overlap, and the voice ends with the + behavior that started it unless `detached` says otherwise), + `Load scene` (swaps the running scene once the frame is over — see [Scenes](Scenes)), `Log`, `Stop game`, `Restart game`. A node type is one file exporting a `NodeDef` (ports, settings, `run`), listed in `nodes/index.ts`; palette, ports, validation and inspector fields are generated from it. A port may declare a `default` for its inline value (e.g. `Move.scale = 1`), shown on the node and in the inspector. diff --git a/Concepts.md b/Concepts.md index 6de6dc2..3d5efa3 100644 --- a/Concepts.md +++ b/Concepts.md @@ -20,8 +20,10 @@ | **Default** | Every property has a schema default. Asset values that differ from it are marked gold in the inspector and can be reset (↺) the same way an override can. The asset's name in the **Assets** panel carries the same gold while any of them does (`assetDeviates`, `src/core/properties/deviation.ts` — the inspector marks the rows of that same schema, so the list cannot claim a deviation the inspector then fails to show). A behavior's variables are not in it: their default is declared on the variable itself, so the asset value cannot differ from it. | | **Instance** | An object resolved from a placed container's template. Its **values** can be edited — position, name, property overrides — and only what actually deviates is stored, as a sparse `instanceEdits` entry on the container object keyed by the template path. Everything else keeps following the template. Its **structure** cannot: it can not be removed, re-parented or given a collider, and nothing of the scene can be parented into it. ↺ ▾ resets to the nearest base, which inside a container is the **template as the containers in between leave it**; ↑ ▾ offers **Apply to container** (write the value into the template, where every other instance still at the old value picks it up) and/or **Apply to asset** (which drops the template's override as well). With one base left the menu collapses to a plain ↺ / ↑, with none the buttons disappear. Gold marks the same thing the buttons offer. | | **Unpack** | Writes a placed container's content into the scene as ordinary objects (hierarchy context menu, or the inspector's *Content* section). The ids survive it, so the selection does; from then on the structure is editable and the objects no longer follow the template. | +| **Start scene** | The scene a run *begins* with, named on the project rather than on any asset (right-click a scene in the Assets panel → *Set as start scene*, `▶` marks it in the list; *Clear start scene* takes it back). It is a different question from which scene is **active**, which is the one you have open in the editor — the switch next to Play says which of the two Play asks. Without a start scene named, both are the same thing. See [Scenes](Scenes). | +| **Load scene** | The node that swaps the running scene for another one, inside the same run. Everything of the old scene is destroyed — its objects, their behaviors, and the sounds those behaviors were playing — while what belongs to the **run** carries on: the values of the Variables assets, the states of the global machines, and the clock. See [Scenes](Scenes). | | **Edit mode** | The Scene tab draws the project model; panels, grid, gizmos and drag-to-move are active. | -| **Play mode** | The editor switches to a **runtime copy** of the project (same ids). A `World` is instantiated from its active scene (one entity per scene object, resolved properties as mutable state) and shown in the **Game** tab. Systems tick it and mirror their changes into the copy; every panel stays open and edits the copy, which the world picks up immediately. **Stop discards world and copy** — the saved model is never touched. | +| **Play mode** | The editor switches to a **runtime copy** of the project (same ids). A `World` is instantiated from the scene the switch next to Play names — the start scene, else the active one (one entity per scene object, resolved properties as mutable state) and shown in the **Game** tab. Systems tick it and mirror their changes into the copy; every panel stays open and edits the copy, which the world picks up immediately. **Stop discards world and copy** — the saved model is never touched. | **The camera defines what is rendered.** A camera asset has a view size (`width` x `height`, default 640x360) and a `zoom`; the frame it covers in world units is `size / zoom`. The Scene @@ -35,7 +37,9 @@ no movement): everything that happens is defined by behavior graphs. **Toolbar and panels.** The toolbar holds the **Project** menu (new project, switch project, *Delete this project…*), the project name (type to rename), the **Scene** picker for the active -scene, Undo/Redo, Play/Pause, and on the right the notice line, the save status and the frame +scene — usable while a run is on, because it only moves the editor's view and never the running +world — Undo/Redo, the **start-scene switch** (*Start scene* / *Active scene*, see **Start +scene**), Play/Pause, and on the right the notice line, the save status and the frame rate. The **Assets** panel's `+` creates an asset and opens its tab; its menu lists the types in two alphabetical groups, first what can be placed into a scene, then what only lives in the library (#43) — the line between them answers the question that follows right after "which type?". @@ -44,8 +48,8 @@ selects (the inspector shows it, no tab moves); a **double-click** opens the tab in — Scene, Behavior, State machine, Shader, Particles, Texture, Animation or Container — and on a scene also makes it the active one. `Enter` does the same once the list has keyboard focus (`Tab`). A sprite, camera, text or collider has no tab of its own, so a double-click on one does nothing. -Right-clicking a row offers *Set as active scene*, *Create sprite from texture*, *Create animation -from texture* and *Delete…*. In +Right-clicking a row offers *Set as active scene*, *Set as start scene* / *Clear start scene*, +*Create sprite from texture*, *Create animation from texture* and *Delete…*. In **Scene objects** it offers *Select asset*, *Add collider*, *Create container*, *Unpack container*, *Unparent*, *Expand all* / *Collapse all* and *Remove*. diff --git a/Development.md b/Development.md index db8a0b1..5d62544 100644 --- a/Development.md +++ b/Development.md @@ -64,7 +64,7 @@ The e2e run is a list of steps in `tools/e2e/` (hierarchy, inspector, play, asse graph, parenting, colliders, containers, text/enabled, menus, textures, frames, overflow, aspect, container tab, run-in-editor, pause, nested behaviors, key repeat, list tab, struct variable, variable nodes, property nodes, animations, state machines, console, shaders, transforms, fonts, -particles, shared scope, render on change, sounds) that build on each other's +particles, shared scope, render on change, sounds, load scene) that build on each other's scene state and share the CDP driver in `tools/lib/harness.mjs` — `press`, `undo`, `play`, `object(id)`, `world()`, `mutate(js)` and friends, so a check reads like the interaction it exercises. @@ -107,6 +107,12 @@ and loop flag) is the witness, for the reason `renderStats` is one. The seed's t audio file the host and the CI image are guaranteed to share, and the bytes are a pure function of the arguments, so storage stays content-addressed and a check can name a file by its exact size. +**The one step that must clean up after itself twice.** `35-load-scene` makes a second scene and +plays *from* it, so it leaves behind both assets and a project field. Its own scene is where all of +its objects live, which keeps the shared fixture scene untouched; the start scene it names is +cleared at the end, because a step that leaves one behind would silently redirect every Play of +every step after it. + **A check that demands an exact number must be handed the number, not something it can be derived from.** The particle preview used to hold second 0.5 by computing the clock reading that *would* mean 0.5 since the field started; the round trip through floating point misses by the last bit about one diff --git a/Home.md b/Home.md index 359ff39..a600c7d 100644 --- a/Home.md +++ b/Home.md @@ -10,6 +10,7 @@ Live: · Widget playground: