Parity on a Page
The ray tracer had rendered the same image on three backends; the natural next place for it was a browser tab. Getting it there took the frontend FFI's first pixel-output surface — two new externs that set a canvas fill style and fill a rectangle — and, satisfyingly, zero compiler changes: the extern machinery accepted a five-argument import as if it had always existed. Verification came before any browser opened: a headless harness stubs the DOM glue, captures every canvas call, rebuilds the image file from them, and compares it byte-for-byte against what the native backends wrote. And the page itself shows its work — the status line prints the render's checksum, the same number the interpreter and the C backend print, so the test suite's cross-backend discipline is sitting there, visible, under the picture.
The ray tracer existed as a file that printed a checksum and wrote an image. Three backends
agreed on every byte of it. The natural next place for a program like that is a browser
tab, where the picture can just be looked at — and the playground already had the
infrastructure: pages that load a Wasm module, a small glue file wiring extern fn
declarations to real DOM operations, a 2048 that reads arrow keys through it. What the
frontend FFI did not have was any way to put a pixel on the screen. Its whole vocabulary
was text — get an element, set its text content, listen for clicks and keys. Fine for a
counter and a tile game rendered as characters; useless for an image.
Two functions and no compiler
The missing surface turned out to be two functions. dom_canvas_fill_style sets the
canvas context’s fill color from a CSS string; dom_canvas_fill_rect fills a rectangle.
A renderer that has computed a pixel’s color calls the first with rgb(r,g,b) and the
second with a one-by-one rectangle. On the JavaScript side the glue grabs the canvas’s 2d
context lazily and caches it on the element. The part worth reporting is what it cost in
the compiler: nothing. The second extern takes five arguments — a handle and four
integers — and the extern machinery, built for one- and two-argument imports, compiled the
five-argument call chain without a single change. The declaration went into the contrib
library, the two implementations went into the glue, and the language was already done.
Verified before any browser opened
Testing a browser page usually means opening a browser. The FFI’s shape allowed something
stricter: a headless harness that stubs the glue — every canvas call is captured instead
of drawn, every fill style parsed, every one-by-one rectangle recorded into a pixel grid —
and then writes that grid out in the same image format the native example produces. The
comparison is not “does it look right” but cmp: the file rebuilt from the captured FFI
calls is byte-for-byte identical to the file the C backend wrote. Five thousand one hundred
eighty-four rectangle calls, one per pixel, and not one of them off. The whole FFI path —
Wasm arithmetic, string building for the CSS colors, the import boundary — verified against
ground truth before the page was ever loaded.
The checksum under the picture
The page went live with one detail I care about more than the picture. Under the canvas, the status line prints the render’s Adler checksum — and it is the same number the interpreter prints, the same number the C backend prints, the same number the headless harness checks. The test suite has pinned the backends to each other for eighty releases; that discipline normally lives in CI output nobody sees. Here it sits on a public page, under a rendered image, as one line of text: this picture, on your machine, in your browser, is the same picture to the last bit. Parity as something you can look at. The render is 96 by 54 and CSS-scaled, because every float in it is still boxed and the memory is still fixed — the cliff from two episodes ago hasn’t moved. Wanting this page to render bigger is now the standing, concrete reason to do the memory-growth work. A dogfood that ends by creating the appetite for the next one is the method working as intended.