Present via a native GPUCanvasContext under Deno, not TypeScript+Playwright, for maximum type safety
Context
Decision 0013 established a standing rule (user-set, explicitly the highest-priority rule in this project, above implementation convenience): use every tool's available type safety to the maximum extent possible. The user pointed out this had been misapplied to tools/browser_check.ts -- TypeScript-under-Deno driving Playwright is real type safety for hand-written logic, but Playwright itself is still a third-party npm dependency with escape hatches (TypeScript's own soundness gaps: `any`, assertions, structural typing), not this project's own, stricter Gleam type system, and not native to the Deno/wgpu stack this project already trusts (decision 0008). Asked for thorough research into alternatives -- naming Deno-native WebGPU, Puppeteer-core/CDP, and 'pure Deno / headless canvas' as starting points, not an exhaustive list -- before concluding TypeScript+Playwright was actually necessary.
Options considered
Decision
glemy/render gained render_entities_to_canvas(entities, bounds, canvas) -- presents directly onto a real WebGPU canvas context (a real <canvas> in a browser, or a genuine OffscreenCanvas in tests; both share the same context surface, exposed to Gleam as an opaque Canvas type) instead of glemy/game's previous offscreen-texture-plus-Canvas2D-putImageData roundtrip. glemy/game.gleam split into tick_and_render (the real per-frame logic: spawn, physics, render/present -- now fully covered by `gleam test --target javascript` using a real OffscreenCanvas, no browser needed) and a thin requestAnimationFrame scheduling wrapper (loop/start) around it, which remains the one piece with no automated coverage, since Deno genuinely has no RAF -- confirmed, not assumed. tools/browser_check.ts's scope shrank to match: it no longer verifies rendering correctness (that's gleam test's job now) or reads canvas pixels at all (headless Chrome cannot present a WebGPU canvas to its compositor on Linux -- a real, documented upstream limitation discovered while adapting the script, gpuweb/gpuweb#1781, no known flag fixes it), instead directly counting real GPUCanvasContext.configure/GPUDevice.createBuffer calls to confirm the RAF loop runs and click-to-spawn grows the real entity count.
Verification
Prototyped the OffscreenCanvas+webgpu approach standalone before committing to the refactor: real adapter/device, real render pass, real copyTextureToBuffer readback, correct pixel value read back on the first attempt. render_test.gleam gained 4 new tests (including one proving render_entities_to_bytes and render_entities_to_canvas produce byte-identical output for the same scene, and one specifically exercising the WebGPU row-alignment-padding logic a canvas's arbitrary width needs that the old fixed-64-wide offscreen texture never had to). game_test.gleam gained 2 new tests covering tick_and_render end-to-end (physics + real rendering) with zero Playwright involved. Full suite: 84 Erlang / 114 JavaScript tests, all passing. tools/browser_check.ts, rewritten to count real WebGPU API activity instead of reading pixels, passes against the new architecture: requestAnimationFrame fires repeatedly, the real entity count starts at 3 (game.main()'s scene) and grows to 33+ after a simulated click-and-hold.
Consequences
Playwright remains a dependency (npm:playwright-core via Deno's npm interop, still no package.json) but now only for the genuinely browser-irreducible sliver -- RAF firing, real DOM wiring, real click coordinates -- not for anything gleam test can already prove. This also resolves decision 0010's deferred performance concern (§6 of development-plan.md: GPU-resident presentation without a CPU roundtrip) as a side effect of chasing type safety, not a separate optimization pass. Future browser-only WebGPU work in this project should default to checking whether Deno's OffscreenCanvas can cover it before reaching for real-browser automation at all -- that question wasn't asked before this decision and should have been.
References
- src/glemy/render.gleam
- src/glemy/render_ffi.mjs
- src/glemy/game.gleam
- src/glemy/game_ffi.mjs
- test/glemy/render_test.gleam
- test/glemy/game_test.gleam
- tools/browser_check.ts
- https://docs.deno.com/runtime/reference/web_platform_apis/
- https://github.com/gpuweb/gpuweb/issues/1781
- docs/decisions.jsonl, decisions 0008, 0010, 0013