Next move held four clusters of Set variable in a row, fourteen single nodes
in all; clear-lines read three variables one after the other. They are now
one plural node each, which takes Next move 40 -> 30 nodes and clear-lines
25 -> 23. Every remaining single node was checked and really is single: ok/no
are the same variable in opposite branches, take/keep the same one across a
write, and the three key setters hang on three different events.
Two contracts carry those merges, both pinned as tests. Set variables writes
per port with writeTyped exactly as the singular node does, so a Pack{rot}
still merges instead of overwriting - without it, turning would have erased
the other three fields. And the interpreter collects every input before the
node runs, so no input may come from a variable the same node writes; a data
node is evaluated once per pulse and keeps its answer. That second rule is
invisible in the game, because OnUpdate re-reads each frame and heals it, so
it lives in a unit test rather than in the smoke.
/opt/node22 and /opt/npm are gone from the tree. build.sh ran into the empty
path once they were moved away; the other seven mentions were documentation.
e2e.sh and check.sh replace the bare npm calls. A long run does not survive
the end of its shell - four full e2e runs died mid-suite with oom_kill 0 -
so e2e.sh start hands the run to a transient game-maker-e2e.service, which
does. e2e.sh status reports the running job or the last result, and calls a
log without a closing line ABORTED rather than reporting the passes it got
to. Both scripts end on an explicit exit line, because set -o pipefail inside
a script does not protect the caller: measured, ./check.sh | tail still
reports 0. Logs carry a timestamp and land in logs/, which is ignored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.3 KiB
Tetris Game mit Structs neu bauen
...und mit neuen Get variables / Set variables node
Gebaut (2026-09-10). Beides, in zwei Durchgängen: erst die Structs, dann die Plural-Knoten.
| vorher | jetzt | |
|---|---|---|
shapes |
224 lose Zahlen | 112 {x, y}-Bündel |
| Zellindex | ((shape·4+rot)·8 + corner·2), zweimal gelesen |
((shape·4+rot)·4 + corner), einmal |
| deklarierte Behavior-Ports | 49 | 35 |
| Variablen im Master | 18 | 13 |
| Master-Graph | 84 Knoten / 94 Kanten | 44 / 64 |
next-move |
44 Knoten | 30 |
clear-lines |
29 Knoten | 23 |
piece und der Kandidat next sind Structs aus shape/rot/x/y. Next move gibt einen ganzen
Kandidaten zurück, Fits beurteilt ihn, der Master übernimmt ihn mit einem Set variable.
Jeder Bewegungsstrang packt nur das Feld, das er ändert (Pack{rot}) — „dasselbe Teil, eine Reihe
tiefer" ist dadurch wörtlich ein Feld.
Zwei Verträge, an denen die Zusammenlegung hängt
Beide gemessen, beide als Unit-Test gepinnt (tests/core/variable-ports.test.ts), beide mit
Gegenprobe abgesichert:
Set variablesschreibt je Port mitwriteTyped, genau wie der Einzelknoten. EinPack{rot}wird deshalb weiterhin permergeStructeingemischt statt das Bündel zu überschreiben. Ohne das hätte Drehen die anderen drei Felder gelöscht.- Ein Datenknoten wird einmal pro Puls ausgewertet und behält die Antwort (
dataCache, geleert je Schleifenrunde). EinGet variablesdarf deshalb keine Schreibung einer seiner Variablen überspannen — sonst liest der spätere Verbraucher den Wert von davor. Deshalb sindgrid/outgridinclear-linesweiterhin zwei Knoten, undtake/keepbleiben getrennt.
Die Cache-Regel ist im Spiel nicht messbar: eine absichtliche Verletzung ließ den Smoke grün,
weil OnUpdate jeden Frame neu liest und der Fehler sich einen Frame später selbst heilt. Der
Unit-Test fällt dagegen sauber (py: 7 statt 1).
Was bewusst nicht passiert ist
Das Brett bleibt eine flache Zahlenliste. -1/Formnummer ist bereits die kompakte Kodierung;
ein {shape}-Struct pro Zelle wären 200 Bündel für ein einziges Feld.
Eine Listen-Variable kann ihre Item-Art im Editor nicht wählen — eine Liste aus Structs
entsteht heute nur aus einer Projektdatei. Steht in struct-und-listen-restposten.md.