2026-09-11, late. A plan, written before any code, from reading Roc's platform tests and safari's browser harness side by side. Nothing here is built.
The browser side already exists and does not need to change. Safari's
web/blitter.js fetches /driving/safari.wasm, binds sixteen exports, and
paints. Per frame it calls renderFrame, which answers a byte length, and
reads that many bytes from bufPtr as a stream of 32-bit words: a tag, a
colour, a point count, then x,y pairs as f32 bit patterns, with a
six-word form for a disc and a gradient form for tags two to six. Between
frames it calls advance and back, and it asks for readouts: clock,
riderSeg, riderTilt, skyTop, skyHorizon, the sun's position and
scale, riderV, truckLead, truckV. The JavaScript is the oracle for
"looks ok", and it stays the fork it is.
Today those sixteen exports are poc/drive_shim.zig, appended to the
Codex-to-zig transpile. The shim holds the state the pure program cannot,
because Codex records live in a bump arena the shim rewinds every frame: a
flat rider, a flat truck, a clock, a 2048-deep history ring for the down
arrow, and the world built once. Its renderFrame calls ride-frame then
blit-expand and packs the commands into words itself.
The Roc side has the pattern in its own test tree. A Roc app sits on a
platform; the platform is a Roc header plus a host in another language.
For wasm the host is zig compiled to wasm32-freestanding, and roc build
app.roc --target=wasm32 --output=x.wasm links the two into one module whose
exports the platform header lists. test/wasm/platform/host.zig is a
working host of a few hundred lines: the allocator, the panic hooks, an
exported wasm_main that calls into Roc. And test/provided-callable-host
shows the shape we want: the platform provides half a dozen named
functions that take and return Box(...) values, the host declares them as
C externs and calls them as often as it likes, and a boxed value crosses as
one pointer whose identity the host keeps. boxed_model_update is that
shape with a model: update : Box(Model) -> Box(Model).
One platform, one app, and the chapter modules we already have.
The model is a Roc value, boxed. The rider, the truck, the clock, the
history as a List of past rides, and the world. Roc's reference counting
keeps it alive between calls, so the arena rewind and the flat copies the
zig shim needed disappear. Safari.ride_next and Safari.ride_frame are
already emitted modules; the app is a hundred lines that hold the model and
call them.
The word packing moves into Roc. renderFrame's loop over commands is
pure: List(DrawCmd) -> List(U32), with F32.to_bits for the coordinates.
Done in Roc it is graded like everything else, by comparing the words the
Codex shim produces for the same ride, frame for frame, which is what the
harness's paint_probe.js already does for three hundred frames.
The platform header provides sixteen functions, one per blitter export,
most of them Box(Model) -> F64 readouts, plus init, advance, back,
and render : Box(Model) -> List(U32).
The host is a copy of Roc's test host with the sixteen exports written
over it: each keeps the one boxed pointer in a static, calls the Roc extern,
and answers. renderFrame hands the List(U32)'s pointer and length
straight to the blitter, no copy.
test/wasm/app.roc on its platform, built
with our compiler, opened in a browser with a five-line page. Proves the
toolchain end to end on this box before any safari is involved: the
wasm32 LLVM path in the debug compiler, the host build, the exports.Blit-adjacent Roc module that packs
List(DrawCmd) into words, and a spec-shaped check that the words for
ride_frame(world, ride_initial) equal the Codex shim's. The verdict is
a byte comparison, so it is a .expected like the other 54.emitted.sh keeps grading
the chapters; this adds the app on top of them.serve.py from safari-codex against the Roc module at
the same path, the same blitter.| repo | revision | role |
|---|---|---|
| roc (roc-lang/roc) | main @ 68267ddd, debug build | test/wasm/platform, test/provided-callable-host are the templates |
| roc-apps | 5fed0d0 | the chapter modules the app imports |
| safari-codex | units of 2026-09-10 | web/blitter.js, poc/drive_shim.zig, harness/serve.py |