347. Горизонтальная пара scanline

Выбрать горизонтальную логическую пару nametable для одного записанного scanline.

Урок 347 из 356 · tests/chapter_13_scrolling/test_347_scanline_horizontal_pair.py

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

emulator/rendering/ppu_background_renderer.py

Ссылки

https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_nametables

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

Завершённый синхронизированный кадр хранит один эффективный vram_addr и значение fine X для каждого видимого scanline. Перед тем как позже рендерер сможет скопировать пиксели этого scanline, он должен знать, какие две горизонтально соседние логические nametable образуют исходную пару.

Поля выбора nametable внутри vram_addr:

bit 10: horizontal nametable position inside a 512-pixel-wide pair
bit 11: vertical nametable row that selects the pair

Выбор пары поэтому использует только бит 11

bit 11 clear: ($2000, $2400)
bit 11 set:   ($2800, $2C00)

Интуитивная модель

upper logical row:  $2000 | $2400
lower logical row:  $2800 | $2C00

Вспомогательная функция выбирает одну полную строку. Позже шаг будет использовать бит 10, coarse X и fine X для выбора горизонтальной позиции пикселя внутри этой строки.

Важные инварианты

  • только бит 11 vram_addr влияет на выбранную пару
  • бит 10 не изменяет пару
  • coarse X, coarse Y, fine Y и fine X не изменяют пару
  • возвращаемые значения — это логические адреса PPU nametable
  • зеркалирование картриджа остаётся поведением PpuBus
  • вспомогательная функция чистая и не выполняет рендеринг или доступ к памяти

Распространённое заблуждение

Бит 10 не выбирает между верхней и нижней горизонтальными парами. Он идентифицирует левый или правый горизонтальный nametable внутри пары и станет частью расчёта viewport-X в следующем уроке.

Вне области действия

  • декодирование горизонтального viewport X
  • чтение байтов nametable или pattern-table
  • применение зеркалирования картриджа
  • композиция строк framebuffer
  • выбор завершённых синхронизированных данных в сравнении со старым fallback

Полный пример реализации

# emulator/rendering/ppu_background_renderer.py

# --- UPDATED LINES: IMPORT THE RECORDED SCANLINE VALUE ---
from emulator.ppu.ppu import (
    BackgroundScanlineState,
    CTRL_BACKGROUND_PATTERN_TABLE,
    PPU,
)

...

# --- NEW BLOCK: SELECT ONE SCANLINE'S LOGICAL HORIZONTAL PAIR ---
def _scanline_horizontal_pair(
    state: BackgroundScanlineState,
) -> tuple[int, int]:
    nametable_y = (state.vram_addr >> 11) & 1
    left_base = BASE_NAMETABLE_ADDR + nametable_y * 0x0800

    return left_base, left_base + 0x0400

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

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