Display Pipeline
From asset to AMOLED
PNG / bitmap atlas
└─ convert_assets.py
└─ game_assets.bin + index
└─ AssetRenderer (RGB565)
└─ GameRenderer into PSRAM canvas A
└─ diff / dirty regions against canvas B
└─ LVGL / BSP flush
└─ CO5300 QSPI AMOLEDWeb and firmware share source PNGs and palette, but firmware never decodes general PNG at runtime. The converter packs binary alpha and RGB565 plus a validated index; Pillow 11 and 12 must produce byte-identical output.
Why two full-screen canvases
- Compare pixels/regions without reading AMOLED.
- Separate the next render from the current submitted frame.
- Compare partial output to a full host reference pixel by pixel.
- Fall back to one full frame on page changes or complex state.
The cost is 868,624 bytes of PSRAM, acceptable with 8 MiB but not an invitation to add a third canvas.
Draw order
- Pure black ground.
- Navigation and structural arcs.
- Underlay FX.
- Page body and Makers.
- Edge gesture / STAR RUSH.
- Particles.
- Overlay FX.
- Callouts and messages.
Dirty regions cover old and new layers. Moving objects and sparse FX use previous ∪ current bounds so colored trails do not remain on black.
RGB565 parity
The browser is RGB888 and the panel RGB565. Both are mapped back to logical CONSOLE-RGB16 colors before layout/semantic comparison. In fixed initial/endgame states at now=0, all six v6 page images matched 114,546 tested pixels per page.
Partial refresh
Stable frames update core, Makers, numbers, meters, particles and feedback. Fingerprint changes and navigation use a full frame. Typical v6 dirty area was about 40.6K pixels/frame with a periodic full refresh around 15/150 frames; stable v7 dirty area is about 15K–18K with roughly 10% periodic full frames.


WEB V4DEVICE V7 SNAPSHOT