328. PPUCTRL: биты nametable во временном регистре

Скопировать выбор базового nametable из PPUCTRL в temp_vram_addr.

Урок 328 из 356 · tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py

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

emulator/ppu/ppu.py

Справочная документация

https://www.nesdev.org/wiki/PPU_scrolling#$2000_(PPUCTRL)_write
https://www.nesdev.org/wiki/PPU_registers

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

Адрес прокрутки PPU собирается из нескольких записей регистров, видимых для CPU. Проект уже обрабатывает большую часть частей $2005 PPUSCROLL:

coarse X -> temp_vram_addr bits 0-4
coarse Y -> temp_vram_addr bits 5-9
fine Y   -> temp_vram_addr bits 12-14
fine X   -> separate fine_x field

Недостающая часть — это выбор логического базового nametable

PPUCTRL bits 0-1 -> temp_vram_addr bits 10-11

Без этого соединения temp_vram_addr ведёт себя так, будто прокрутка всегда начинается с логического nametable 0 ($2000), даже если игра выбрала $2400, $2800 или $2C00. Из-за этого будущий рендерер области просмотра пересекал бы неверную логическую границу.

Внутренняя структура прокрутки

temp_vram_addr (t): yyy NN YYYYY XXXXX

XXXXX -> coarse X tile position
YYYYY -> coarse Y tile position
NN    -> logical nametable selection
yyy   -> fine Y pixel position

fine_x (x) is stored separately

Отображение PPUCTRL

bits 0-1 = 00 -> logical nametable $2000
bits 0-1 = 01 -> logical nametable $2400
bits 0-1 = 10 -> logical nametable $2800
bits 0-1 = 11 -> logical nametable $2C00

Предлагаемая реализация

# emulator/ppu/ppu.py

case 0x2000:
    self.ctrl = value

    # --- NEW LINE: COPY BASE NAMETABLE INTO temp_vram_addr: ...GH.. ........ <- value: ......GH
    self.temp_vram_addr = (self.temp_vram_addr & 0b1111_0011_1111_1111) | ((value & CTRL_BASE_NAMETABLE_MASK) << 10)
    # --- END NEW LINE ---

Зачем сначала очищать биты 10-11? Игра может переключиться с любого логического nametable на любой другой. Использование только OR устанавливало бы новые биты, но не могло бы очистить старые. Операция «очистить, затем вставить» заменяет предыдущий выбор, сохраняя при этом грубый X, грубый Y и тонкий Y.

Почему не использовать старое поле scroll? $2005 получает две записи, но поле scroll, используемое для совместимости, хранит только самый последний байт. temp_vram_addr вместе с fine_x сохраняют полное состояние в аппаратном стиле.

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

Этот шаг фиксирует выбор логического nametable. Затем зеркалирование картриджа отображает этот логический выбор на физическую RAM nametable. Ни одна из этих операций сама по себе не перемещает видимую область просмотра; извлечение/рендеринг области просмотра появятся в более поздних шагах.

Вне рамок

  • вычисление пиксельных X/Y области просмотра
  • рендеринг соседних nametable
  • перенос t в v в точные аппаратные такты
  • изменение зеркалирования PpuBus
  • фикстуры коммерческих ROM

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

uv run pytest tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py -v