Commit Graph
16 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 c74b6b1cab Show where an inspector row can be dragged and answer the pointer
game-maker / gate (push) Successful in 3m10s
game-maker / suite (push) Failing after 16m38s
After #310 rows could be dragged by their mark, but nothing said which
marks take a drag, and the pointer on one changed nothing but the
cursor.

- A mark that drags is now a Grip: the same widget wrapped, the same
  size, so the value column stays where it was. Once sortBlocks takes
  it, it carries six faint dots at its left edge.
- Marks that cannot drag stay plain: the variables of a placed object
  and the fields of a typed struct show no dots.
- With the pointer on a grip its dots light up and the whole block that
  would move is washed in the accent colour, a struct with its fields.
  While the block is carried the wash gives way to the dimming, and the
  grip stays lit.
- While a block is carried the other grips let the pointer pass: no
  lit dots, no wash. Every grip of the column shows grabbing instead of
  grab until the drop.
- The wash first used controlHover like list rows. In the inspector it
  is darker than the panel and vanished next to the dark fields, so it
  became the accent at 15 percent, the colour of the drop line.
- __gm.inspectorMark also reports the dots: none, idle or lit.

Tests:
- Gate on melektaus-atx: 236 files, 3576 tests (+4 in
  block-drag.test.ts, one of them a drag passing over another grip).
- e2e 22: 40 checks (+1, sum 1740). 22f-iii reads idle dots, moves the
  mouse onto the mark and reads them lit.
- Two counter-checks bite: no wash on hover fails 1 test, grips never
  made grabbable fail 3. A third: hover ignoring the drag fails 1.
- Screenshots of a lit single row and a lit struct block looked at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:11:41 +02:00
adminandClaude Opus 5.5 2c19c89878 Let variables, fields, ports and list items be dragged into order (#310)
game-maker / gate (push) Successful in 3m10s
game-maker / suite (push) Failing after 19m34s
Variables of a behavior, the fields of a struct, the ports of a start
or end node and the items of a list could only be added and removed.
Changing their order meant removing and adding them again. Now each of
them is dragged by the mark in front of its row.

- sortBlocks (ui/blockDrag.ts) makes the rows of one column sortable.
  A block is every row it covers, so a struct moves with its fields and
  a list item with its nested list. The block is dimmed while it is
  carried, an accent line shows where it lands, and a scrolled panel
  follows the pointer at its edge. Less than 4 px stays a click.
- The arithmetic is pure and lives in ui/sortable.ts: blockGap,
  gapLineY, dropSlot and edgeScroll. dropIndex of the tile strip moved
  there too, so the reorder maths sits in one file.
- MoveVariableCommand, MoveStructFieldCommand and MovePortCommand
  build on the existing MoveCommand. Undo moves back.
- Lists need no command. InlineList replaces the list as a whole, as
  it does for every other edit of its items.
- The grip is the kind mark, for a list item its number. Moving only
  happens among siblings: a field stays in its struct.
- On a Struct asset the move runs through WithStructTypes. Typed
  variables, Pack/Expand ports and split outputs follow the new order.
- Values and edges hang on names, so only the order changes. Nodes
  running a behavior read their ports from the start node, the new
  order reaches them without a copy.
- Not sortable by design: the variables of a placed object, the fields
  of a typed struct (moved on the Struct asset) and the picked ports
  of Variables/Properties nodes.
- Test hook __gm.inspectorMark(label) finds the mark of a row.

Tests:
- Gate on melektaus-atx: 235 files, 3572 tests, CHECK EXIT 0 (+9).
- tests/core/reorder.test.ts: a variable, a field two levels deep, a
  Struct asset reaching a typed variable and Pack, and start ports
  reaching the calling node with edges and result kept.
- e2e 22: ALL PASSED, 39 checks (+4, sum 1739). 22f-iii drags a
  variable past a struct of several rows, then a field inside it.
- Counter-probes: dropSlot by top edge instead of middle and a field
  move that ignores its path fail 3 unit tests. With sortBlocks
  switched off, the three order checks of 22f-iii fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 21:45:04 +02:00
adminandClaude Opus 5.5 ec2fd9084f Say what a button without words does when the pointer rests (#209)
game-maker / gate (push) Successful in 2m50s
game-maker / suite (push) Successful in 15m34s
The top bar, the magnet, the eye and many marks of the inspector are
pictures without a caption. Whoever did not know one could only try
it. The toolkit had no hint on hover, which also forced the point
menu of #195 to be words. The answers in the issue settle the open
points: a cut tab name shows in full, shortcuts are part of the
text, the point menu stays as it is.

- Button takes a tooltip, a text or a function asked each time. It
  shows on a small sign once the pointer has rested for half a
  second.
- There is one sign for the page, a TooltipLayer on the overlay
  above the menus. A control reports each pointer move and the
  pointer leaving, the layer does the waiting.
- A press, a key, the wheel or a blur of the window takes the sign
  away. initUI listens for them on the window.
- placeTooltip in ui/tooltip.ts is the rule for the place as a pure
  function: under the control and flush left, flush right at the
  right border, above the control at the bottom.
- toolbarTip says what the five buttons of the top bar do right now.
  Undo and redo name their step from CommandStack.undoLabel and
  redoLabel, as in "Undo Paint tiles (Ctrl+Z)".
- A SortableList reports its own marks with a key per mark: the eye,
  the duplicate mark, the cross and the plus.
- The magnet, both fullscreen buttons, the eye of the scene tab, the
  overflow of the tab strip, the plus of the assets panel, the marks
  of the inspector rows and the nine anchors of the resize dialog
  carry a text.

Decided here:

- Every button whose text is cut short shows the whole text, not
  only a tab.
- A row action of the inspector names the one thing a click does. A
  button that opens a menu says only Reset, Apply or Size from the
  picture.
- A button just clicked stays quiet until the pointer has left it.
- A greyed out button shows no sign.
- The switches of the eye popup, the cross of a document tab and the
  tools of pixel art have none. The tools carry words, other than
  the issue assumes.
- The marks of a list say Show or Hide, Duplicate, Remove and Add,
  whatever the list holds.

Found on the way:

- Signing out empties the overlay and would have destroyed a sign on
  screen with it. The editor takes the tooltip away first. This has
  no test of its own.
- The text hooks locate and has skip the sign, or it would answer
  for the button under it.

Tests:
- Gate on melektaus-atx: 210 files, 3209 tests (3176 + 33), exit 0
- e2e 46 on melektaus-atx: 20 checks, all pass
- e2e 1-14 on melektaus-atx: 508 checks, all pass
- e2e 1-9,14,39,40,44-48: 685 pass, 2 fail, both in module 14. It
  measures tabs that modules 10 to 13 open, and the chain 1-14 is
  green. The cut chain was not run on the state before.
- No full run and no smokes. The sum of 1667 (1664 + 3) is reckoned,
  1664 is what CI counted for 3452899.
- 33 unit tests in tests/ui/tooltip.test.ts
- Three e2e checks in block 46h of module 46: the redo button after
  the wait, taking the sign away, turning at the right border
- 50 unit counter-probes and four browser counter-probes all fail
  their tests. Three of the unit ones did not at first and got
  sharper tests.
- Not checked in the browser: the sign of a cut tab, the marks of
  the lists, turning upwards at the bottom, each other button alone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:02:57 +02:00
adminandClaude Opus 5.5 e8b3ac0a3b Keep behavior variables private to their own graph (#257)
game-maker / gate (push) Successful in 3m40s
game-maker / suite (push) Successful in 10m56s
A behavior often needs working state such as a timer or a counter.
Until now every variable showed up on each placed object and in the
Properties menu, so the inspector filled up with values nobody should
touch. A private variable works like any other inside its graph but
is not offered anywhere outside.

- Each behavior variable gets a lock switch next to Keep. It starts
  off, so existing projects stay as they are (`privateVariables`).
- A placed object shows no private variable, neither in the
  inspector nor while the game runs. If all are private, it says so.
- Switching a variable to private drops its overrides on every
  placed object in one undo step. Switching back does not restore
  them.
- A running game does not write a private value back as an
  override, and a project change during the run does not reset it.
- `+ Property` no longer offers private variables. A port that
  names one by hand still reads and writes it without a warning.
- The default value of a private variable is read-only in the asset
  inspector. Its shape (name, fields, type, delete) stays editable.
- Keep is now a pin icon instead of a word. With the lock added, the
  header of a list variable had no room left and the lock covered
  `+ Item`.

Not done: the animation track picker still offers private variables.

Tests: ./check.sh -> 186 files, 2711 tests, CHECK EXIT 0.
e2e 1,19,20,21,56 -> ALL PASSED, 119 checks. New module 56 has
7 checks with a real mouse. Counter-checks fail as expected: unit
without the write-back filter, the sync guard, dropping overrides or
loading the flag. Browser with all variables on the object, an
unfiltered menu, or no value lock.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:54:57 +02:00
adminandClaude Opus 5.5 b0d0868f55 Give a list one struct type so every item keeps its shape (#254)
game-maker / gate (push) Successful in 3m28s
game-maker / suite (push) Successful in 10m38s
A list of structs used to let every item carry its own fields. Adding
an item meant typing the same fields again, and nothing kept the items
in step with the struct asset from #253. A list can now name a struct
type for its items, and the asset keeps them in shape.

- A list variable and a list field get the row Items with Mixed, every
  struct asset and Make struct type. Make struct type collects every
  field any item has, so no item loses a field.
- A typed item carries its type under the key `$struct`
  (ITEM_TYPE_KEY). listItems does not count it as a field.
- A list that holds anything but structs refuses a type, also when
  the offending item sits in an override. The console names the list
  and the count, and the list stays as it was.
- In a typed list `+ Item` adds an item with the defaults, without the
  kind menu. Field names are locked, values stay editable.
- The rows of a typed item take their kind from the struct asset
  (declaredKind in InlineList.ts). An asset field shows the asset
  picker instead of a text field, as reported in the issue.
- A struct item in a mixed list gets its own Type row. Switching a
  list back to Mixed keeps the type on each item.
- syncStructTypes brings marked items into shape and carries renames
  into them, also inside overrides and nested lists. One undo step.
- The delete dialog of a struct asset also counts typed lists.
- The game never sees the key. World and sharedState strip it with
  withoutItemTypes and bagWithoutItemTypes, so a struct stays a
  plain object at runtime.
- Add item and Set item get the setting Type. With a type they only
  accept a struct with exactly its field names (fitsType in
  SetItem.ts). Anything else is left out and logged.
- The item indent from #252 is back to its old width, the narrow one
  squeezed the value fields.

Not done: writing a variable does not check the type, and the List
node offers no type.

Found on the way: a test helper in list-of-structs.test.ts called
nodes without params. It now passes the same arguments as the
interpreter.

Tests:
- ./check.sh: 183 files, 2679 tests, CHECK EXIT 0
- e2e GM_STEPS=1,21,22,32,54,55: ALL PASSED, 150 checks
- e2e GM_STEPS=1-6,21,22,32,54,55 before the follow-up: 404 checks
- New: tests/core/typed-lists.test.ts, e2e module 55 with 13 checks
- Counter-checks bite in unit and browser, originals restored by cmp

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 07:23:00 +02:00
adminandClaude Opus 5.5 61bbb96a96 Define a struct once as an asset and reuse its shape (#253)
game-maker / gate (push) Successful in 3m30s
game-maker / suite (push) Successful in 10m33s
A struct used to declare its fields wherever it stood. The same shape
in two variables meant typing it twice, and Pack/Expand copied it once
without staying in sync. A Struct asset now holds the shape in one
place, and every struct that picks it as its type follows it.

- New asset type "Struct" (not placeable, group Structs, own icon).
  The inspector shows its fields with name, kind and default value,
  and "Open" shows them in the variables tab.
- Struct variables and struct fields get a Type row: own fields, any
  Struct asset, or "Make struct type". With a type the fields come
  from the asset and cannot be added, renamed or removed there. The
  values stay per variable, overrides on placed objects still work.
- Pack and Expand get the setting Type. With a type their ports are
  the fields of the asset and read-only in the inspector.
- A change to the asset reaches every typed place in one undo step
  (syncStructTypes). A new field arrives with its default. A removed
  field goes along with its port, edge and typed value. A renamed
  field keeps its value in variables, overrides and on ports with
  edges, also through nested types.
- Undo for these edits records plain assignments (Recorder), because
  the reach of a change is not known in advance.
- Cycles such as Loot in Loot, also through another type, are refused
  and not offered in the picker.
- Deleting the asset names how many places use it. Afterwards each
  place keeps its fields as its own.
- Loading reshapes a stale copy in a project file to its type.
- "Make struct type" creates an asset from an existing struct and
  links the place to it.

Differs from the plan:

- A changed default value applies to new fields and newly typed
  places only. Existing values stay.
- Removing a single field shows no warning. Undo brings it back.

Typed lists are left for #254. Lists stay dynamic for now.

Found by e2e: after deleting or renaming the type the inspector did
not rebuild and kept showing the old name. Fixed, the check was red
before.

Tests:
- ./check.sh: 182 files, 2661 tests, CHECK EXIT 0
- GM_STEPS=1,4,21,22,54: ALL PASSED, 176 checks
- 14 unit tests in tests/core/struct-types.test.ts
- New e2e module 54 with 14 checks, real mouse. Module 04 counts
  23 asset types.
- Counter-probes: without the rename pass 4 unit tests fail, without
  reshaping values 8, without the cycle rule 1. In the browser,
  without spreading changes and with + Field despite a type, 3
  checks fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 06:04:40 +02:00
adminandClaude Opus 5.5 4857bb75e6 Show lists in the inspector and variables in their own tab (#252)
game-maker / gate (push) Successful in 3m24s
game-maker / suite (push) Successful in 10m22s
The List tab could show one list at a time and was the only place to
see its items. A list now shows its items right where it lives, and a
new Variables tab shows all variables of an asset or placed object.

- InlineList draws the items of a list under its row in the inspector,
  in struct fields and in the Variables tab. A list inside a list or a
  struct field is drawn the same way, one step indented.
- The index column is the indent. Items sit under their list without an
  own margin, and the index column is 16 px wide instead of 28 px.
- Above 20 items (INLINE_LIMIT) the rest collapses into "... N more -
  open in tab". During a run the rows show the values from the game.
- ListHead puts "N items" and "+ Item" in one row. "N items" opens the
  list in a tab. A separate Open button squeezed the counter to zero
  width in the narrow inspector column.
- VariablesEditor shows every variable of a behavior asset, a variables
  asset or a placed behavior. On an asset it edits the declarations. On
  a placed object an edit only overrides that object, with the usual
  reset and push buttons.
- Opening a list inside a tab adds a new tab and keeps the open one.
  valuesKey gives each list its own document, so a list that is already
  open is shown instead of opened twice.
- Titles read "Pickup · Variables" or "Listy · nums · [3]". The tab
  closes when its asset or object is deleted.
- Lists on node inputs stay inline only, a node owns no variables.
- The List tab, ListEditor and the __gm.list hook are gone.

Tests:
- ./check.sh: 181 files, 2645 tests, CHECK EXIT 0
- GM_STEPS=1,21,22,32: ALL PASSED, 123 checks
- e2e 21 is rewritten for inline lists and the new tab, 22 and 32 are
  adapted. Sum in README: 1486 (+6 in 21, -1 in 22), counted by hand.
- Counter-probes fail as expected: sub-lists not told apart (1 unit
  test), sub-list replacing the open tab, all items drawn inline (60
  rows instead of 20), no live values (0 rows at 24 items in a run).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:56:21 +02:00
adminandClaude Opus 5.5 0c5ac32b59 Let structs and lists nest to any depth (#239)
game-maker / gate (push) Successful in 3m52s
game-maker / suite (push) Successful in 10m24s
Structs and lists were capped at two fixed levels (list of structs,
struct with a list field). Anything deeper was dropped silently on
load, in node settings and when written to a variable. The cap came
from an early design choice, not from a technical need, so it is
lifted completely instead of adding a third fixed level.

- One kind list NESTED_KINDS replaces ListItemKind and
  StructFieldKind. The flat special cases in listItems.ts are gone,
  every value goes through one recursive path.
- A struct inside a struct is declared, like a top-level struct.
  Values coming in over an edge are brought into that shape. Struct
  items in lists still take their fields from the value.
- New valuePath.ts addresses any nested spot by keys and indices.
  ListSlot.field is now ListSlot.path.
- Field commands (add, rename, remove) take the path to the struct.
  They change declaration, own value and all overrides in one undo
  step, as before on the top level.
- Inspector: "+ Field" offers Struct and List on every level. A
  nested struct is a header row with its own "+ Field", its fields
  are indented one step further.
- List tab: a list item shows "n items" and opens one level deeper.
  The title shows the path, e.g. "Listy · nums · [3]".
- Pack in Pack now writes a struct into a struct field instead of
  text. Objects are kept at any depth and stripped at every exit.
- A list edit in a struct field without an existing object override
  now starts from the shown value instead of an empty struct.
- Old projects load unchanged. Save, load and save again gives the
  same result.

Tests:
- ./check.sh: 172 files, 2498 tests, CHECK EXIT 0
- e2e 21 + 22: ALL PASSED, 90 checks (new 21d-iii, 22f-ii,
  21d and 22f updated)
- 20 new unit tests in nested-values.test.ts and
  nested-list-slot.test.ts, three tests that pinned the old cap
  turned around
- 6 counter-probes, each fails 1 to 8 tests, originals restored
  and checked with cmp
- Not covered: a nested struct inside a struct item in the list
  tab has no e2e click

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 22:33:20 +02:00
adminandClaude Opus 5.5 454d202109 Let each variable of a behavior choose whether calls keep it (#229)
game-maker / gate (push) Successful in 3m13s
game-maker / suite (push) Failing after 8m46s
The first version of #229 put a Keep state switch on the Run behavior
node. The user wanted it where the variable is declared instead. Whether
a value lives on belongs to the variable, not to every call site.

- The node switch keepState is gone again. RunBehavior, CallResult and
  nested.ts are back to what they were before #229.
- BehaviorAsset.keptVariables lists the kept names. It lives on the
  asset and not in PropertyDef, because that is the schema of every
  inspector row.
- A variable added from now on starts with Keep on. Variables in
  existing projects start with Keep off, so old projects run unchanged.
- SetVariableKeptCommand flips the switch as one undo step. Add, remove
  and rename carry the list along. Loading drops names that are not
  declared.
- The interpreter keeps only the listed variables, per Run behavior
  node and per object as before. The others start at their defaults.
- Local state machines in a called behavior start empty on every call
  again, as before #229. Global and scene machines are unchanged.
- The inspector shows a Keep button in each variable row of a behavior.
  Its state is part of the asset signature, so a click redraws the row.
  Without that the button kept its old look until the inspector was
  reopened.

Found: the project Garden of Sacrifices still has keepState = true on
the call Player -> Character Movement. It is ignored now. Direction
there predates the switch, so Keep is off until set by hand.

Tests:
- ./check.sh: 163 files, 2392 tests, CHECK EXIT 0 (before 2388)
- tests/core/keep-state.test.ts rewritten, 15 tests
- e2e 19: ALL PASSED, 35 checks. 19c-ii now clicks the Keep button
  of a new variable: on when added, off by a click, on again, off
  after undo.
- Counter-checks, each fails its own test: keep list ignored (2),
  states kept (1), new variable not kept (8), rename drops the list
  (1), no Keep button (e2e 19c-ii).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 15:50:59 +02:00
adminandClaude Opus 5.5 0e8585665a Load the 500 weight and draw missing glyphs as SVG icons (#214)
game-maker / gate (push) Successful in 2m47s
game-maker / suite (push) Successful in 8m41s
The theme asks for Source Sans 3 at weight 500, but fonts.ts only
loaded 400, 600 and 700. The browser synthesised the 500 text. The
issue comment also asked for the symbols the font lacks to become SVG
icons. A scan of src/ against the font's cmap found 20 such characters
in 30 places.

- fonts.ts loads source-sans-3-latin-500-normal.woff2 as well.
- Menus draw their ticks and the "current" dot as icons. The dot is a
  new MenuItem.current field and no longer part of the label text.
- Inspector action buttons (reset, apply, fit width/height) are
  icons. The menu arrow behind them is the drawn Dropdown chevron.
- Kind marks come from a table in markIcons.ts. Picture kinds use an
  icon, # and Aa stay text because the font has them.
- Scene marks in the asset panel, sound play/stop and the transition
  arrows are icons too.
- Apply is an icon although the font has the arrow. It sits next to
  the reset icon and would look different as a glyph.
- A new Icon widget draws on redraw like Button, so Node tests need no
  DOMParser.
- Tests find icons through the SVG id. The new hook
  __gm.locateIcons(id) and the harness helpers findIcon/clickIcon
  replace locate() on the old characters.

Findings:
- Weight 700 is loaded but the theme never uses it. Left as is.
- The old scene-mark check in module 35 accepted any play glyph on
  screen. It now checks the mark on the start scene's row.
- Module 44 needs the textures from module 37 and fails without it.

Tests:
- ./check.sh: 145 files, 2187 tests, exit 0 (before 144 / 2143).
- New tests/ui/fonts.test.ts: every theme weight is loaded, and every
  character in src/ exists in every shipped face. The cmap reader
  matches fontTools (231 characters).
- Icon tests cover the 19 new files. Each kind mark is either an
  icon or text, never both.
- e2e 1-16,22,30,34,35,43,44: 586 green, 0 FAIL, then 44 stopped
  for lack of module 37. e2e 1-16,37,44,47,48: 618 green.
  e2e 1-14 on the final build: 454 green.
- Two new e2e checks: loaded weights in 14, a reset icon pixel in 04.
- Counter-probes all fail as expected: 500 removed, a play glyph put
  back, the struct icon removed (one unit test each). In the browser,
  500 removed and reset icon hidden fail exactly the two new checks.
- Editor-suite check sum 1312 (1310 measured by CI at 056a622, +2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 08:13:22 +02:00
rootandClaude Opus 5 8143130ed2 A shared value belongs to no behavior, so it becomes an asset of its own (#91)
game-maker / suite (push) Failing after 8m59s
This reverses the design of the previous commit. `PropertyDef.scope` is gone
without replacement: a behavior variable is an object property again, without
exception, and sharing is something you point at rather than something you
declare twice.

A **Variables asset** is declarations and nothing else -- no graph, no position,
never placed, no tab of its own (`TAB_OF_ASSET` is already `Partial<>`, so that
costs no line). A `Variables` node names one through its new `variablesAsset`
setting, empty meaning the behavior's own variables as before, and then reads
and writes one value per key for the whole run. Its property list follows the
chosen source, which was the actual request: a value another graph declared is
now offerable, where before every participant had to declare the name itself.

That deletes more than it adds (-72 lines net): `globalConflicts` and its
run-stopping kind clash, `SetVariableScopeCommand`, the scope picker, both
object-inspector filters and the "shared by the whole run" note on the placed
object. A typo is no longer a silent second variable -- naming an asset is
picking from a list. And two Variables assets may carry the same key without
meeting, because the asset is the key, not the name. The one shape it copies is
the one that already existed: `Set state` picks its machine the same way.

Lifetime is unchanged. The bag is built at `BehaviorSystem.start()` from the
declarations and never written back, so playing in the editor cannot change the
project -- which is the whole reason a counter must not be an object property
(`World.syncToModel` would `commit()` it, and the coin counter would stand at 7
for good).

Full symmetry across that gap, without mirroring anything. Two named wires:
`Inspector.liveVariables` shows the values as they stand while a run is on and
falls back to the declarations when it ends, and `adoptDeclarations()` takes a
key into the run *only where the document changed it since the last sync*.
Without that condition any unrelated project edit would snap a running counter
back to its starting value; a counter-test bends exactly that and falls over.
Nothing was ever cached, incidentally: `GraphInterpreter` only pulse-caches
nodes without a `read`, and the variable node has one. The wire was missing.

The state machine `Scope` row is now an ordinary `enum` row through
`createFieldRow`. It was hand-built from an `HStack` with a ghost dropdown and
began ~34px left of every value above it; the explanations beside `Local` and
`Global` are gone from the menu, the sentence below the row keeps saying it.
Machine scope is otherwise untouched -- `StateMachineAsset.scope`,
`SetMachineScopeCommand`, `stateBag()` and its persistence all stand, so #59
stays closed as built and `Scope` as a type now carries machines alone.

Six counter-proofs, each biting at its own guard. One of them did not bite at
first: the suggestion list was computed twice, in `nodeSection` and
`portSection`, so only the browser test covered the request itself. It is one
named function now, `variableChoices`, with a unit test of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 18:05:36 +02:00
rootandClaude Opus 5 d1fcfe8b81 A shared value belongs to the run, so a declaration carries its scope (#91)
A variable or a state machine declared `global` keeps one value for the whole
run instead of one per behavior instance. The scope sits on the declaration --
`PropertyDef.scope` and `StateMachineAsset.scope`, both optional, absence
meaning `local` -- and `local` is never written, so every project made before
this saves back unchanged.

**The name is the key.** Two behaviors declaring a global `score` mean one
variable; a behavior declaring `score` locally keeps its own. Kinds that
disagree about one global name stop the run before it starts, with the two
behaviors named; starting values that disagree are a warning on the behavior
that declares it, and the first declaration wins (`globalConflicts`).

The runtime needs no redirection layer. A global variable falls out of
`schemaForObject`, so it never reaches `entity.props` -- and then the whole rule
is "whoever declares the name takes their own value" (`graph/sharedState.ts`).
The `Variables` node is untouched and stays transparent: switching a variable to
global breaks no existing graph, because `GraphInterpreter.readAll` builds a
fresh object per read anyway and only asks `port.key in all`.

That same cut answers the inspector. A shared value has no owner, so the placed
object shows no row for it and can hold no override -- `SetVariableScopeCommand`
takes the deviations that were there and undo gives them back.

Both places say it in words rather than leaving a glyph to be guessed: the
declaration explains the mark below the list, and the placed object names the
variable it no longer holds instead of claiming the behavior declares none --
which is what it said before, untruthfully. Screenshots of the e2e run turned
that up; no check had been looking at it.

`SharedState` hangs off `NodeContext`, is built in `BehaviorSystem.start()` from
the *declarations* rather than from any instance (a global variable exists
before its behavior is placed), and is thrown away with the run: nothing is
written back, not even in the editor run. `GraphInterpreter.call` passes it on
through `...ctx` while nested calls keep getting fresh local variables, which is
why `program.defaults` is stripped of global keys.

Two traps this hit. `defSignature`/`assetSignature` have to carry the scope or
the inspector does not rebuild after the switch -- and a state machine has no
rows at all, so its scope is appended to the signature by hand. And the object
inspector does not go through `schemaForObject` for behaviors, it reads
`asset.variables` directly; filtering in one place was not enough, and no unit
test covered that path. The e2e caught it (`global: true` where false belonged),
and the rule now lives in one named function, `objectRows`.

Scopes `object` and `scene` are deliberately not built. The object case already
has a route (`Object by name` -> `Properties`), and a run knows exactly one
scene, so `scene` would be indistinguishable from `global` until #39 exists.

Closes #59: a machine's scope is a word on the machine asset, not a setting per
`Set state` node, so the same machine cannot live in two places at once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 16:22:53 +02:00
rootandClaude Opus 5 3ed8b68ecf Describe every value kind in one table instead of spreading it over the code
A kind like colour or texture used to be spelled out in five parallel
lists, its port type, its empty value, its glyph, its label and its list
tag - 22 literal places for texture alone. core/properties/kinds.ts now
holds one row per kind and the rest is derived from it, so adding a kind
is one row. The five literal nodes collapse into literals.ts, the six
kind menus into one helper.

Number, Text, Boolean is now the one order everywhere; the variable and
port menus used to list Boolean before Text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 14:47:17 +02:00
rootandClaude Fable 5.1 11c900c998 Drop the per-list item declaration; every item carries its own kind
A list no longer says what its items are. ListDef.items, ListDef.fields,
ListFieldDef.items and SetListItemsCommand are gone; the single seam is
src/core/properties/listItems.ts. JSON already tells number, text, boolean
and struct apart, so only two kinds need a tag: {"$color": n} and the new
{"$texture": id}. Field names cannot start with "$", so a tag never
collides with a struct field. On a data link a texture item arrives as
its bare id; the graph has no images.

List tab: the mark in front of each row is the kind picker, "+ Item"
asks for the kind, both are ordinary value edits (one undo step). A list
inside a struct field only offers the flat kinds because the depth rule
forbids a struct item there (ListAccess.kinds); the menu used to offer
it and silently do nothing.

Legacy lists that declared items: 'color' are tagged on load.

Actor demo: the four walk animations and four standing frames live in
two list variables of texture items indexed by facing (Item at), so the
graph can be rebuilt in the editor. The stand frames get their grid ids
back; 44 nodes instead of 46.

Also: the user's edits to on-start-during-play.md and the new note
reseed-because-of-list.md.

Verified: check.sh 794 tests, full e2e 631 checks, smokes 24/19/20.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 13:35:02 +02:00
rootandClaude Opus 5 8f92a06c84 Consolidate duplicated editor code, trim comments, refresh README
Split GraphCanvas (1076 -> 831 lines) into EdgeLayer and graphMenus;
the port and marker menus were the same list and are now one function.
Value specs of the inspector and the list tab share valueSpecs.ts, and
the remaining copies (compound commands, type glyphs, typed asset
lookup, name rule, play/editor world setup, marquee) are merged.

Four copies had already drifted apart; merging them fixes:
- "Disconnect N links" in the port menu is one undo step, not N
- "+ Add collider" is hidden on a placed container instance, as in
  the hierarchy menu, instead of always failing
- a bool property coerced from the text "false" becomes false
- a port command on an unknown node type reports instead of crashing

Comment-only edits leave the bundle byte-identical. README corrected in
35 places (step limit, jump-run clock, suite duration, 409 body, ...).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 11:00:30 +02:00
rootandClaude Opus 5 631cf54c72 Give every behavior variable one row shape on asset and instance
buildStructBlock only covered structs, so a placed instance drew numbers,
text, bools and lists through createFieldRow on a 64 px name column while
its struct block sat on the asset's grid below it. variableRows.ts now
builds the row for every variable kind on both sides; the edit option
decides what the owner may change (asset: rename, x, item kind; instance:
revert and push). Label and Button take a fixed width so the mark column
no longer shifts names when a list shows its item-kind picker.

The two structFields calls that became dead with 9f48257 are gone. The
e2e steps locate the Enabled toggle through __gm.inspectorField instead
of clicking a fixed offset from its label.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 07:27:29 +02:00