Skip to content

fix(web): integrate compiled game WASM into the web build (#72)#73

Merged
proggeramlug merged 1 commit into
mainfrom
fix/web-build-game-integration
Jun 27, 2026
Merged

fix(web): integrate compiled game WASM into the web build (#72)#73
proggeramlug merged 1 commit into
mainfrom
fix/web-build-game-integration

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Fixes #72.

Problem

./native/web/build.sh game.ts never produced a working game:

  • build.sh compiled the game to a throwaway /tmp/bloom_game.html and copied an unrelated template, so the game WASM was never loadedbloom_glue.js only console.warn'd.
  • The FFI bridge was double-converting: it manually NaN-box-converted every argument, but Perry's runtime already wraps the ffi namespace (wrapFfiForI64), delivering plain JS values and re-encoding returns. The manual conversion would have crashed.
  • Latent build.sh bug: it cd'd into the web crate, then tested the relative game path from the wrong cwd, silently skipping compilation.

Approach

Reimplementing Perry's ~280-function rt runtime in JS (the issue's suggested approach #1) is infeasible. Instead, reuse Perry's self-contained HTML as the carrier of its own correct runtime + NaN-boxing, and splice the Bloom engine into it.

Changes

  • bloom_glue.js — rewritten as the engine bootstrap + FFI bridge following Perry's plain-value contract. Maps bloom_* straight through, routes string/asset params to the _str/_bytes variants, wires input/audio/HiDPI canvas, and drives the rAF game loop via Perry's global callWasmClosure (runGame short-circuits to bloom_run_game on web, platform === 7).
  • splice_game.py (new) — injects the Bloom canvas + a synchronously-created window.__bloomReady promise + the deferred bootstrap module into Perry's HTML, and gates the game's bootPerryWasm() call on that promise so the game boots only after the engine + FFI are live. Anchored to the boot block so it ignores decoy .catch( in the runtime.
  • index.html — now a thin engine-only page (no-game case).
  • build.sh — resolves the game path up-front (bug fix), compiles to a temp dir, splices into dist/web/index.html, cleans up.
  • CLAUDE.md — updated file roles + the FFI value contract.

Validation

Verified end-to-end against the real Perry compiler (v0.5.1206) and a full wasm-pack engine build of examples/pong: the pipeline produces dist/web/index.html containing Perry's runtime, the embedded game WASM, the gated boot, and the Bloom canvas; every pkg export the bridge references exists; the splicer correctly ignores decoy patterns; temp files are cleaned up; all new JS/Python passes syntax checks.

Browser-runtime behavior (WebGPU surface creation, live closure dispatch) was not exercised headless — cd dist/web && python3 -m http.server 8080 would confirm the last mile. Note the build needs PERRY_ALLOW_PERRY_FEATURES=1 (or bloom/* in the host perry.allow.nativeLibrary) since Bloom links native libraries.

The web build flow (`./native/web/build.sh game.ts`) never produced a
working game: `build.sh` compiled the game to a throwaway
`/tmp/bloom_game.html` and copied an unrelated template, so the game WASM
was never loaded — `bloom_glue.js` only warned. The FFI bridge was also
wrong: it manually NaN-box-converted every argument, but Perry's runtime
already wraps the `ffi` namespace (`wrapFfiForI64`), delivering plain JS
values and re-encoding returns, so the manual conversion double-converted.

Reuse Perry's self-contained HTML as the carrier of its own correct `rt`
runtime + NaN-boxing, and splice the Bloom engine into it:

- bloom_glue.js: rewritten as the engine bootstrap + FFI bridge following
  Perry's plain-value contract. Maps bloom_* straight through, routes
  string/asset params to the _str/_bytes variants, wires input/audio/HiDPI
  canvas, and drives the rAF game loop via Perry's global callWasmClosure
  (runGame short-circuits to bloom_run_game on web, platform === 7).
- splice_game.py (new): injects the canvas + a synchronously-created
  window.__bloomReady promise + the deferred bootstrap module into Perry's
  HTML, and gates the game's bootPerryWasm() on that promise so the game
  boots only after the engine + FFI are live. Anchored to the boot block so
  it ignores decoy `.catch(` in the runtime.
- index.html: now a thin engine-only page (no-game case).
- build.sh: resolve the game path up-front (was tested from the wrong cwd
  after cd'ing into the web crate, silently skipping compilation), compile
  to a temp dir, splice into dist/web/index.html, clean up.
- CLAUDE.md: updated file roles + the FFI value contract.

Validated end-to-end against Perry v0.5.1206 + a full wasm-pack engine
build of examples/pong: produces dist/web/index.html with Perry's runtime,
the embedded game WASM, the gated boot, and the Bloom canvas.
@proggeramlug proggeramlug merged commit 71fedc9 into main Jun 27, 2026
9 checks passed
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.

Web build: bloom_glue.js bootBloomGame() doesn't load game.wasm — FFI bridge incomplete

1 participant