A state change is something the graph can react to, so the machine announces it (#53, #54, #55, #57)
game-maker / suite (push) Successful in 10m58s

Four issues that all reach for the same line: how the current state is
read and written. Splitting them into four commits would mean four
states of the tree that do not compile, so they arrive together.

#55 start state — the machine asset carries an optional `start` holding
a State node's id. Right-click a state to set or remove it, a ring in
the node's title says which one it is. A dead id (the state was deleted)
reads as "no start", so nothing has to clean up after a deletion.

#53 entered/exited — `On state entered` and `On state exited`, one exec
output per state. Every change now goes through a single `setState`
that reports it; BehaviorSystem delivers it after the exec flow that
caused it, `exited` of the old before `entered` of the new, round after
round until the queue is empty, with a cap that stops a ping-pong graph
with an endless-loop error. A local machine tells the instance that
switched, a global one tells every instance holding a matching event
node. The start state fires `entered` too: per instance for a local
machine, once per run for a global one.

#57 reading — `Current state` with `name` for showing and `id` for
comparing, `Is in state` with one boolean per state. Neither has a
`run`, so `read` learned `def` and `lookup`, and the catalog test knows
this third kind of node.

#54 seeing it — during a run the state machine tab marks every state
some instance is holding faintly, and the one held by the selected
scene object's instance strongly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-15 06:18:20 +02:00
1 parent bc57f61c91
commit 7babf88636
30 files changed
+906 -44

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
import type { NodeDef } from '../../NodeRegistry';
import { currentState } from '../../sharedState';
import { machinePort } from '../../stateMachine';
import { asString } from '../../values';
import { str } from '../shared';
/**
* The data side of `State branch`: two outputs, because the two uses pull apart. `name` is what
* `Join` and a `Text` want to show, `id` is what a comparison should hold – a rename moves the
* name, never the id. Both are empty while the machine is in no state.
*/
export const CurrentState: NodeDef = {
type: 'CurrentState',
label: 'Current state',
category: 'data',
description: 'The state the machine is in: its name to show, its id to compare. Empty for no state.',
inputs: [machinePort()],
outputs: [str('name'), str('id')],
params: [],
read: (ctx, _params, input, _def, lookup) => {
const machine = asString(input('machine'));
const id = currentState(ctx, machine);
const name = (id && lookup.machine(machine)?.states.find((s) => s.id === id)?.name) || '';
return { name, id };
},
};
+26
View File
@@ -0,0 +1,26 @@
import type { NodeDef } from '../../NodeRegistry';
import { currentState } from '../../sharedState';
import { machineOf, machinePort, statePorts } from '../../stateMachine';
import { asString, type Value } from '../../values';
/**
* One boolean per state, all false while the machine is in none – the data twin of `State branch`,
* for the places a flow cannot go: `Branch`, `And`, a property. Every output is answered, so a
* state nobody is in reads `false` rather than nothing at all.
*/
export const IsInState: NodeDef = {
type: 'IsInState',
label: 'Is in state',
category: 'data',
description: 'True on the output of the state the machine is in, false on every other.',
inputs: [machinePort()],
outputs: [],
params: [],
derivePorts: (source) => ({ outputs: statePorts(machineOf(source), 'boolean') }),
read: (ctx, _params, input, def) => {
const current = currentState(ctx, asString(input('machine')));
const values: Record<string, Value> = {};
for (const port of def.outputs) values[port.key] = port.key === current;
return values;
},
};