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

@@ -0,0 +1,24 @@
import type { NodeDef } from '../../NodeRegistry';
import { GET_VARIABLES } from '../../nested';
import type { Value } from '../../values';
/**
* The plural of Get variable: one output per variable picked, so a graph reading a handful of them
* is one node instead of a column of five. The ports are declared on the node, the way Expand
* declares the fields of a struct.
*/
export const GetVariables: NodeDef = {
type: GET_VARIABLES,
label: 'Get variables',
category: 'variable',
description: "Reads several of this behavior's variables at once, one output per variable.",
inputs: [],
outputs: [],
params: [],
declaresPorts: 'outputs',
run: (ctx, _inputs, _params, def) => {
const outputs: Record<string, Value> = {};
for (const port of def.outputs) outputs[port.key] = ctx.variables[port.key] ?? null;
return { outputs };
},
};
@@ -0,0 +1,25 @@
import type { NodeDef } from '../../NodeRegistry';
import { SET_VARIABLES } from '../../nested';
import { writeTyped } from '../../values';
import { exec } from '../shared';
/**
* The plural of Set variable: one input per variable picked. An input left unconnected writes the
* value typed on the node, exactly as it does on the singular node – the node writes every variable
* it lists, every time it runs.
*/
export const SetVariables: NodeDef = {
type: SET_VARIABLES,
label: 'Set variables',
category: 'variable',
description: "Writes several of this behavior's variables at once, one input per variable.",
inputs: [exec()],
outputs: [exec()],
params: [],
declaresPorts: 'inputs',
run: (ctx, inputs, _params, def) => {
for (const port of def.inputs)
if (port.type !== 'exec') writeTyped(ctx.variables, port.key, inputs[port.key]);
return { next: 'exec' };
},
};