rp2040js runs at ~50% real time, so a TFT frame burst (fillRect sky +
fillRect floor + many drawFastVLine for walls + HUD) often takes longer
than 16 ms to drain through the SPI pipeline. Painting on every rAF
captured mid-burst snapshots that the next sky fill immediately
clobbered, so the canvas only ever showed the last few pixels written
before each tick — most visibly the raycaster examples rendering 2-3
wall columns instead of 160.
Strategy: each SPI pixel write resets a 16 ms idle timer. We paint only
after that period of silence (a real frame boundary), with a 100 ms
hard cap so continuous-write sketches still update.
Also adds test/pico_doom_demo/raycaster-perf.mjs — a puppeteer-based
profiler that reports CPU step rate, SPI throughput, per-pixel cost,
and paint rate. Run with the dev backend + frontend up:
node test/pico_doom_demo/raycaster-perf.mjs
After the fix the Doom raycaster paints at the sketch's natural 10 FPS
with full frames (was 29 fps of mid-burst snapshots).
A new entry in the games category for the Raspberry Pi Pico. Renders
a first-person 3D corridor à la Wolfenstein / early Doom using a
column-wise DDA raycaster — 160 rays per frame drawn straight to a
320×240 ILI9341 TFT via drawFastVLine, no framebuffer. 16×16 tile map
with 5 wall palettes (slate, blood, brown, toxic green, bronze door),
darkened on NS faces so corners read in 3D. Forward / back move,
two more buttons turn the player. 40-px HUD bar.
Why a demo, not the canonical id Software Doom: Graham Sanderson's
rp2040-doom port shoehorns DOOM1.WAD into 2 MB of flash with custom
compression and pushes video out over PIO-driven DVI / VGA — none of
that survives the rp2040js emulator (no PIO accuracy, no flash
mapping for huge assets). A raycaster reproduces the *visual* of
early Doom using only ~67 KB of flash and 9 KB of RAM, which the
emulator runs perfectly.
Pre-flight: arduino-cli compile against rp2040:rp2040:rpipico
already verified inside the Velxio backend container — 3 % flash,
3 % RAM. The Adafruit_GFX + Adafruit_ILI9341 libs the example
declares are already in the gallery's auto-install list.
Bundled:
test/pico_doom_demo/arduino_sketch.ino — source of truth sketch
test/pico_doom_demo/README.md — what + why + pin map
test/pico_doom_demo/compile_check.sh — operator script that
runs arduino-cli against the same FQBN the prod backend uses
frontend/src/data/examples.ts — gallery entry (boardType
raspberry-pi-pico, category games, difficulty advanced, 5
components, 14 wires)
frontend/src/__tests__/examples-pico-doom.test.ts — 10 vitest
assertions: example is registered exactly once, target board /
category / difficulty match, libraries declared, all four
pushbuttons present, every wire endpoint references a real
component id, SPI pin mapping matches the sketch's #define
block, every button has a GND wire, the renderFrame loop is
still present in the embedded code.