326. Mapper preserves mirroring

Propagate nametable mirroring metadata from Cartridge into Mapper000.

Lesson 326 of 356 · tests/chapter_12_mirroring/test_326_mapper_preserves_mirroring.py

Files to update

emulator/cartridge/mapper000.py
emulator/cartridge/mapper_factory.py
emulator/cartridge/mapper_interface.py

Why this step exists

PpuBus already receives the cartridge mapper. Exposing mirroring through the mapper keeps one cartridge-hardware ownership path:

INesHeader
    -> Cartridge
    -> Mapper000
    -> PpuBus (next step)

This is preferable to separately connecting Cartridge directly to PpuBus. Future mappers may control mirroring through mapper registers, so the mapper is the useful long-term boundary.

Suggested implementation changes

# emulator/cartridge/mapper000.py

@dataclass
class Mapper000:
    prg_rom: bytes
    chr_rom: bytes

    # --- NEW LINE ---
    is_vertical_mirroring: bool = False
    # --- END NEW LINE ---

    ...


# emulator/cartridge/mapper_factory.py

def create_mapper(cartridge: Cartridge):
    if cartridge.mapper_number == 0:
        return Mapper000(
            prg_rom=cartridge.prg_rom,
            chr_rom=cartridge.chr_rom,

            # --- NEW LINE ---
            is_vertical_mirroring=cartridge.is_vertical_mirroring,
            # --- END NEW LINE ---
        )

    ...


# emulator/cartridge/mapper_interface.py

class MapperInterface(Protocol):
    # --- NEW LINE ---
    is_vertical_mirroring: bool
    # --- END NEW LINE ---

    def read_prg(self, addr: int) -> int:
        ...

Annotation compatibility

The protocol may use immediate annotations or from __future__ import annotations. The executable test resolves type hints before checking that mirroring is Boolean, so the contract does not depend on whether Python stores the annotation as bool or as the string "bool".

Why append a defaulted Mapper000 field? Historical tests and tutorial code construct Mapper000 with only PRG and CHR ROM. Appending a False default preserves that constructor and means horizontal mirroring when no metadata is supplied.

Meaning

False -> horizontal mirroring
True  -> vertical mirroring

Out of scope

  • changing PpuBus address mapping
  • changing CpuBus ownership
  • four-screen mirroring
  • scrolling
  • commercial ROM fixtures

Run this lesson

uv run pytest tests/chapter_12_mirroring/test_326_mapper_preserves_mirroring.py -v