Commit Graph
9 Commits
Author SHA1 Message Date
root 1bd6e89ff5 Kill only this run's browser on the remote host
Both shutdown paths ran Get-Process chrome,msedge | Stop-Process -Force,
which is every browser on the machine. The remote host is somebody's
desktop: a test run must not close the windows they are working in. It
only stayed harmless because the test account cannot touch another
user's processes.

The launcher now writes its browser pid to %TEMP%\gm-browser-<port>.pid
and kills that pid; the job object still takes the renderer children.

Measured against the Windows desktop: five msedge processes belonging to
the logged-in user before the run, five after, and our chrome gone.
24 smoke checks ALL PASSED.
2026-09-09 09:24:51 +02:00
root 47492bcbe8 Make the remote browser die with the run
The leash that ties the Windows browser to the run never held. Three
findings, each measured:

- stdin is not a signal: only the first chunk a caller writes reaches
  the remote command, so the stdin heartbeat looked like a dead caller
  and killed a run at check 44.
- a dropped SSH connection does not kill the remote command; Windows
  OpenSSH leaves it running. Six orphaned chrome.exe survived a
  SIGKILLed tunnel -- this is what locked up the desktop.
- Get-NetTCPConnection goes through WMI, which is denied to the
  non-admin test account; the broken CIM session took a browser down
  mid-run.

The launcher now watches the forwarded CDP port with .NET sockets: a
connected client means the run is alive, 45 s without one ends it. A job
object with KILL_ON_JOB_CLOSE and a 30-minute cap catch the rest, and
the caller shoots the browser on the way out instead of waiting for the
idle window.

Measured: survives 80 s of an idle held connection, gone 44 s after
kill -9 on node, immediately on an ordered end. Full suite 267 checks in
128 s and 24 smoke checks in 16 s against an RTX 4070, with this machine
at load 0.04.
2026-09-09 09:21:09 +02:00
rootandClaude Opus 5 32a40ee34b Run the check browser on the GPU instead of rasterising in software
The harness forced software WebGL (--disable-gpu --use-gl=swiftshader) on every host.
Measured here on an idle editor page over 20 s: that costs 373% of a core at 8 fps,
while the Intel HD 630 via ANGLE/Vulkan costs 69% at 56 fps. The full suite now runs
in ~124 s. Note the app is not rendering 60 fps as previously assumed - in software
it only reaches 8, and a single frame costs more than any throttle interval would save,
which is why capping the framerate had measured no effect.

--use-angle=gl and --use-gl=egl look cheaper still but get no WebGL context at all,
so the app never renders. They are not usable and are named in the comment as such.

There is deliberately no software fallback: a host without a usable GPU should fail
loudly rather than drag the machine to a standstill. None of the 267 checks reads the
rendered image anyway - they read window.__gm.locate() and the debug hook, and the
screenshots assert nothing - but Pixi needs WebGL2 to run at all.

The browser launch moves out of harness.mjs into tools/lib/browser.mjs, which also
supports GM_REMOTE=user@host via an SSH-forwarded CDP port. That path is unfinished:
the tunnel degraded until the suite crawled at ~25 s per check, and forcing software
WebGL on a desktop with a real card brought it to its knees. README says so plainly
rather than advertising it.

Also: the jump-run HUD check asserted a fixed "Coins 1/10", which fails on a faster
host where the same walk sweeps up two coins. It now asserts the HUD against the count
the run actually reached - the real guarantee is that HUD and state agree.

Verified: npm run check green (404 tests), 267 e2e ALL PASSED and 24 jump-run checks
ALL PASSED against the GPU flags. The fallback removal leaves the flag set on this host
identical (same flags, different order), so it was not re-run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 08:46:16 +02:00
rootandClaude Opus 5 3d038e11cb Verify that typed text actually arrived
typeText fired Input.insertText per character and never looked back, so a
partially typed value was indistinguishable from a wrong one: a run committed
x=3 for "300" and the failing check blamed the value rather than the three lost
characters.

Trying to reproduce that failed over 21 rounds across four variants (no pause,
400ms between characters, with the preceding scrub, and both commit paths), and
the code rules out the obvious suspects: typing lands in a hidden input, each
character is only a preview, objectSignature omits position so no rebuild is
triggered, and NumberField.setNumber guards write-back with isEditing. Cause
unknown, so this makes the symptom name itself instead of hardening against a
mechanism that may not exist.

Typing stays per character - that exercises the live-preview and search-filter
paths - but now asserts the text is in the focused input, reporting focus and
actual contents when it is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 06:56:03 +02:00
rootandClaude Opus 5 c256859854 Make a dying E2E run fail loudly instead of exiting 0
A run whose chromium died stopped mid-step and exited with code 0, silently
reporting success for checks it never ran: CDP.send() left its promise pending
when the socket closed, and an unsettled promise does not keep node alive.
Waiting on the server had the mirror problem – a bare fetch keeps its socket
open, so a stalled response hung the run forever.

Every wait can now time out, the socket rejects what it can no longer answer,
and reaching process exit without a summary reports RUN DIED with exit 1.
Chromium's stderr and exit signal are kept, so "killed by SIGKILL" is visible
rather than guessed at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:41:56 +02:00
rootandClaude Opus 5 f40b8a0523 Fix instance lookups that trusted stale data
Two regressions from the template-resolution rework, plus the harness gap
that hid them:

- `instanceTargetOf` decided from the shape of the id. Unpacking keeps the
  derived ids, so unpacked content still counted as template content. It now
  asks whether the object is spelled out in the holder.
- The parent picker captured its holder when the row was built. Since
  `viewOf()` returns a derived copy, the menu listed the objects of that
  moment (measured: 40 in the model, 17 in the menu). It resolves on open.
- `evaluate()` swallowed page exceptions, so a mutate that died half-way let
  every later check pass or fail for the wrong reason.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 21:59:22 +02:00
rootandClaude Opus 5 ead649eec3 Resolve container content from the template instead of copying it
A scene now stores only the container object; its content is resolved from
the template on every read, with deviations kept as sparse instanceEdits on
the container object. This drops the propagation bookkeeping entirely --
templateSync.ts is gone and the three structural gaps (add, remove, reparent)
cannot diverge any more.

Values stay editable on an instance, structure does not; Unpack writes the
content into the scene, keeping ids. Play mode bakes containers into the
throwaway copy before entities are built. No migration: formatVersion is 2
and the seeders write the new format.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 21:09:53 +02:00
rootandClaude Opus 5 d54a620ebd Add ESLint and Prettier, and close the node test gaps
Tooling:
- Prettier as the single formatting authority (110 columns, single quotes,
  measured against the existing style); Markdown excluded because table-cell
  padding turns the README glossary into 1000-character lines.
- Type-aware ESLint ending in eslint-config-prettier, so a rule always means
  "this is a bug", never "this looks different".
- .editorconfig and an `npm run check` that runs typecheck, lint, format and
  tests in one go.

Findings fixed on the first run: three more duplicate imports, four
String(unknown) calls (parameter types narrowed instead of casts), two dead
initialisers, two redundant casts, a console.log for a real error. Two
warnings were false here and are disabled with a reason in place.

Tests 132 -> 269:
- node-catalog.test.ts covers every node with a run(), with a guard that fails
  when a new node has no row.
- behavior-events.test.ts covers the ten event nodes through a running World,
  checking timing rather than existence.
- Input's state machine (press/release/releaseAll) is reachable without DOM
  events now; the DOM handlers are thin adapters.

Compare fell back to == without a configured operator although the schema
default is <; aligned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:02:22 +02:00
rootandClaude Fable 5.1 1eb28314cf Refactoring: one file per node, consistent comments, shared helpers, split large modules
- Graph: nodes.ts split into nodes/<category>/<Type>.ts (62 files) with
  shared.ts factories and index.ts registry; NodeContext/values/NodeRegistry
  separated, writeTyped() replaces duplicated typed writes
- Comments: one style (JSDoc on non-obvious exports, // for decisions and
  traps), wrong/stale comments fixed or removed, ids.ts now real 10 base-36 chars
- Core: chainOf, shared worldPosition/editorPosition core, commands/require.ts,
  commands/naming.ts, util/list.ts, setBagValue/toPosition/propDef; dead code
  removed (isBehaviorAsset, SCENE_OBJECT_SCHEMA, CommandStack.subscribe,
  Input.axis, setMode, titleText, World.worldPosition, unused ctor params)
- Editor: Inspector split into inspector/ sections, App.ts into Editor.ts +
  debugHook.ts + shortcuts.ts, Viewport into items.ts + symbols.ts, shared
  "Add collider" action, unified type labels/swatches
- UI toolkit: Widget.trackHover/onPointerRelease, drawControlBox, math helpers,
  Popup.openBelow, Menu/Dialog/ColorPicker/ListRow in own files, named Theme sizes
- Tools: seed-demo/smoke-demo and demo-check project removed, tools/lib
  (harness, projects, builders), e2e.mjs as runner over tools/e2e/*.mjs sections
- Tests: tests/helpers.ts replaces copied fixtures and NodeContext doubles

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 08:37:22 +02:00