a6c00f7b8b1b0296ab0bc44699e3e723f4822a23
22
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c74b6b1cab |
Show where an inspector row can be dragged and answer the pointer
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> |
||
|
|
2c19c89878 |
Let variables, fields, ports and list items be dragged into order (#310)
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> |
||
|
|
0f6e628616 |
Check the declared type when a graph writes a typed variable (#271)
A list variable with an item type only kept its shape in the editor. A graph could write any list into it at runtime, and the List node had no type at all. Now the declaration decides what a write may store. - Writing a list into a typed list variable leaves out every item that does not have exactly the fields of the type. The rest is written and the console names each item that was left out, like Add item does. - The same rule holds for typed list fields inside a struct variable and for lists inside typed items, at any depth. - A typed struct variable refuses a struct with a field the type does not have, and a value that is no struct. A struct with missing fields still merges in as before, so a partial Pack keeps working. - The check runs in the Variables node (own variables and Variables assets) and in the Properties node when it sets a behavior variable of another object. - Ports learn their declaration through GraphLookup: what a variable declares about its type, and which behavior owns a graph. The rule itself lives in the new src/core/graph/typedValues.ts, fitsType moved there from SetItem. - The List node gets a Type setting. With a type its items are shaped and marked, follow the struct asset, count when the type is deleted and offer Split into fields in For each. The mark never reaches the running game. - A type on a List node holding something other than structs is refused with a console line. - Setting the List node back to None removes the marks, unlike a list variable. A node list has no per-item type row, so marked items could never be freed there. Undo brings everything back. - tests/core/kinds.test.ts looked for the value as the first parameter of a literal. Type now comes first on the List node, so the test finds the value by its key. Tests: - Gate: 198 files, 2869 tests (2856 + 13), CHECK EXIT 0 - e2e 55: 19 checks (+55j write at runtime, +2 for 55k List node) - e2e 54: 16 checks, smokes 24 / 19 / 21 - Five mutations, each fails between 1 and 4 tests - e2e total 1606 is computed (1603 + 3), no full run Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ded71a92c9 |
Pick the target of an object variable in the Scene View (#276)
Choosing the target of an object variable meant finding its name in a dropdown. With many objects, or several with the same sprite, the name alone does not say which one it is. The eyedropper lets you click the object where you see it instead. - Every object dropdown gets an eyedropper button next to it. This covers variables, struct fields and list entries, in the Inspector and in the Variables tab. - The button opens the Scene View or the container tab the object lives in. A strip above the view names the field and the current candidate and offers Take and Cancel. The cursor is a crosshair. - A click picks a candidate and writes nothing yet. The candidate gets its own cyan frame, the selection stays on the object with the variable. - Clicking follows the selection rule. `pickOn` in `src/viewport/pick.ts` uses `pressOn` and only counts objects the dropdown offers. A click on empty space keeps the candidate. - Enter or Take writes through the same handlers as the dropdown. That is one undo step, or none if nothing changes. - Esc, Cancel, a right click in the view or a second click on the button cancel without writing. So do a tab switch, another selection, undo, redo, play and loading a project. - After Take or Cancel the editor goes back to the tab the pick came from. - While the eyedropper runs, the view does not drag, marquee, paint, show handles or open its context menu. Panning and zooming stay. Found on the way: - Started from the Variables tab, the click missed the object. The Scene View is hidden while that tab is open, and the layout skips hidden children. The strip appeared then, the view moved down by 32 px but kept its old height. `Editor.display()` now lays the row out again after showing it. Tests (on melektaus-atx): - unit pick and object-pick: 27 passed, 13 new - gate: 196 files, 2844 tests, CHECK EXIT 0. Two more come from svg-icons, which checks every icon twice. - e2e 57: ALL PASSED, 12 checks, 5 new (57e to 57g) - Counter-checks work. Without the layout fix 57g fails. Picking objects the dropdown does not offer fails 2 tests. Writing on cancel fails 2 more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bc688f3efc |
Refactoring round 7: split BehaviorSystem, fewer copies (#85)
Recurring cleanup round over the code since round 6 (
|
||
|
|
b0d0868f55 |
Give a list one struct type so every item keeps its shape (#254)
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> |
||
|
|
4857bb75e6 |
Show lists in the inspector and variables in their own tab (#252)
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> |
||
|
|
92fad6b9e3 |
Show the list a running game fills in the list tab (#251)
While playing, the inspector said "2 items" for a list a graph had filled, but clicking it opened an empty list tab. The inspector reads a Variables asset from the running game, the list tab only read the list stored in the project. It did not follow the run's ticks either. - resolveList takes the live values. For a Variables asset it uses the running game's value while one runs, the project's otherwise. This is the inspector's rule. - ListEditor refreshes on runtime ticks about 10 times a second, and not while a field in the tab is being typed in, like the inspector. - Editor hands the list editor the inspector's liveVariables. - __gm.list.info() also returns rows, the rows the last build drew. - Editing in the tab while playing goes into the run's copy of the project, as in the inspector. A very long, growing list rebuilds all rows up to 10 times a second. Tests: - ./check.sh: 180 files, 2634 tests, CHECK EXIT 0 - e2e GM_STEPS=1,32: ALL PASSED, 30 checks - e2e 32b-ii, 2 checks: a graph adds an item every frame, the test pauses and clicks "N items" with a real mouse. The tab draws 26 rows for 26 items, grows to 50 after resuming and is empty after Stop. - Counter-checks: without live values the tab draws 0 rows for 23 items, and without the refresh it stays at 23 - README: 1481 editor checks, counted by hand Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0c5ac32b59 |
Let structs and lists nest to any depth (#239)
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> |
||
|
|
085e6c8cce |
Keep edited list values when an item is deleted (#235)
The list tab read the list once when it built its rows. Editing a value does not rebuild the rows, so the delete button later filtered that old copy and wrote it back. Every edit since the last rebuild was lost. Adding an item only triggered it, because adding rebuilds the rows and that state became the old copy. - ListAccess.value in src/editor/list/slot.ts is now read on every access instead of once when the access is built. - The kind switch of a row and the struct field buttons (field type, rename, remove field, + Field) read the item when clicked, not when the row was built. They had the same stale copy. - New test hook __gm.list.removeAt(i) clicks the delete button of one row, because the tab shows many buttons with the same label. Not covered by a test of their own: the struct field buttons and the kind switch after an edit. The cause is the same, the fix is in code. Tests: - ./check.sh: 2435 tests, CHECK EXIT 0. - Unit test in tests/editor/list-slot.test.ts keeps one access, edits a value, adds and edits an item, deletes another. All edits stay. It fails without the fix. - e2e 21b-ii with real clicks: set row 2 to 66, delete row 0, expect ["11","66"], two undos take both back. e2e 21: 52 checks passed. - Counter probe: with the old snapshot the check fails with ["11","44"], the reported bug. - Check total in README is now 1400 (+2), counted, not measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
0e8585665a |
Load the 500 weight and draw missing glyphs as SVG icons (#214)
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
|
||
|
|
42cafa7543 |
Shader assets: graph-built shaders, the Shader tab and sprite rows from the graph (#62)
game-maker / suite (push) Failing after 3m59s
A shader is an asset carrying a node graph (Texture color, Output, Property, Color, Add/Subtract/Multiply/Divide, Mix, Luminance) that compiles to the GLSL fragment of a Pixi filter. The Shader tab reuses the Behavior canvas; a sprite wears a shader through its Shader picker, and the rows below it are the graph's Property nodes. Property schema, resolution and deviation take the project because a sprite's rows now live in another asset. The built-in registry of the prototype is gone; "Adjust colors" ships as a graph in the fixture and the three example games. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
e7d82bd1a9 |
State machines: asset, tab, State branch and Set state nodes (#51)
A state machine is an asset whose graph holds one `State` node per state (no ports, name as title), edited in a new "State machine" tab that reuses the graph canvas: right-click places "State n", snap toggle shared with the Behavior tab via SnapToggle. `statemachine` is a value kind like texture (port, literal node, variable, struct field, tagged list item). Behaviors use it through `State branch` (one exec output per state) and `Set state` (one exec input per state; the interpreter now hands `run` the entry port). States live per behavior instance in NodeContext.states, with no initial state. Ports derive from the inline picker or a wired `State machine` literal; BehaviorLookup became GraphLookup with behavior(id) and machine(id). The Graph tab is labelled "Behavior" (key unchanged). Unit tests (state-machine, kinds, catalog, ports), e2e step 26 and README added. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
8a7185948d |
Make texture and colour full kinds and drop the load-time migrations
- `Texture` and `Color` can be picked as a variable kind, a struct field kind and a declared port kind; a new `Texture` node hands a picture id into the graph. Declared, a texture is its bare id – the `$texture` tag stays a list-only thing. - The `String` node is labelled "Text" like its kind; the type id stays. - Whole-number inputs (`Item at`, `Set item`, `Sub list`, `Fill`, `Repeat`, `Substring`, `To text`) show no decimals (`int()` in shared.ts). - All server projects are reseeded, so `legacyNodes.ts`, `migrateDeclaredLists` and the `ownerId` fallback go, with their tests. - Note `reseed-because-of-list` moved to done/; new note `refactor-types`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
10d030376a |
Finish the struct and list leftovers: inline lists, list fields, item kinds
An unconnected list input is a button into the List tab instead of a number field; a struct field can hold a list, so ListSlot carries an optional field name and the three owner cases only say what sits at a key; and a list variable picks its item kind in the inspector (marker doubles as the chooser), so a list of structs no longer has to be born from a project file. The declaration keeps the storable shapes finite: a struct field may be a list, but that list holds primitives only, so list->struct->primitive and struct->list->primitive are the two deepest forms. check 674, e2e 483, jump-run 24, Tetris 19. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f60358c951 |
Let a list hold structs, and read or write variables in bundles
A list item may now be a struct. The depth is bounded in the declaration
rather than by a comment: FieldKind (number|text|boolean|color) says what a
struct field holds, ListItemKind adds struct on top and says what a list
holds, so list -> struct -> primitive is the deepest storable shape and
sameValue stays finite. A struct item renders inline in the List tab, which
is why it needs no path in the slot address.
Four paths silently assumed a list item was a primitive: sameValue compared
items with ===, textOf would have printed [object Object], asListItem turned
a struct into text on Set item and Fill, and Index of used indexOf, which is
reference equality and could never have found a struct.
Tetris now runs on bundles. shapes is 112 {x, y} structs instead of 224 loose
numbers, piece and the candidate are structs, so Next move hands back a whole
candidate and each strand packs only the field it changes, since mergeStruct
leaves the rest standing. Declared behavior ports drop 49 -> 35, the master's
variables 18 -> 13, its graph 84/94 -> 69/76 nodes/edges.
Get variables and Set variables declare their ports from a list of variable
names, the way Pack and Expand already do, so one node can read or write
several at once. Renaming a variable rewires them, because there the port key
is the variable name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
0449218cb6 |
Let a variable declare struct fields
The value type was already there: StructValue, textOf, sameValue, writeTyped/mergeStruct and the struct port type came with the first part, and Get/Set variable carry any-ports, so a bundle already flowed through the graph unchanged. What was missing was the declaration. PropertyDef gains kind 'struct' with a list of fields, VARIABLE_KINDS a fifth kind, variableDef a fifth parameter. A declared field is a primitive - a list field would need a nested editor and therefore the path in the slot address, the same step "a list of structs" is waiting for. coerceValue now returns PropertyValue rather than FieldValue, the first value type that is not a field; none of its five callers break. In the inspector a struct variable is a block, not a tab. The list tab exists because hundreds of rows fit in no inspector row; a struct is the opposite case - few fields that fit under each other. It is the port editor Pack already has, one level down and with a value column: type, key and default value in one place. A placed object sees the same fields as plain value rows: overriding yes, declaring no. Adding or removing a field touches the declaration, the default, the behavior's value and every instance override in one command, so a stored value can never keep a shape its declaration no longer promises. The row handlers read the bundle at the moment of the change rather than closing over a snapshot - a field writes the whole bundle, so with a snapshot the second edited row revives the first one's old value. defSignature carries the fields, otherwise a new row appears only at the next rebuild. Fixes a fixture fault this uncovered: step 10 pushed its text object into the scene without instanceEdits, which overrideSites walks with Object.entries. AddStructFieldCommand is the first caller to reach it from that state; the step was green alone and failed three checks in the full suite. Moves the dynamic-ports note to done - all three of its parts are built. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
4d07946c74 |
Lay out a scroll container's content when the content changes
A short list opened after a long one drew all its rows stacked on one point. The fault was not in the list tab: rebuild() handed the row stack to setContent() while it was still empty, so the content was sized to max(rows, viewport) = the viewport. The rows arrived after that, and for a short list the same formula yields the same height, so resize() saw no change and skipped layout(). A long list changes the height and never showed it. relayoutContent() now runs whenever the content changed, rather than leaning on resize()'s change detection - that detection is the wrong instrument for "the children moved". The list tab was the only affected caller: the panels use ListView, which positions its rows itself, and the inspector fills its body before setContent. Also drops the comma line under the list. Every section that can carry a list supplies openList, so the comma fallback in fieldFactory was unreachable; listToText/listFromText go with it. The list-value test now covers coerceListItem through coerceValue instead, so the boolean spelling stays guarded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e52e95c0e9 |
Give a list its own tab instead of a comma line
A list property in the inspector was a single text field holding a comma
separated line -- unreadable past a handful of items, and it could not
carry anything but text. It becomes a button ("3 items") that opens a
sixth centre tab, where every item is a row built by the very widget the
inspector would build for that item kind.
The renderer was free: a PropertyDef of kind 'list' already carries
`items`, so item i becomes a synthetic def of that kind -- number, text,
boolean and colour rows appear without a new branch.
The way into the tab goes through the state (EditorState.listSlot,
openList(), event 'list'), like every other tab switch here; a tab may
not call showTab itself. ListSlot lives next to Selection rather than in
the tab folder, or state.ts would have to import upwards.
A row's handlers must not close over a snapshot of the list: they outlive
a commit, since the tab only rebuilds when the length changes, and the
second edited row would otherwise revive the value the first replaced.
showTab switched five `visible` lines by hand, where a sixth tab is
silently forgotten; it is a Record<CentreTab, Widget> now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|