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>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-10 14:01:56 +02:00
1 parent 9d62dfea7b
commit f60358c951
32 files changed
+1083 -253

No files matched your search

+4 -2
View File
@@ -1,6 +1,7 @@
import { RemoveNodeCommand, SetNodeParamCommand } from '../../core/commands/graphCommands';
import {
callProblem,
declaresVariablePorts,
type Graph,
edgesInto,
type GraphNode,
@@ -16,7 +17,7 @@ import type { PropertyDef } from '../../core/properties/PropertySchema';
import { Button, Label } from '../../ui';
import { createFieldRow, type FieldHandlers } from './fieldFactory';
import { addGap, addNote, addTitle, type InspectorHost } from './host';
import { buildPortSection } from './portSection';
import { buildPortSection, buildVariablePortSection } from './portSection';
import { listOpener, previewingHandlers } from './previewHandlers';
/** Structure-defining facts of a node selection: ports, connected inputs, declared variables. */
@@ -51,7 +52,8 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
addNote(host, def.description);
const problem = callProblem(node, behaviorLookup(host.state.project), assetId);
if (problem) addNote(host, problem, 'warning');
if (def.declaresPorts) buildPortSection(host, assetId, node);
if (declaresVariablePorts(node.type)) buildVariablePortSection(host, assetId, node, asset.variables);
else if (def.declaresPorts) buildPortSection(host, assetId, node);
if (def.params.length > 0) addTitle(host, 'SETTINGS');
for (const param of def.params) {