327. Горизонтальное/вертикальное зеркалирование в PpuBus

Применить горизонтальное или вертикальное зеркалирование nametable в PpuBus.

Урок 327 из 356 · tests/chapter_12_mirroring/test_327_ppu_bus_horizontal_vertical_mirroring.py

Файл для обновления

emulator/bus/ppu_bus.py

Зачем нужен этот шаг

Бит зеркалирования теперь проходит через

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

PpuBus наконец может использовать эти метаданные при отображении четырёх логических nametable на два физических региона RAM nametable.

Логические nametable

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

Каждая таблица занимает $400 байт. Полное логическое окно составляет $1000 байт, тогда как текущее физическое окно хранения составляет $800 байт.

Вертикальное зеркалирование

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

[A B]
[A B]

Горизонтальное зеркалирование

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

[A A]
[B B]

Пример предлагаемой реализации

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

Зачем сохранять вертикальное поведение без маппера? Исторические тесты туториала создают PpuBus напрямую и вводили старое фиксированное поведение обёртывания $800 ещё до появления зеркалирования картриджа. Шины, подключённые к мапперу, используют реальные метаданные картриджа; шины без маппера сохраняют эту историческую модель тестов.

Важное различие

Зеркалирование определяет псевдонимы памяти. Оно не перемещает видимую область просмотра. Текущий рендерер по-прежнему рисует фиксированную область nametable, поэтому горизонтальная прокрутка — это отдельная будущая глава.

Вне рамок

  • извлечение позиции прокрутки
  • обрезка области просмотра по соседним nametable
  • четырёхэкранное зеркалирование
  • динамическое зеркалирование, управляемое маппером
  • фикстуры коммерческих ROM

Запустить этот урок

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