Expand description
v2.3.2 “Lucid” — per-byte write attribution for PPU-visible memory.
§What this is for
The pixel-provenance debugger answers “why is this pixel this color?” by walking backwards from an emitted pixel through the PPU fetch pipeline to the bytes that fed it — the nametable byte, the attribute byte, the pattern-table bytes, the palette entry, the sprite record. That chain stops dead at the bytes themselves unless something remembers who put them there.
The existing devtools each hold one piece and none holds this one. The Trace
Logger has the PC but not the effect; the Event Viewer
(LockstepBus::events) has the CPU-side $2000-$3FFF write with its PPU
position but neither the resolved VRAM address nor the PC; the memory-access
counter has per-address read/write counts and a last-access cycle stamp but,
again, no PC. This module supplies the missing edge: for each byte of the
PPU’s own memories, the program counter and CPU cycle of the write that
last stored it.
§Why the storage lives here and not on the bus
Attribution cannot be recorded at crate::PpuBus’s CPU-write boundary,
because the effective destination is not visible there. A nametable byte is
written by a STA $2007 whose target address lives in the PPU’s internal v
register, set earlier by two $2006 writes; a palette entry is the same
$2007 store landing in a different memory; an OAM byte arrives either
through $2004 or as one of 256 bytes of an OAM DMA burst triggered by a
single $4014 store. Only the PPU knows where each one actually went, so the
record is stamped at the store site — Ppu::write_vram, Ppu::write_palette,
and the two OAM write paths — with a (pc, cycle) context the bus latches per
instruction and hands down.
§Cost
The whole module is debug-hooks-gated, and even under that feature the store
is allocated lazily — Ppu::write_attribution is None until the frontend
arms it, so a debug-hooks build with the provenance panel closed pays one
Option discriminant test per PPU-memory write and nothing else. Armed, it
costs WriteAttribution::HEAP_BYTES of heap and one 16-byte store per
write. Nothing here is read by emulation, so the framebuffer, the audio, and
the cycle counts are bit-identical whether it is armed or not.
§Deliberate scope limit
CHR writes ($0000-$1FFF) are not attributed. That window is owned by the
mapper — it may be ROM (undriveable), CHR-RAM, or a board-specific window with
its own banking — so a byte offset here is not a stable identity across a bank
switch the way CIRAM, palette RAM, and OAM offsets are. Pattern-table
provenance is reported as “supplied by mapper bank N”, which the mapper
already knows, rather than being faked with an unstable offset.
Structs§
- Pixel
Provenance - The causal record for one emitted pixel.
- Pixel
Provenance Frame - One frame of
PixelProvenance, indexed byy * SCREEN_W + x. - Provenance
Stash - Both provenance stores, moved out of a
Ppuso a caller can put them back. - Write
Attrib - One write-attribution record: who wrote a byte, when, and with what.
- Write
Attribution - Per-byte write attribution for the PPU’s own memories.
Enums§
- Pixel
Layer - Which layer won the priority decision at a pixel.
Constants§
- CIRAM_
LEN - Number of attributed CIRAM bytes — the 2 KiB of internal nametable RAM.
- OAM_LEN
- Number of attributed OAM bytes (64 sprites x 4).
- PALETTE_
LEN - Number of attributed palette-RAM entries.
- PATTERN_
ADDR_ NONE PixelProvenance::pattern_addrwhen the pixel has no pattern behind it (backdrop), so a zero would be indistinguishable from a real$0000fetch.- SCREEN_
H - Screen height in pixels. Alias of
crate::SCREEN_HEIGHT. - SCREEN_
W - Screen width in pixels, and the stride of a
PixelProvenanceFrame. - SPRITE_
SLOT_ NONE PixelProvenance::sprite_slotwhen no sprite won the pixel.