Skip to main content

Module provenance

Module provenance 

Source
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§

PixelProvenance
The causal record for one emitted pixel.
PixelProvenanceFrame
One frame of PixelProvenance, indexed by y * SCREEN_W + x.
ProvenanceStash
Both provenance stores, moved out of a Ppu so a caller can put them back.
WriteAttrib
One write-attribution record: who wrote a byte, when, and with what.
WriteAttribution
Per-byte write attribution for the PPU’s own memories.

Enums§

PixelLayer
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_addr when the pixel has no pattern behind it (backdrop), so a zero would be indistinguishable from a real $0000 fetch.
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_slot when no sprite won the pixel.