Interactive ABI

This ABI is for stateful, event-driven modules that render an output frame. We prefer a small host/module contract over protocol complexity: send key and pointer events directly, advance state with tick, then pull pixels with render(input_size).

The design goal is practical interoperability: browser and native hosts can adapt input to this ABI, and modules stay tiny.

Core Contract #

Required exports:

Output Format

Little-endian note:

Rendering Target Interop

We chose rgba8_srgb with stride = width * 4 for direct compatibility with browser <canvas> 2D context’s putImageData(), which expects RGBA bytes in that logical order.

Event Semantics

key_event(...):

pointer_event(...):

Tick + Render Flow

We intentionally split simulation from rendering.

tick(now_ms) return value:

Time source requirements:

Sizing Variants #

Use the same core ABI for both variants.

Static Size Variant #

Module decides size at instantiation and keeps it fixed.

Additional required exports:

Rules:

Host Loop Pattern #

Typical host flow:

  1. Deliver input as it arrives via key_event(...) / pointer_event(...).
  2. Call tick(now_ms) when input is ready to apply, or when host time reaches next_wake_at_ms.
  3. Call render(0) after each tick that was run, then read output_ptr() bytes.
  4. Schedule the next host wake from the returned next_wake_at_ms (if non-zero) and pending input times.

This gives low-latency input handling while keeping frame scheduling host-driven.

Browser Presentation #

The interactive ABI describes framebuffer pixels, not CSS layout. Browser hosts can scale a fixed framebuffer at presentation time.

For <qip-play>, the page can set presentation attributes:

The same sizing values can also come from CSS custom properties when a page wants layout to live in stylesheets:

Attributes win over CSS custom properties.

<qip-play> leaves image-rendering at the browser default. That is the better default for high-DPI downscaling, and 1:1 rendering is unaffected.

Pointer events are still converted from the displayed canvas box into render-space pixels. This lets a component render a 2x backing buffer, while the page displays it at a 1x CSS size for high-DPI screens.

For debug instrumentation, add debug to <qip-play>. In debug mode the host compares each rendered framebuffer with the previous frame, reports unchanged renders, and shows the latest exact-compare time. This is opt-in because scanning the framebuffer can be as expensive as, or more expensive than, drawing it to the canvas.

Why This Shape #

This ABI keeps packet parsing out of the Wasm module. Hosts adapt browser or native input at the edge, then modules handle a familiar game/UI loop: receive input events, advance state with tick, and render the current frame.