Variables and state machines can reset with every scene (#164)
A shared variable or machine used to live either per object or for the
whole run. Levels need a third lifetime: shared like global, but built
fresh from the declarations whenever a scene is loaded.
- New scope `scene` for Variables assets and state machines. A missing
field keeps the old meaning: `global` for assets, `local` for
machines, so existing projects do not change.
- Returning to a scene that was visited before is fresh as well. There
is no memory per scene.
- A `scene` machine announces its start state again after a scene swap,
so `On state entered` in the new scene hears it. A global machine
stays quiet because it never left its state.
- The rule lives in two pure functions in `sharedState.ts`
(`carriedForward`, `restartedAssets`). BehaviorSystem only calls them.
- The inspector shows a Scope dropdown on Variables assets and a third
step on the machine dropdown, with a note on what `scene` means.
Decisions:
- `scene` and `persistent` exclude each other (variant A). The choice
dialog was cancelled, so this is an assumption to revisit. A
scene-scoped asset hides the Persistent switch, and switching to
`scene` drops the flag (undo restores it). The guard sits in
`isPersistent` itself, so no path can save values the scope throws
away anyway.
Findings:
- `clickText('Scene')` hits the Scene tab, not the menu entry. One e2e
check was red because of that, and its partner was falsely green
because it compared `undefined` with `undefined`. Both use `clickLast`
now.
Tests:
- ./check.sh: 144 files, 2142 tests, CHECK EXIT 0 (before 143 / 2133)
- 9 new unit tests in tests/core/scene-scope.test.ts
- e2e 32 fresh: ALL PASSED, 17 checks
- e2e 1-3,17,18,26,32,35 fresh: ALL PASSED, 213 checks
- 3 new e2e checks (35: scene-scoped asset resets on swap, 32h: dropdown,
note, hidden Persistent switch)
- Counter-checks bite: ignoring `restarted` fails 2 unit tests and the
new check in 35, the old announcement rule fails 1, dropping the scope
guard in `isPersistent` fails 1. Originals restored, cmp IDENTICAL.
- Check sum 1307 (1304 + 3) is computed. Last CI measurement: 1293.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e3e8d23c5
commit
d7c0b7cfdb
12 files changed
+487
-43
No files matched your search
@@ -4,6 +4,7 @@ import {
|
||||
SetAssetPropertyCommand,
|
||||
SetMachineScopeCommand,
|
||||
SetVariablesPersistentCommand,
|
||||
SetVariablesScopeCommand,
|
||||
} from '../../core/commands/assetCommands';
|
||||
import { isPersistent } from '../../core/graph/savedValues';
|
||||
import { nextFieldName, nextVariableName } from '../../core/commands/naming';
|
||||
@@ -19,7 +20,7 @@ import type { Command } from '../../core/commands/Command';
|
||||
import type { Asset, BehaviorAsset, StateMachineAsset, VariablesAsset } from '../../core/model/Asset';
|
||||
import { findAsset, type Project } from '../../core/model/Project';
|
||||
import type { PropertyBag, PropertyValue } from '../../core/model/Properties';
|
||||
import { isGlobal, readScope, scopeOf } from '../../core/model/Scope';
|
||||
import { readScope, readSharedScope, type Scope, scopeOf, sharedScopeOf } from '../../core/model/Scope';
|
||||
import { type StructFieldKind, VARIABLE_KINDS } from '../../core/properties/kinds';
|
||||
import {
|
||||
isRowShown,
|
||||
@@ -52,8 +53,9 @@ export function assetSignature(project: Project, assetId: string): string {
|
||||
if (!asset) return 'missing';
|
||||
// A machine's own rows are none, so its scope has to be named here or switching it rebuilds nothing.
|
||||
const machine = asset.type === 'statemachine' ? `|${scopeOf(asset)}` : '';
|
||||
// The same for the switch that keeps shared values between runs: its notes and button depend on it.
|
||||
const kept = asset.type === 'variables' ? `|${isPersistent(asset)}` : '';
|
||||
// The same for the switch that keeps shared values between runs and for the scope beside it: the
|
||||
// notes, the button and whether the switch is shown at all depend on the two.
|
||||
const kept = asset.type === 'variables' ? `|${isPersistent(asset)}|${sharedScopeOf(asset)}` : '';
|
||||
// Vector art shows its shapes under its rows (#129), so a shape added or renamed is a new structure.
|
||||
const shapes = asset.type === 'vectorart' ? `|${shapeListSignature(asset)}` : '';
|
||||
const values = resolveAssetProps(project, asset);
|
||||
@@ -81,7 +83,10 @@ export function buildAssetSection(host: InspectorHost, assetId: string): void {
|
||||
|
||||
buildProps(host, asset, markedAssetSchema(host.state.project, asset));
|
||||
if (asset.type === 'behavior' || asset.type === 'variables') buildVariables(host, asset);
|
||||
if (asset.type === 'variables') buildPersistence(host, asset);
|
||||
if (asset.type === 'variables') {
|
||||
buildVariablesScope(host, asset);
|
||||
buildPersistence(host, asset);
|
||||
}
|
||||
if (asset.type === 'statemachine') buildMachineScope(host, asset);
|
||||
// The drawing itself: its shapes are picked here as a texture's frames are picked in its tab (#129).
|
||||
if (asset.type === 'vectorart') buildShapeListSection(host, asset);
|
||||
@@ -214,6 +219,12 @@ const PERSISTENT: PropertyDef = { key: 'persistent', label: 'Persistent', kind:
|
||||
function buildPersistence(host: InspectorHost, asset: VariablesAsset): void {
|
||||
const set = (value: PropertyValue): void =>
|
||||
void host.state.execute(new SetVariablesPersistentCommand(asset.id, value === true));
|
||||
// Nothing to keep between runs when every scene throws the values away – the switch is not shown.
|
||||
if (sharedScopeOf(asset) === 'scene') {
|
||||
addNote(host, 'One value each, built again for', 'small');
|
||||
addNote(host, 'every scene that is loaded.', 'small');
|
||||
return;
|
||||
}
|
||||
const row = createFieldRow(host, PERSISTENT, isPersistent(asset), { onInput: set, onChange: set });
|
||||
host.body.add(row.row);
|
||||
// Two lines rather than one: the panel is narrow enough to cut a longer note off mid-sentence.
|
||||
@@ -247,20 +258,44 @@ const MACHINE_SCOPE: PropertyDef = {
|
||||
options: [
|
||||
{ value: 'local', label: 'Local' },
|
||||
{ value: 'global', label: 'Global' },
|
||||
{ value: 'scene', label: 'Scene' },
|
||||
],
|
||||
};
|
||||
|
||||
const MACHINE_SCOPE_NOTES: Record<Scope, string[]> = {
|
||||
local: ['Each behavior instance keeps its own current state.'],
|
||||
global: ['Every behavior using this machine shares its', 'current state, for the whole run.'],
|
||||
scene: ['Shared like global, but it starts over', 'whenever a scene is loaded.'],
|
||||
};
|
||||
|
||||
/** A state machine has no properties of its own, so the inspector shows just its name and this. */
|
||||
function buildMachineScope(host: InspectorHost, asset: StateMachineAsset): void {
|
||||
const set = (value: PropertyValue): void =>
|
||||
void host.state.execute(new SetMachineScopeCommand(asset.id, readScope(value)));
|
||||
const row = createFieldRow(host, MACHINE_SCOPE, scopeOf(asset), { onInput: set, onChange: set });
|
||||
host.body.add(row.row);
|
||||
addNote(
|
||||
host,
|
||||
isGlobal(asset)
|
||||
? 'Every behavior using this machine shares its current state.'
|
||||
: 'Each behavior instance keeps its own current state.',
|
||||
'small',
|
||||
);
|
||||
// Short lines rather than one: the panel is narrow enough to cut a longer note off mid-sentence.
|
||||
for (const line of MACHINE_SCOPE_NOTES[scopeOf(asset)]) addNote(host, line, 'small');
|
||||
}
|
||||
|
||||
/** The same switch for a Variables asset, which has no `local`: the asset is the shared thing. */
|
||||
const VARIABLES_SCOPE: PropertyDef = {
|
||||
key: 'scope',
|
||||
label: 'Scope',
|
||||
kind: 'enum',
|
||||
default: 'global',
|
||||
options: [
|
||||
{ value: 'global', label: 'Global' },
|
||||
{ value: 'scene', label: 'Scene' },
|
||||
],
|
||||
};
|
||||
|
||||
function buildVariablesScope(host: InspectorHost, asset: VariablesAsset): void {
|
||||
const set = (value: PropertyValue): void =>
|
||||
void host.state.execute(new SetVariablesScopeCommand(asset.id, readSharedScope(value)));
|
||||
const row = createFieldRow(host, VARIABLES_SCOPE, sharedScopeOf(asset), {
|
||||
onInput: set,
|
||||
onChange: set,
|
||||
});
|
||||
host.body.add(row.row);
|
||||
}
|
||||
Reference in new issue
Block a user