← All decisions

Headless WebGPU verification does work in this sandbox -- decision 0011's conclusion was wrong

webgpuplaywrightenvironmentverification

Context

Decision 0011 concluded real-browser WebGPU verification was unavailable in this sandbox after navigator.gpu.requestAdapter() returned null across several flag combinations, and pointed at the absence of /dev/dri as the likely cause. The user pushed back: they manually confirmed the game runner renders correctly in a real browser, and pointed out this is WSL2 -- which does GPU passthrough via a different mechanism than standard Linux (/dev/dri), and asked for the actual root cause rather than accepting the surface-level conclusion. That pushback was correct on both counts.

Options considered

Accept decision 0011's conclusion (no GPU device node = impossible here) — rejected
Wrong. /dev/dxg -- WSL2's actual GPU passthrough device, entirely different from the standard Linux /dev/dri render-node convention 0011 checked for -- is present and openable in this sandbox. The earlier investigation searched for the wrong device path and stopped there instead of checking what WSL2 actually uses.
Chromium flags: --use-vulkan=swiftshader, --enable-unsafe-swiftshader, --disable-gpu-sandbox (0011's attempts) — rejected
--use-vulkan is not the flag that selects ANGLE's rendering backend (--use-angle is); these were simply the wrong flags, guessed rather than sourced from Chrome's own documentation. Also tested against about:blank rather than a real navigated page, which independently suppresses navigator.gpu.
Chrome for Developers' documented headless-WebGPU flag set: --headless=new --no-sandbox --use-angle=vulkan --enable-features=Vulkan --disable-vulkan-surface --enable-unsafe-webgpu, against a real navigated page — chosen
Works. requestAdapter() returns a real adapter with a substantial feature list (texture-compression-bc/astc/etc2, subgroups, timestamp-query). --disable-vulkan-surface in particular matters: without it Vulkan init tries to create a windowing-system surface that doesn't exist headlessly and fails silently. Verified end-to-end, not just adapter acquisition: navigated to the project's actual index.html, read back real canvas pixel data after the RAF loop ran (137 red/entity pixels after 2s), simulated a real click-and-hold via Playwright's mouse API, and confirmed rendered coverage increased (1001 red pixels) -- proving click-to-spawn works, not just that an adapter exists.

Decision

Real-browser WebGPU verification is available in this sandbox after all, via headless Chromium (Playwright's existing local install) launched with the exact flag set above, navigated to a real page. Committed this as a reusable tool, tools/browser_check.mjs (a dependency-free Deno script -- no package.json, using Deno's npm: specifier support for playwright-core, consistent with decision 0008's Node-free stance), rather than a throwaway scratch probe. It spins up its own dev-server subprocess, renders the real index.html/game.gleam bootstrap, and asserts both that entities render and that click-to-spawn increases rendered coverage. It is explicitly a manual/on-demand tool, not wired into `gleam test` -- decision 0008's reasoning for keeping Playwright out of the actual CI-facing suite still stands.

Verification

Ran tools/browser_check.mjs standalone (no manually-started dev server, no pre-existing state) end to end: PASS, 137 red pixels after 2s rising to 1001 after a simulated click-and-hold, zero uncaught page errors. Re-ran to confirm reproducibility.

Consequences

The verification gap flagged in decision 0011/development-plan.md section 4 is closed -- an agent in this environment can now visually-equivalent-verify glemy/game changes without asking the user to check manually every time, closing the loop this decision log's own working method calls for (research first, verify important decisions independently). Future browser-only WebGPU work (e.g. the deferred WebGPU-canvas-context presentation path from decision 0010) should be checked with this tool before being called done. The lesson to carry forward: when a headless/sandboxed environment claim doesn't match the user's own hands-on experience, that's a strong signal to keep digging for the actual mechanism (here: WSL2's GPU passthrough uses /dev/dxg, not /dev/dri) rather than trusting the first plausible-looking negative result.

References