341. Увеличить вертикальный адрес vram

Увеличить вертикальный компонент текущего адреса рендеринга v.

Урок 341 из 356 · tests/chapter_13_scrolling/test_341_increment_vertical_vram_addr.py

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

emulator/ppu/ppu.py

Ссылки

https://www.nesdev.org/wiki/PPU_scrolling#Y_increment
https://www.nesdev.org/wiki/PPU_scrolling#Wrapping_around

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

На точке 256 PPU продвигает адрес рендеринга на одну строку пикселей. В отличие от горизонтального увеличения, вертикальное движение сначала продвигает fine Y внутри текущей 8-пиксельной плитки:

yyy NN YYYYY XXXXX
+++    +++++
 |       +---- coarse Y: bits 5-9
 +------------ fine Y: bits 12-14

vertical nametable: bit 11

Поведение Fine-Y

0 -> 1 -> 2 -> ... -> 7

Когда fine Y уже равен 7, он переходит в 0 и coarse Y увеличивается.

Поведение Coarse-Y после переноса fine-Y

coarse Y 0-28 -> increment by one
coarse Y 29   -> 0 and toggle vertical nametable
coarse Y 30   -> 31 through the normal increment branch
coarse Y 31   -> 0 without toggling vertical nametable

Строки 0-29 — это 30 видимых строк плиток nametable. Значения 30 и 31 — это не обычные видимые строки, но они могут существовать, потому что coarse Y — это пятибитовое поле, и операции адреса CPU могут загружать эти значения.

Почему добавлять $1000 для обычного увеличения fine-Y? Fine Y начинается с бита 12, поэтому добавление 1 << 12 увеличивает это упакованное поле на один. Это не означает концептуально перемещение на 4096 байт через память PPU.

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

  • горизонтальные coarse X и выбор nametable остаются неизменными
  • несвязанные биты внутреннего адреса остаются неизменными
  • вертикальный nametable переключается только при переносе строки 29
  • вспомогательная функция чистая и не изменяет PPU

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

Вертикальное увеличение не просто симметрично горизонтальному увеличению. Горизонтальное движение продвигает один столбец плиток; вертикальное движение продвигает одну строку fine пикселей и только иногда продвигает строку coarse плиток.

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

  • вызов этой вспомогательной функции на точке 256
  • копирование вертикального t-в-v
  • время pre-render
  • запись viewport scanline
  • рендеринг framebuffer

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

# emulator/ppu/ppu.py

# --- NEW BLOCK: PURE VERTICAL v INCREMENT ---
def increment_vertical_vram_addr(vram_addr: int) -> int:
    fine_y = (vram_addr >> 12) & 0b111

    if fine_y < 7:
        return vram_addr + (1 << 12)

    vram_addr &= ~0b111_00_00000_00000
    coarse_y = (vram_addr >> 5) & 0b1_1111

    if coarse_y == 29:
        coarse_y = 0
        vram_addr ^= 0b000_10_00000_00000
    elif coarse_y == 31:
        coarse_y = 0
    else:
        coarse_y += 1

    return (
        (vram_addr & ~0b000_00_11111_00000)
        | (coarse_y << 5)
    )

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

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