Skip to content

#542 AC4: port four WebGL-era specs to WebGPU engine unit tests - #585

Merged
MaximumTrainer merged 1 commit into
next/svelte-webgpufrom
claude/project-thread-lddi4g-ac4-port
Oct 25, 2026
Merged

MaximumTrainer merged 1 commit into
next/svelte-webgpufrom
claude/project-thread-lddi4g-ac4-port

Conversation

@MaximumTrainer

Copy link
Copy Markdown
Owner

#542 AC4. The four React/WebGL-era specs (gl-context-budget, webgl-context-recovery, rower3d-postfx, scene-quality-match) targeted the retired three.js renderer — not a line of them can pass against the Svelte + WebGPU engine. This PR replaces them with engine unit tests that check the same four behaviours against the new engine, drops the specs and their shard-weight / testIgnore entries, and opens #584 for the integrated boot-and-row half of the port (which needs engine seams that don't exist yet, plus a GPU runner).

Dan's call ("a, b & c") was all three options; this is (b) + (c), with (a) folded into #584's AC5 because the e2e ports need engine seams first (device-lost reattach, timestamp-query, forced-loss helper) and a real GPU runner that's still dormant.

Summary

  • New unit tests at src/lib/renderer/engine/__tests__/ac4PortedBehaviors.test.ts:
    • (A) context budget — 1 adapter + 1 device per attach, 0 WebGL probes
    • (B) device-lost — publishDeviceLost + counter on first loss, renderer-unavailable on the second, contextState stays ready on first loss (today's observed behaviour; the recovery gap is tracked as it.todo and in AC4 follow-up: wire real device-lost recovery + GPU-runner e2e ports #584)
    • (C) post-fx — pass tree assembles at every tier; SSAO/DOF gates follow tierCaps
    • (D) scene quality — shadow + content gates climb monotonically with the tier; software adapters declined outright
  • Deleted the four spec files, their testIgnore entries in playwright/playwright.config.next.ts, and their rows in playwright/shard-weights.json.
  • Filed AC4 follow-up: wire real device-lost recovery + GPU-runner e2e ports #584 to track the one real engine bug the RC pass surfaced: device-lost is detected (counter + onDeviceLost(null) fire) but no re-attach, no banner, no fallbackReason, and the rAF loop keeps submitting to the dead device.

Measured first, written second

Every assertion was measured against the real engine on Dan's Intel Gen-9 laptop (hardware WebGPU, next/svelte-webgpu @ b32f746) on 2026-10-25 before being encoded. The one gap the measurement found is the device-lost recovery hole — not the first-loss detection, which works, but the follow-through (reattach, banner copy, state flip). That's in #584 with its own ACs; smuggling it into AC4 as a drive-by engine fix would make this PR a different change.

Test plan

  • npx vitest run src/lib/renderer/engine/__tests__/ac4PortedBehaviors.test.ts — 15 pass, 1 todo
  • npx vitest run src/lib/renderer/engine/__tests__/ — 116 tests + 1 todo, all pass
  • npx tsc --noEmit -p tsconfig.app.json
  • npx tsc -p tsconfig.playwright.json --noEmit
  • npm run lint
  • Pre-commit + pre-push hooks green
  • CI on next/svelte-webgpu branch (preflight / scaffold smoke / WebGPU engine smoke / e2e-shards 1 of 2 + 2 of 2 / GPU runner skipped)

Generated by Claude Code

The four React/WebGL specs (gl-context-budget, webgl-context-recovery,
rower3d-postfx, scene-quality-match) targeted the retired three.js
renderer. Replace them with engine unit tests under
src/lib/renderer/engine/__tests__/ac4PortedBehaviors.test.ts, split by
the behaviour each one used to check:

  (A) context budget — 1 adapter + 1 device per attach, 0 WebGL probes
  (B) device-lost — publishDeviceLost + counter on first loss,
      renderer-unavailable on the second, contextState stays ready on
      the first loss (today's observed behaviour)
  (C) post-fx — pass tree assembles at every tier, SSAO/DOF gates
      follow tierCaps
  (D) scene quality — shadow + content gates climb monotonically with
      the tier, software adapters are declined outright

Measured first against the real engine on Dan's Intel Gen-9 laptop
(hardware WebGPU, next/svelte-webgpu @ b32f746) so every assertion
encodes observed behaviour, not aspiration. The one gap the RC pass
found — first-loss recovery (banner, reattach, fallback publish) is
not wired up today — is tracked as it.todo in the new file and in #584
with its own ACs; writing it green would smuggle an engine fix into
this PR.

Also drops the four spec files, their testIgnore entries in
playwright.config.next.ts, and their shard-weights.json rows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5D9BTXmu7SeMa3LghJD8b
@MaximumTrainer
MaximumTrainer merged commit 43433ba into next/svelte-webgpu Oct 25, 2026
6 checks passed
@MaximumTrainer
MaximumTrainer deleted the claude/project-thread-lddi4g-ac4-port branch October 25, 2026 12:55
MaximumTrainer added a commit that referenced this pull request Oct 25, 2026
The WebGPU engine detected device.lost but did nothing with it: no
re-attach, no banner, no fallback publish, and the rAF loop kept
submitting work to a dead device. Measured on Intel Gen-9 against
next/svelte-webgpu @ 43433ba: 45 GPUCommandEncoder.finish + 45
queue.submit in 1.5s after a forced loss, no visible sign to the rower,
__ROWER3D_CONTEXT_STATE stuck at 'ready'.

Factors the per-device scene into src/lib/renderer/engine/rideScene.ts;
attach.ts stays the thin orchestration layer (telemetry, outer render
loop, device session lifecycle). On the first device.lost (D1-a):

  - publish fallbackReason='device-lost'
  - flip __ROWER3D_CONTEXT_STATE.kind to 'lost', backend stays 'webgpu'
  - publish the "restoring…" banner (D2-a copy ported verbatim from
    the retired WebGL spec)
  - dispose the current scene so no further work lands on the dead
    device
  - request a fresh device with isRetry: true
  - on retry success, build a new scene on the SAME canvas and
    continue; on retry failure, the retry's own onDeviceLost fires
    with 'renderer-unavailable', the engine publishes the
    "couldn't get it back" banner and stops drawing (D3-a).

FR2 (D4-a): installs __ROWER3D_FORCE_DEVICE_LOST on the current
device, gated on window.__PLAYWRIGHT_TESTING, so a spec can trigger
a loss without reaching into the Chrome rendering panel. Replaced on
every scene build, cleared on dispose.

FR3 (D6-c): device opts in to the timestamp-query feature when the
adapter has it (follow-up wires per-pass timestampWrites + readback
for __ROWER3D_FRAME_STATS.gpuMs). cpuMs lands this PR as the always-on
fallback — measured around frameOnce with performance.now, written
to telemetry every frame.

UI: /row polls window.__ROWER3D_BANNER at 250 ms and renders a banner
overlay at the top of the stage with variant-colored backgrounds for
restoring (amber) and failed (red). The row keeps counting through
either state (ride.currentSession is DOM-driven, not render-driven).

Flips the it.todo in ac4PortedBehaviors.test.ts (added in #585) to
a passing test; adds three more: reattach-success, retry-also-dies,
and force-loss seam presence. rideScene.ts is a mechanical extraction
from attach.ts, exercised transitively via the attach suite — TDD
guard off for the file move only (no new behaviour).


Claude-Session: https://claude.ai/code/session_01Q5D9BTXmu7SeMa3LghJD8b

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants