327. Ppu bus horizontal vertical mirroring
Apply horizontal or vertical nametable mirroring in PpuBus.
Lesson 327 of 356 · tests/chapter_12_mirroring/test_327_ppu_bus_horizontal_vertical_mirroring.py
File to update
emulator/bus/ppu_bus.pyWhy this step exists
The mirroring bit now travels through
INesHeader
-> Cartridge
-> Mapper000
-> PpuBus.mapperPpuBus can finally use that metadata when mapping four logical nametables onto two physical nametable RAM regions.
Logical nametables
table 0: $2000-$23FF
table 1: $2400-$27FF
table 2: $2800-$2BFF
table 3: $2C00-$2FFFEach table is $400 bytes. The complete logical window is $1000 bytes, while the current physical backing window is $800 bytes.
Vertical mirroring
logical: 0 1 2 3
physical: 0 1 0 1
[A B]
[A B]Horizontal mirroring
logical: 0 1 2 3
physical: 0 0 1 1
[A A]
[B B]Suggested implementation example
# emulator/bus/ppu_bus.py
NAMETABLE_SIZE = 0x800
NAMETABLE_BYTES_PER_TABLE = 0x400
NAMETABLE_LOGICAL_SIZE = 0x1000
def normalize_nametable_addr(self, addr: int) -> int:
if 0x3000 <= addr <= 0x3EFF:
addr -= 0x1000
logical_offset = (
addr - NAMETABLE_START
) % NAMETABLE_LOGICAL_SIZE
logical_table = logical_offset // NAMETABLE_BYTES_PER_TABLE
offset_inside_table = logical_offset % NAMETABLE_BYTES_PER_TABLE
is_vertical_mirroring = (
True
if self.mapper is None
else self.mapper.is_vertical_mirroring
)
if is_vertical_mirroring:
physical_table = logical_table % 2
else:
physical_table = logical_table // 2
return (
NAMETABLE_START
+ physical_table * NAMETABLE_BYTES_PER_TABLE
+ offset_inside_table
)Why preserve vertical behavior without a mapper? Historical tutorial tests construct PpuBus directly and introduced the old fixed $800 wrapping behavior before cartridge mirroring existed. Mapper-backed buses use real cartridge metadata; mapper-less buses preserve that historical test model.
Important distinction
Mirroring determines memory aliases. It does not move the visible viewport. The current renderer still draws a fixed nametable region, so horizontal scrolling is a separate future chapter.
Out of scope
- scroll-position extraction
- viewport cropping across adjacent nametables
- four-screen mirroring
- mapper-controlled dynamic mirroring
- commercial ROM fixtures
Run this lesson
uv run pytest tests/chapter_12_mirroring/test_327_ppu_bus_horizontal_vertical_mirroring.py -v