journal 2026-05-05

P74 AtomVM interactive game host bridge

P74 turns the framebuffer viewer into the front half of an interactive device. P73 could show frames produced by AtomVM; P74 sends host input back into the simulated chip and lets Erlang decide what to do.

The path is:

pygame key event
  -> captured/input.fifo
  -> Verilator harness queue
  -> host_input_valid/data top-level pins
  -> MMIO_INPUT_STATUS / MMIO_INPUT_DATA
  -> chip:input/0
  -> Erlang server process state update
  -> framebuffer redraw
  -> chip:frame_ready/1
  -> host frame dump and viewer refresh

The game itself is intentionally small: a square moves on a gradient background, Space changes color, and a meter at the bottom reflects how many input events the chip consumed.

Verification target:

cd projects/74_atomvm_interactive_game/test
make all
make verilator-run-input-smoke

The smoke target writes scripted key events into the FIFO so the non-interactive run can prove the input path without needing a human at the keyboard. The interactive target is make view-interactive.

Follow-up jank pass:

cd projects/74_atomvm_interactive_game/test
make profile-interactive-smoke

That showed the original roughness was mostly simulated chip work, not frame transfer. The project now has a coarse chip:draw_game_frame/5 NIF so Erlang crosses into C once per frame instead of roughly 109 times. The current profile renders 32 frames in 13,764,739 post-load cycles, runs Verilator at about 1.40 MHz wall-clock, and averages about 137,700 chip cycles per steady-state frame. The harness framebuffer dump cost is only about 0.072 ms/frame, and the pygame read/convert/draw path profiles around 4.77 ms/frame.

Note: the first implementation tried AtomVM’s OTP gen_server, but that path parked before the first framebuffer render in this bare-metal setup. The checked-in P74 demo uses a small Erlang mailbox server so the host-input bridge is proven now; swapping back to OTP gen_server is a follow-up runtime task.

Honest status: RTL simulation only. F/D compliance was NOT RUN and LibreLane hardening was NOT RUN.