puck

apps that travel between tiny computers.

5 apps down, 11 devices across, 55 cells. Every one of them either runs that app's own C, one click away, or says plainly what is missing. Nothing below is a mockup and nothing below is a date somebody typed: the whole table is ledger.json, written by bun run ledger, which builds and replays every cell it claims. puck is a device-agnostic emulator, a set of self-contained device packs, and a set of portable app bundles.

the matrix

A cell with a picture is a build that ran: emulator is the module rebuilt from the port's own source and its traces replayed, host is the same C compiled natively under address and undefined-behaviour sanitizers and diffed against it frame by frame, and silicon is how many real boards have run that port's trace and confirmed the frames. blind is whether an agent with no session context ported it from the pack folder alone. A cell with no port carries the mechanical verdict instead. Every mark folds a sentence: hover it, or open it, for the whole reason it says what it says.

device packsreal firmware, in this repository an author's own packnamed by their bundle, not carried here silhouettesa device.json and nothing else, no firmware anywhere
RP2350-Touch-AMOLED-1.8368×448 ESP32-S3-Touch-AMOLED-1.8368×448 Web-Touch368×448 tinydraw-esp32-s3-touch-amoled-18 M5StickC-PLUS2135×240 · datasheet only Feather-ESP32S2-TFT240×135 · datasheet only LILYGO T-Display-S3170×320 · datasheet only Pico Display Pack 2.0 on Pico 2320×240 · datasheet only Watchy200×200 · datasheet only M5Stack CoreS3320×240 · datasheet only Waveshare ESP32-S3-Touch-LCD-2.8240×320 · datasheet only
chrono A full-screen stopwatch: six seven-segment digits, two buttons, nothing else on screen. descriptor run it
native

native, verified by pixel-exact frame diffs. Source: packs/rp2350-touch-amoled-18/firmware/apps/chrono.c.

emulator PASS

4/4 frame(s) matched

host MATCH

4/4 capture point(s) identical to the wasm build, sanitizers address, undefined

no board has confirmed this yet
run it
faithful port

faithful port, verified by pixel-exact frame diffs. Source: apps/chrono/ports/esp32-s3-touch-amoled-18/chrono.c.

emulator PASS

4/4 frame(s) matched

host MATCH

4/4 capture point(s) identical to the wasm build, sanitizers address, undefined

no board has confirmed this yet
run it on your phone
faithful port

faithful port, verified by pixel-exact frame diffs. Source: apps/chrono/ports/web/chrono.c.

emulator PASS

4/4 frame(s) matched

host MATCH

4/4 capture point(s) identical to the wasm build, sanitizers address, undefined

not this app's target

chrono running on the M5StickC-PLUS2 silhouette
degraded

chrono on M5StickC-PLUS2 (silhouette): degraded. The cost is stated: panel, 135x240 is under the 200x200 this app asks for, and above the 128x96 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

240x135 (this panel's 135x240, presented a quarter turn round), 16.7% of the panel painted, ghost buttons a, b, pwr.

chrono running on the Feather-ESP32S2-TFT silhouette
refuse

chrono on Feather-ESP32S2-TFT (silhouette): refuse. Refused because buttons: this device offers 1 button an app may use and this app needs 2; what they are for: key, start and stop, and it must feel instant; click, reset to 00:00:00, on a different control so it cannot be hit by accident. It would also cost: panel, 240x135 is under the 200x200 this app asks for, and above the 128x96 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

135x240 (this panel's 240x135, presented a quarter turn round), 14.4% of the panel painted, ghost buttons d0, and this panel is already landscape, so the host's own quarter turn puts the app across the device's short side rather than along it.

chrono running on the LILYGO T-Display-S3 silhouette
degraded

chrono on LILYGO T-Display-S3 (silhouette): degraded. The cost is stated: panel, 170x320 is under the 200x200 this app asks for, and above the 128x96 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

320x170 (this panel's 170x320, presented a quarter turn round), 16.4% of the panel painted, ghost buttons boot, io14.

chrono running on the Pico Display Pack 2.0 on Pico 2 silhouette
go

chrono on Pico Display Pack 2.0 on Pico 2 (silhouette): go. Every dimension the app states is met by what the device declares. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

240x320 (this panel's 320x240, presented a quarter turn round), 16.0% of the panel painted, ghost buttons a, b, x, y, and this panel is already landscape, so the host's own quarter turn puts the app across the device's short side rather than along it.

chrono running on the Watchy silhouette
degraded

chrono on Watchy (silhouette): degraded. The cost is stated: buttons, "UP" is a click standing in for the key this app wants (start and stop, and it must feel instant). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs, no page here

200x200 (this panel's 200x200, presented a quarter turn round), 15.4% of the panel painted, ghost buttons up, down, menu, back. It ran, and there is no page for it here: this board declares a mono1 panel and the shared emulator only reads the 16-bit formats the real packs declare, so presenting it would mean pretending packs/web's own RGB565 framebuffer is this board's glass.

chrono running on the M5Stack CoreS3 silhouette
go

chrono on M5Stack CoreS3 (silhouette): go. Every dimension the app states is met by what the device declares. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

240x320 (this panel's 320x240, presented a quarter turn round), 16.0% of the panel painted, ghost buttons pwr, a, b, c, and this panel is already landscape, so the host's own quarter turn puts the app across the device's short side rather than along it.

chrono running on the Waveshare ESP32-S3-Touch-LCD-2.8 silhouette
go

chrono on Waveshare ESP32-S3-Touch-LCD-2.8 (silhouette): go. Every dimension the app states is met by what the device declares. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

320x240 (this panel's 240x320, presented a quarter turn round), 16.1% of the panel painted, ghost buttons boot, pwr.

fluidbox A particle liquid that sloshes and settles inside the device's own enclosure shape. descriptor run it
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it degraded. Source: apps/fluidbox/ports/rp2350-touch-amoled-18/fluid.c.

emulator PASS

all invariants held over 3 frame(s)

host MATCH

3/3 capture point(s) identical to the wasm build, sanitizers address, undefined

no board has confirmed this yet
run it
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it go. Source: apps/fluidbox/ports/esp32-s3-touch-amoled-18/fluid.c.

emulator PASS

all invariants held over 3 frame(s)

host MATCH

3/3 capture point(s) identical to the wasm build, sanitizers address, undefined

no board has confirmed this yet
run it on your phone
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it go. Source: apps/fluidbox/ports/web/fluid.c.

emulator PASS

all invariants held over 3 frame(s)

host MATCH

3/3 capture point(s) identical to the wasm build, sanitizers address, undefined

not this app's target

fluidbox running on the M5StickC-PLUS2 silhouette
degraded

fluidbox on M5StickC-PLUS2 (silhouette): degraded. It runs at 25 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (135x240 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 25 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (135x240 at one per 1268 px).

runs here

135x240, 3.8% of the panel painted, ghost buttons a, b, pwr.

fluidbox running on the Feather-ESP32S2-TFT silhouette
refuse

fluidbox on Feather-ESP32S2-TFT (silhouette): refuse. Refused because sensors: this app needs a vector sensor (gravity direction, read every step, so tilting the device pours the liquid) and the device declares none at all. It would also cost: buttons, "D0" is a click standing in for the key this app wants (reset the fluid to a settled block, one tap, no explanation). It runs at 25 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (240x135 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 25 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (240x135 at one per 1268 px).

runs here

240x135, 3.8% of the panel painted, ghost buttons d0.

fluidbox running on the LILYGO T-Display-S3 silhouette
refuse

fluidbox on LILYGO T-Display-S3 (silhouette): refuse. Refused because sensors: this app needs a vector sensor (gravity direction, read every step, so tilting the device pours the liquid) and the device declares none at all. It runs at 42 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (170x320 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 42 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (170x320 at one per 1268 px).

runs here

170x320, 3.8% of the panel painted, ghost buttons boot, io14.

fluidbox running on the Pico Display Pack 2.0 on Pico 2 silhouette
refuse

fluidbox on Pico Display Pack 2.0 on Pico 2 (silhouette): refuse. Refused because sensors: this app needs a vector sensor (gravity direction, read every step, so tilting the device pours the liquid) and the device declares none at all. It runs at 60 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (320x240 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 60 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (320x240 at one per 1268 px).

runs here

320x240, 3.8% of the panel painted, ghost buttons a, b, x, y.

fluidbox running on the Watchy silhouette
refuse

fluidbox on Watchy (silhouette): refuse. Refused because panel: this app's colour carries information and the panel is mono1, which is not a colour one. It would also cost: buttons, "UP" is a click standing in for the key this app wants (reset the fluid to a settled block, one tap, no explanation). It runs at 31 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (200x200 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 31 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (200x200 at one per 1268 px).

runs, no page here

200x200, 3.8% of the panel painted, ghost buttons up, down, menu, back. It ran, and there is no page for it here: this board declares a mono1 panel and the shared emulator only reads the 16-bit formats the real packs declare, so presenting it would mean pretending packs/web's own RGB565 framebuffer is this board's glass.

fluidbox running on the M5Stack CoreS3 silhouette
degraded

fluidbox on M5Stack CoreS3 (silhouette): degraded. It runs at 60 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (320x240 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 60 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (320x240 at one per 1268 px).

runs here

320x240, 3.8% of the panel painted, ghost buttons pwr, a, b, c.

fluidbox running on the Waveshare ESP32-S3-Touch-LCD-2.8 silhouette
degraded

fluidbox on Waveshare ESP32-S3-Touch-LCD-2.8 (silhouette): degraded. It runs at 60 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (240x320 at one per 1268 px). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs. It runs at 60 particles against the reference 130 (how many particles the fluid is made of), bound by panel area (240x320 at one per 1268 px).

runs here

240x320, 3.8% of the panel painted, ghost buttons boot, pwr.

tinydraw A full-panel finger-drawing canvas: variable-width antialiased ink, two-level zoom, one-stroke undo. descriptor run it
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it degraded. Source: apps/tinydraw/ports/rp2350-touch-amoled-18/tinydraw.c.

emulator PASS

all invariants held over 4 frame(s)

host MATCH

4/4 capture point(s) identical to the wasm build, sanitizers address, undefined

no board has confirmed this yet
run it
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it degraded. Source: apps/tinydraw/ports/esp32-s3-touch-amoled-18/tinydraw.c.

emulator PASS

all invariants held over 4 frame(s)

host MATCH

4/4 capture point(s) identical to the wasm build, sanitizers address, undefined

ported blind 2026-09-02

claude-sonnet ported tinydraw to esp32-s3-touch-amoled-18 on 2026-09-02 with no session context; given the pack folder, the app bundle, and docs/convention/ - no session context and no sibling port to copy from; bun run verify-bundle went green on the first run, with no correction from a person; 1 attempt; about 50 minutes. Recorded by hand in blind-ports.json, not computed here.

no board has confirmed this yet
run it on your phone
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it degraded. Source: apps/tinydraw/ports/web/tinydraw.c.

emulator PASS

all invariants held over 4 frame(s)

host MATCH

4/4 capture point(s) identical to the wasm build, sanitizers address, undefined

not this app's target

tinydraw running on the M5StickC-PLUS2 silhouette
refuse

tinydraw on M5StickC-PLUS2 (silhouette): refuse. Refused because touch: this app needs 1 touch point(s) and the device declares 0. It would also cost: panel, 135x240 is under the 200x200 this app asks for, and above the 128x128 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

blank

m5stickc-plus2 declares no digitizer (touch.points 0), and tinydraw demands 1 touch point(s), so there is nothing here that could ever answer a touch. the app compiled, instantiated and painted this 135x240 panel, and 100.00% of it is one single colour. That is what an app with nothing to show yet looks like, and it is also what an app drawing at coordinates outside this panel looks like: the PNG beside this line is the only thing that tells the two apart.

tinydraw running on the Feather-ESP32S2-TFT silhouette
refuse

tinydraw on Feather-ESP32S2-TFT (silhouette): refuse. Refused because buttons: this device offers 1 button an app may use and this app needs 2; what they are for: key, cycle the zoom level; click, undo the last stroke, on a different control so cycling zoom cannot erase work, and because touch: this app needs 1 touch point(s) and the device declares 0. It would also cost: panel, 240x135 is under the 200x200 this app asks for, and above the 128x128 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

blank

feather-esp32s2-tft declares no digitizer (touch.points 0), and tinydraw demands 1 touch point(s), so there is nothing here that could ever answer a touch. the app compiled, instantiated and painted this 240x135 panel, and 100.00% of it is one single colour. That is what an app with nothing to show yet looks like, and it is also what an app drawing at coordinates outside this panel looks like: the PNG beside this line is the only thing that tells the two apart.

tinydraw running on the LILYGO T-Display-S3 silhouette
refuse

tinydraw on LILYGO T-Display-S3 (silhouette): refuse. Refused because touch: this app needs 1 touch point(s) and the device declares 0. It would also cost: panel, 170x320 is under the 200x200 this app asks for, and above the 128x128 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

blank

lilygo-t-display-s3 declares no digitizer (touch.points 0), and tinydraw demands 1 touch point(s), so there is nothing here that could ever answer a touch. the app compiled, instantiated and painted this 170x320 panel, and 100.00% of it is one single colour. That is what an app with nothing to show yet looks like, and it is also what an app drawing at coordinates outside this panel looks like: the PNG beside this line is the only thing that tells the two apart.

tinydraw running on the Pico Display Pack 2.0 on Pico 2 silhouette
refuse

tinydraw on Pico Display Pack 2.0 on Pico 2 (silhouette): refuse. Refused because touch: this app needs 1 touch point(s) and the device declares 0. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

blank

pico-display-pack-2 declares no digitizer (touch.points 0), and tinydraw demands 1 touch point(s), so there is nothing here that could ever answer a touch. the app compiled, instantiated and painted this 320x240 panel, and 100.00% of it is one single colour. That is what an app with nothing to show yet looks like, and it is also what an app drawing at coordinates outside this panel looks like: the PNG beside this line is the only thing that tells the two apart.

tinydraw running on the Watchy silhouette
refuse

tinydraw on Watchy (silhouette): refuse. Refused because touch: this app needs 1 touch point(s) and the device declares 0. It would also cost: buttons, "UP" is a click standing in for the key this app wants (cycle the zoom level). This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

blank

watchy declares no digitizer (touch.points 0), and tinydraw demands 1 touch point(s), so there is nothing here that could ever answer a touch. the app compiled, instantiated and painted this 200x200 panel, and 100.00% of it is one single colour. That is what an app with nothing to show yet looks like, and it is also what an app drawing at coordinates outside this panel looks like: the PNG beside this line is the only thing that tells the two apart.

tinydraw running on the M5Stack CoreS3 silhouette
go

tinydraw on M5Stack CoreS3 (silhouette): go. Every dimension the app states is met by what the device declares. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

the module's own emu_device() declares M5Stack CoreS3: 320x240, buttons pwr, a, b, c; the painted canvas is 320x240 device pixels, one per panel pixel; a synthetic drag changed the panel: 4.70% of pixels differ from before it, over the 0.5% floor.

tinydraw running on the Waveshare ESP32-S3-Touch-LCD-2.8 silhouette
go

tinydraw on Waveshare ESP32-S3-Touch-LCD-2.8 (silhouette): go. Every dimension the app states is met by what the device declares. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

the module's own emu_device() declares Waveshare ESP32-S3-Touch-LCD-2.8: 240x320, buttons boot, pwr; the painted canvas is 240x320 device pixels, one per panel pixel; a synthetic drag changed the panel: 4.51% of pixels differ from before it, over the 0.5% floor.

gameos A handheld game console shell: pick a game to launch a thermal gunner, a slot machine, or a swing-driven procedural golf course. descriptor run it
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it degraded. Source: apps/gameos/ports/rp2350-touch-amoled-18/gameos_port.c.

emulator PASS

all invariants held over 12 frame(s)

host MATCH

12/12 capture point(s) identical to the wasm build, sanitizers address, undefined

no board has confirmed this yet
run it
adaptation

adaptation, verified by stated behavioral invariants, and its own port notes call it degraded. Source: apps/gameos/ports/esp32-s3-touch-amoled-18/gameos_port.c.

emulator PASS

all invariants held over 21 frame(s)

host SANITIZER

the sanitized host build trapped on gameos-demo-esp32 (address, undefined): signed integer overflow: 1499045572 + 668265263 cannot be represented in type 'int'

no board has confirmed this yet
go

gameos on Web-Touch (pack): go. Every dimension the app states is met by what the device declares. Unchecked: memory (this target declares no budget.ram.bytes, and nothing here invents one); tick (this target declares no budget.tickBudgetMs). A verdict is a comparison of two documents, never a prediction that the port runs.

no port yet: how to port it

not this app's target

gameos running on the M5StickC-PLUS2 silhouette
refuse

gameos on M5StickC-PLUS2 (silhouette): refuse. Refused because panel: 135x240 is under the 184x224 this app can scale to, and because touch: this app needs 1 touch point(s) and the device declares 0. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

135x240, 66.1% of the panel painted, ghost buttons a, b, pwr. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own.

gameos running on the Feather-ESP32S2-TFT silhouette
refuse

gameos on Feather-ESP32S2-TFT (silhouette): refuse. Refused because panel: 240x135 is under the 184x224 this app can scale to, and because touch: this app needs 1 touch point(s) and the device declares 0, and because sensors: this app needs a vector sensor (GUNSHIP aims the reticle by tilt, the donor's own default aim mode) and the device declares none at all. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

240x135, 44.6% of the panel painted, ghost buttons d0. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own.

gameos running on the LILYGO T-Display-S3 silhouette
refuse

gameos on LILYGO T-Display-S3 (silhouette): refuse. Refused because panel: 170x320 is under the 184x224 this app can scale to, and because touch: this app needs 1 touch point(s) and the device declares 0, and because sensors: this app needs a vector sensor (GUNSHIP aims the reticle by tilt, the donor's own default aim mode) and the device declares none at all. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

170x320, 62.8% of the panel painted, ghost buttons boot, io14. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own.

gameos running on the Pico Display Pack 2.0 on Pico 2 silhouette
refuse

gameos on Pico Display Pack 2.0 on Pico 2 (silhouette): refuse. Refused because touch: this app needs 1 touch point(s) and the device declares 0, and because sensors: this app needs a vector sensor (GUNSHIP aims the reticle by tilt, the donor's own default aim mode) and the device declares none at all. It would also cost: panel, 320x240 is under the 368x448 this app asks for, and above the 184x224 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

320x240, 59.9% of the panel painted, ghost buttons a, b, x, y. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own.

gameos running on the Watchy silhouette
refuse

gameos on Watchy (silhouette): refuse. Refused because panel: this app's colour carries information and the panel is mono1, which is not a colour one, and because touch: this app needs 1 touch point(s) and the device declares 0. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs, no page here

200x200, 60.0% of the panel painted, ghost buttons up, down, menu, back. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own. It ran, and there is no page for it here: this board declares a mono1 panel and the shared emulator only reads the 16-bit formats the real packs declare, so presenting it would mean pretending packs/web's own RGB565 framebuffer is this board's glass.

gameos running on the M5Stack CoreS3 silhouette
degraded

gameos on M5Stack CoreS3 (silhouette): degraded. The cost is stated: panel, 320x240 is under the 368x448 this app asks for, and above the 184x224 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

the module's own emu_device() declares M5Stack CoreS3: 320x240, buttons pwr, a, b, c; the painted canvas is 320x240 device pixels, one per panel pixel; a synthetic tap changed the panel: 59.43% of pixels differ from before it, over the 0.5% floor. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own.

gameos running on the Waveshare ESP32-S3-Touch-LCD-2.8 silhouette
degraded

gameos on Waveshare ESP32-S3-Touch-LCD-2.8 (silhouette): degraded. The cost is stated: panel, 240x320 is under the 368x448 this app asks for, and above the 184x224 it scales to: the layout comes out of the panel, so it fits, smaller. This target is unverified against silicon, so every number above came from a datasheet, not from a board. A verdict is a comparison of two documents, never a prediction that the port runs.

runs here

the module's own emu_device() declares Waveshare ESP32-S3-Touch-LCD-2.8: 240x320, buttons boot, pwr; the painted canvas is 240x320 device pixels, one per panel pixel; a synthetic tap changed the panel: 87.91% of pixels differ from before it, over the 0.5% floor. Compiled from its RP2350-Touch-AMOLED-1.8 port's own file: this app has no web-pack port of its own.

aliceisjustplaying/tinydraw their repository reproduced from aliceisjustplaying/tinydraw@0c0b2d9 on 2026-09-02
no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

no port yet: how to port it
no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

no port yet: how to port it
no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

no port yet: how to port it
emulator PASS

28/28 frame(s) matched

built by aliceisjustplaying/tinydraw@000de0a, its own command: ./scripts/build-puck-wasm

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

no verdict

aliceisjustplaying/tinydraw's descriptor.md's Demands section carries no ```json demands block, so this app's requirements are prose only. Add one (docs/convention/app-bundle.md, "Demands are also machine-readable"); nothing here guesses at prose.

build-failed

aliceisjustplaying/tinydraw's bundle names no port source this pack could compile: a silhouette runs through packs/web, and this bundle declares no web port and no rp2350 port to borrow one from.

A silhouette is a board nobody has written firmware for: one device.json with its numbers read off a datasheet and never measured against silicon, compiled against through packs/web so the app really runs at that panel size with those buttons. Last computed 2026-09-02.

reference app

Not a port: each device pack's own shipped app, plus the instrument's own minimal example firmware - every tile below links to it running live, the same as the cells above.

ESP32-S3-Touch-AMOLED-1.8: demo

A bouncing square: the pack's own reference app, proving its band-render contract with nothing borrowed from another device.

puck-example

The instrument's own minimal reference firmware: a 240x240 panel, two buttons (one with a long-press verdict), a shake sensor, and a scripted two-button chord. Not a device pack, not a ported app - the smallest complete emu_device() implementation, and where a new firmware author starts.

how it works

device packs

A self-contained folder for one target: real drivers, a real board's firmware, and a device.json descriptor the emulator reads at runtime. Nothing in the shared instrument names one device; every panel size and button comes from the pack itself. The browser is one of those targets, so a phone is a device pack too, and an app ported to it goes through the same verifier as an app ported to a chip.

device-pack.md

app descriptors

An app is defined by its descriptor and traces, not by one implementation's source code: what appears on screen, every interaction, and separate requirements from preferences. A port starts with a verdict against the target pack, computed from those two documents, before any code is written.

app-bundle.md

the ledger

Every cell above is recomputed when its own inputs change, and reused when they do not. A faithful port replays its traces and compares frames pixel for pixel; an adaptation states behavioral invariants and gets checked against those; either way the shared harness decides, not a claim in a README.

decision 0012