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