A transition is a named arrow between two states, drawn without pins (#52)

State machines: the asset is no longer "a set of states and nothing more" -- it
holds the moves between them. How a transition is drawn (right-click, Make
transition, click the target), why a state still carries ports it never shows,
what the arrow looks like border to border, and that the inspector's out/in lists
are the only way to delete one. Names need not be unique across a machine, only
among the transitions leaving the same state, and that is exactly what makes one
`State transition` node usable from several states. The node itself is the third
way a behavior reads a machine, beside `State branch` and `Set state`.

Concepts, Home: the summary lines name `State transition` too.

Development: 844 -> 862 checks.
root committed 2026-09-14 14:06:07 +02:00
1 parent 6e524c262e
commit 7b5974b37c
4 files changed
+44 -11

No files matched your search

+1 -1
@@ -3,7 +3,7 @@
| **Asset** | Named thing with a type (`scene`, `camera`, `sprite`, `text`, `behavior`, `collider`, `container`, `texture`, `animation`, `statemachine`, `shader`, `font`, `particles`) and typed properties. Assets have **no position**. |
| **Texture** | An uploaded image. Scenes, textures, animations, state machines, shaders and fonts are the six types that cannot be placed: a texture has no position, it is *worn* by a sprite through the ordinary property `texture`. See [Textures](Textures). |
| **Font** | An uploaded typeface, worn by a text through the ordinary property `font`; no font means the editor's own. Uploaded and picked exactly like a texture, and stored the same way. See [Fonts](Fonts). |
| **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state` and branches on it with `State branch`; each behavior instance keeps its own current state. See [State machines](State-machines). |
| **State machine** | A named set of **states**, edited as nodes in its own tab. A behavior puts it into a state with `Set state`, moves it along a named transition with `State transition` and branches on it with `State branch`; each behavior instance keeps its own current state. See [State machines](State-machines). |
| **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite wears it through `shader`; the numbers the graph declares are rows below that dropdown. See [Shaders](Shaders). |
| **Particles** | A node graph that says what *one* particle is as a function of its age and of a random number drawn once for it — no forces, no simulation. Unlike the other graph assets it **is** placeable: the object made from it is a source, whose rows say how many go out and for how long they live. Because a particle is a formula, the same second always draws the same picture and the tab's slider can jump to any of them exactly. See [Particles](Particles). |
| **Text** | An asset that draws a line of text (`text`, `font`, `fontSize`, `color`, `align`) — titles, HUD readouts, button labels. Its content is an ordinary property, so a `Properties` node of type Text with `text` switched to Set changes it while playing. |
+2 -2
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 844 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 862 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
@@ -165,4 +165,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 844 checks as here.
WebGL 2.0, and the same 862 checks as here.
+1 -1
@@ -15,7 +15,7 @@ Live: <https://nockal.com/game-maker/> · Widget playground: <https://nockal.com
| [Colliders](Colliders) | Collider assets, collision events, blocking movement |
| [Textures](Textures) | Getting images in, where they go, tinting, the filter, frames, upload limits |
| [Animations](Animations) | Animation assets, the Animation tab, how they run |
| [State machines](State-machines) | The state machine asset, `State branch` and `Set state` |
| [State machines](State-machines) | The state machine asset, named transitions, `State branch`, `Set state` and `State transition` |
| [Shaders](Shaders) | The shader asset, the Shader tab, the node set, the Adjust colors recipe, wearing a shader |
| [Containers](Containers) | Editing a template, instances and their edits, screens without scene switching |
| [Keyboard](Keyboard) | Every shortcut |
+40 -7
@@ -1,14 +1,40 @@
A **state machine asset** is a named set of states and nothing more, yet: its graph holds one
`State` node per state (a title and a *Name* setting, no ports) and no edges. Transitions,
events like "on state entered" and a highlight of the current state while playing are later
steps. The states are edited in the **State machine** tab, which is the graph canvas the Behavior
tab uses — so placing, dragging, the header's **Snap** checkbox, renaming in the inspector,
`Delete` and undo are the graph's.
A **state machine asset** is a named set of states and the moves allowed between them: its graph
holds one `State` node per state (a title and a *Name* setting) and one edge per transition. Events
like "on state entered" and a highlight of the current state while playing are later steps. The states are edited in the **State machine** tab, which is the graph
canvas the Behavior tab uses — so placing, dragging, the header's **Snap** checkbox, renaming in
the inspector, connecting, `Delete` and undo are the graph's.
Double-clicking the asset (or creating it) opens the tab, like every asset with a tab; a click
only selects. Right-click on its canvas offers a `State` and
nothing else, named by its number (`State 3` for the third); no behavior palette offers one.
**A behavior reads it through two nodes**, both with a `machine` input that is a picker while
## Transitions
A **transition** is an arrow from one state to another. Right-click a state, choose
**Make transition**, then click the state it should lead to; anywhere else, or `Escape`, drops the
attempt. It joins two whole states, so there is nothing to aim at on either node and **no pins are
drawn**.
Underneath it is an ordinary graph edge, which is the whole point: connecting, undo, the clean-up
when a state is deleted and the save format all come from the graph and needed nothing new. A state
therefore still *carries* an `in` and an `out` port — a port is the only anchor an edge has in this
editor — the editor simply never draws them and never lets them be grabbed. Between two states the
edge is a **straight arrow** rather than the usual curve, running border to border with a little
air at each end. Two transitions between the same pair are pushed apart, so a there-and-back pair
lies side by side instead of on top of each other. Two arrows the *same* way round are refused
(`Already connected`), because every transition hangs on the same two ports.
A transition can be **named**. Select a state and the inspector lists its transitions in two
groups, *out* and *in*, each row with the state at the far end, a name field and a button to
remove it — that list is also the only way to delete a transition, since the arrow itself cannot
be clicked. The name is drawn on the arrow. Names need **not** be unique across the machine: what
must not repeat is two transitions of the same name leaving the *same* state (the inspector warns
when they do). That is the point of naming them — a single `State transition` node asking for
`alarm` moves every state that has an `alarm`.
A transition does **not** restrict `Set state`: that node still puts the machine into any state it
is wired to. What the arrows are for at runtime is `State transition`.
**A behavior reads it through three nodes**, each with a `machine` input that is a picker while
unconnected (or takes a `State machine` node, the literal every kind has):
- **`State branch`** grows **one exec output per state**, named after the state, and continues
@@ -19,6 +45,13 @@ unconnected (or takes a `State machine` node, the literal every kind has):
flow *arrives through* names the new state — so `OnStart → Set state (Open)` and `On key down →
Set state (Closed)` are two edges into one node. The interpreter hands every node the exec input
a pulse entered through (`run(…, entry)`); this is the first node to read it.
- **`State transition`** takes the transition of a chosen **name** out of the current state, then
continues. The name is picked in the inspector from the transitions of the machine on its
`machine` input — the options depend on another field, which no field kind can express, so that
picker is written by hand. Leaving it at *(unnamed)* follows the one transition nobody named.
If the current state carries no transition of that name, or **two** of it, the machine stays
where it is — choosing between two arrows the graph never picked between would be a guess. It
reads the machine's transitions through the project lookup the interpreter already carries.
The ports are keyed by the **state's node id** and labelled by its name: renaming a state keeps
every edge. Deleting one is the awkward case, because the port lives in a *behavior* while the