Files
feuerwerk/tools/seed-e2e.mjs
T
rootandClaude Opus 5 fe3c63c48c 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>
2026-09-10 16:00:28 +02:00

102 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Writes the fixture scene tools/e2e.mjs asserts against: two top-level crates with a collider
* child each, a camera, the Mover behavior owning the red crate and a Spawner. The objects are
* uniquely named and positioned exactly where the checks expect them.
*
* Usage: node tools/seed-e2e.mjs [projectId] [baseUrl]
*/
import { edge, node, sceneObject, variable } from './lib/builders.mjs';
import { PROJECTS, cliArgs, writeProject } from './lib/projects.mjs';
const { projectId, baseUrl } = cliArgs('seed-e2e.mjs', PROJECTS.fixture);
/**
* "Mover" in five nodes: every frame, move the owner along the normalized arrow-key axis,
* scaled by the `speed` variable per second (On update's dt feeds the dt input). "Collide with
* solids" stops the owner's solid colliders at other solid colliders.
*/
const moverGraph = {
nodes: [
node('n-update', 'OnUpdate', 0, 0),
node('n-owner', 'Owner', 0, 120),
node('n-axis', 'AxisInput', 0, 200),
node('n-speed', 'GetVariable', 0, 400, { variable: 'speed' }),
node('n-move', 'Move', 340, 0, { collide: true }),
],
edges: [
edge('e1', 'n-update', 'exec', 'n-move', 'exec'),
edge('e6', 'n-update', 'dt', 'n-move', 'dt'),
edge('e2', 'n-owner', 'object', 'n-move', 'object'),
edge('e3', 'n-axis', 'x', 'n-move', 'dx'),
edge('e4', 'n-axis', 'y', 'n-move', 'dy'),
edge('e5', 'n-speed', 'value', 'n-move', 'scale'),
],
};
/** "Spawner": pressing Space places a new Crate at world (0, 150) and logs its name. */
const spawnerGraph = {
nodes: [
node('s-key', 'OnKeyDown', 0, 0, { key: ' ' }),
node('s-spawn', 'Spawn', 260, 0, { asset: 'crate', y: 150 }),
node('s-log', 'Log', 520, 0),
],
edges: [
edge('s-e1', 's-key', 'exec', 's-spawn', 'exec'),
edge('s-e2', 's-spawn', 'exec', 's-log', 'exec'),
edge('s-e3', 's-spawn', 'object', 's-log', 'message'),
],
};
const project = {
formatVersion: 2,
id: projectId,
name: 'E2E fixture',
activeSceneId: 'scene1',
assets: [
{
id: 'scene1',
type: 'scene',
name: 'Level 1',
props: { backgroundColor: 0x22252c },
objects: [
sceneObject('o1', 'crate', { x: -150 }),
sceneObject('o2', 'crate', {
name: 'Blue crate',
x: 120,
y: -80,
overrides: { width: 140, color: 0x4488ff },
}),
sceneObject('o3', 'cam'),
// The Mover is parented to the red crate (its owner); its position is local to it.
sceneObject('o4', 'mover', { y: 110, parentId: 'o1' }),
// Both crates carry the shared collider as a centred child; the blue one is widened to
// match its 140-wide sprite override.
sceneObject('oc1', 'collider', { parentId: 'o1' }),
sceneObject('oc2', 'collider', { parentId: 'o2', overrides: { width: 140 } }),
// Top-level spawner (no owner) left of the red crate: Space spawns a crate.
sceneObject('o5', 'spawner', { x: -300 }),
],
},
{ id: 'crate', type: 'sprite', name: 'Crate', props: { width: 100, height: 60, color: 0xe0553f } },
{ id: 'cam', type: 'camera', name: 'Main camera', props: { width: 640, height: 360, zoom: 1 } },
{
id: 'collider',
type: 'collider',
name: 'Crate collider',
props: { shape: 'box', width: 100, height: 60, radius: 50, isTrigger: false },
},
{
id: 'mover',
type: 'behavior',
name: 'Mover',
props: { speed: 200 },
variables: [variable('speed', 'number', 0)],
graph: moverGraph,
},
{ id: 'spawner', type: 'behavior', name: 'Spawner', props: {}, variables: [], graph: spawnerGraph },
],
};
await writeProject(projectId, baseUrl, project);