327. Mirroring horizontal y vertical del bus del PPU

Aplicar mirroring horizontal o vertical de nametables en PpuBus.

Lección 327 de 356 · tests/chapter_12_mirroring/test_327_ppu_bus_horizontal_vertical_mirroring.py

Archivo a actualizar

emulator/bus/ppu_bus.py

Por qué existe este paso

El bit de mirroring ahora viaja a través de

INesHeader
    -> Cartridge
    -> Mapper000
    -> PpuBus.mapper

PpuBus puede por fin usar esos metadatos al mapear cuatro nametables lógicos sobre dos regiones físicas de RAM de nametables.

Nametables lógicos

table 0: $2000-$23FF
table 1: $2400-$27FF
table 2: $2800-$2BFF
table 3: $2C00-$2FFF

Cada tabla es de $400 bytes. La ventana lógica completa es de $1000 bytes, mientras que la ventana física de respaldo actual es de $800 bytes.

Mirroring vertical

logical:  0 1 2 3
physical: 0 1 0 1

[A B]
[A B]

Mirroring horizontal

logical:  0 1 2 3
physical: 0 0 1 1

[A A]
[B B]

Ejemplo de implementación sugerido

# 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
    )

¿Por qué conservar el comportamiento vertical sin mapper? Los tests históricos del tutorial construyen PpuBus directamente e introdujeron el antiguo comportamiento de ajuste fijo en $800 antes de que existiera el mirroring de cartucho. Los buses con mapper usan metadatos reales del cartucho; los buses sin mapper conservan ese modelo histórico de test.

Distinción importante

El mirroring determina alias de memoria. No mueve el viewport visible. El renderizador actual sigue dibujando una región fija de nametable, por lo que el scrolling horizontal es un capítulo futuro separado.

Fuera del alcance

  • extracción de la posición de scroll
  • recorte del viewport a través de nametables adyacentes
  • mirroring de cuatro pantallas
  • mirroring dinámico controlado por el mapper
  • fixtures de ROM comerciales

Ejecutar esta lección

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