254. Mapper chr write routing
Implement CHR write routing through the mapper.
Lesson 254 of 356 · tests/chapter_03_ppu_memory_and_graphics_data/test_254_mapper_chr_write_routing.py
Reference
https://www.nesdev.org/wiki/PPU_memory_map
https://www.nesdev.org/wiki/NROMFiles to update
emulator/cartridge/mapper_interface.py
emulator/cartridge/mapper000.py
emulator/bus/ppu_bus.pyWhat is CHR memory? CHR memory is where the PPU reads tile pattern bytes from. The PPU address range $0000-$1FFF is the CHR area.
Simple example
PPU reads $0000
mapper returns the first byte of CHR ROMFor official Mapper000/NROM in this tutorial, CHR is ROM, so writes are rejected.
Why this step exists
PpuBus should only route addresses. It should not decide whether CHR writes are legal. The mapper owns that policy.
Correct responsibility split
PpuBus.write($0000-$1FFF, value)
-> mapper.write_chr(addr, value)
Mapper000.write_chr(addr, value)
-> rejects writes because official Mapper000 CHR ROM is read-onlySuggested implementation example
class MapperInterface(Protocol):
def read_prg(self, addr: int) -> int:
...
def read_chr(self, addr: int) -> int:
...
def write_chr(self, addr: int, value: int) -> None:
...
class Mapper000:
def write_chr(self, addr: int, value: int) -> None:
if not (CHR_ROM_START <= addr <= CHR_ROM_END):
raise ValueError(f"Address out of CHR ROM range: {addr:04X}")
raise ValueError("CHR ROM is read-only for official Mapper000")
class PpuBus:
def write(self, addr: int, value: int) -> None:
addr = addr & PPU_ADDRESS_MASK
if CHR_START <= addr <= CHR_END:
if self.mapper is not None:
self.mapper.write_chr(addr, value)
return
self.vram.write(addr, value)
returnOut of scope
- CHR RAM support
- unlicensed/homebrew Mapper000 variants with writable CHR RAM
- mapper bank switching
- rendering CHR tiles
Run this lesson
uv run pytest tests/chapter_03_ppu_memory_and_graphics_data/test_254_mapper_chr_write_routing.py -v