Collapse Tetris' variable clusters, and give the long runs a home

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>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-10 16:00:28 +02:00
1 parent f60358c951
commit fe3c63c48c
15 files changed
+439 -183

No files matched your search

+27 -2
View File
@@ -575,7 +575,7 @@ and reasons, not what the code does: a `/** */` on every export whose name does
## Development
Node 22 is required (on this machine: `PATH=/opt/node22/bin:$PATH`).
Node 22 is required (`/usr/bin/node`, in the default PATH).
```bash
npm install
@@ -585,10 +585,14 @@ npm run typecheck
npm run lint # ESLint, type-aware; --fix for the fixable half
npm run format # Prettier over src, tests and tools
npm run check # typecheck + lint + format:check + tests, what CI would run
./check.sh # the same, ending in `=== CHECK EXIT n ===`: piped into `tail` npm's exit
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
export GM_REMOTE=user@host # optional: run the browser elsewhere; see below
node tools/seed-e2e.mjs && node tools/e2e.mjs # 456 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 465 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 – a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
node tools/seed-jumprun.mjs && node tools/smoke-jumprun.mjs # 24 checks: the example game, played end to end
node tools/seed-tetris.mjs && node tools/smoke-tetris.mjs # 19 checks: the Tetris example, played end to end
```
@@ -606,6 +610,17 @@ four cores of this box (60% renderer, 18% GPU process, load ~1.0), which it carr
step is being worked on, `GM_STEPS` keeps a run to seconds – take the whole suite before a commit,
or when a change touches drawing, the widget toolkit or anything else every step sees.
**`./e2e.sh start` runs the suite detached**, in a transient `game-maker-e2e.service`, and returns at
once; output goes to `logs/e2e-<date>_<time>.log` (gitignored) and ends in `=== E2E EXIT n ===`.
Backgrounding it from a shell instead is not enough – four runs in a row died mid-suite with no
closing line, because the whole process group is killed when the calling shell exits (`nohup` only
covers SIGHUP, and `/proc/vmstat` confirmed `oom_kill 0`, so it was never memory). The unit outlives
its caller. It takes the same `GM_STEPS` argument and refuses to start a second run over a live one.
**`./e2e.sh status`** reports the live run (elapsed, PASS/FAIL so far, the check it is on) or, when
nothing is running, the newest log's result plus its FAIL lines. A log without a closing line is
reported as `ABORTED`, not as its PASS count: a run that was cut short is not a result.
**`GM_REMOTE=user@host` moves the browser to another machine** – `tools/lib/browser.mjs` starts it
over SSH and forwards its CDP port back to `127.0.0.1`, so the harness itself is unchanged. The
checks load the public base URL, so the remote side needs only a browser and internet: no repo, no
@@ -677,6 +692,16 @@ parallel variables copied back and forth, and each strand packs only the field i
(`mergeStruct` keeps the rest). Reading a cell is `((shape · 4 + rot) · 4 + corner)` into that
list instead of the same index doubled and read twice.
Where a step needs several variables at once, one `Get variables` / `Set variables` stands for the
lot: the call to `Next move` has **one node at each side** of it rather than five, and the master
graph is **44 nodes / 64 edges** where the same game in single nodes was 69 / 76. Inside `Next
move` each strand writes its whole answer – the candidate, what kind of move it was and the wish
it used up – in one node rather than three to five, which is **30 nodes** against 40. Two things
bound how far such a node may reach: a data node answers once per frame and keeps that answer, so
a `Get variables` must not straddle a write of one of its variables (which is why the board is
read a second time after the lines were cleared), and a `Set variables` reads all its inputs
before it writes any, so none of them may come from a variable it writes.
It is **ten behaviors**, not one graph. `Tetris` owns the state and does none of the work: each
step of a frame is a behavior of its own, called as a node – `Spawn piece`, `Next move`, `Fits`,
`Draw piece`, `Lock piece`, `Clear lines`, `Show lines`, `Draw board`, with `Piece cell` (where