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 将渲染地址推进一个像素行。与水平递增不同,垂直移动首先在当前 8 像素图块内部推进细Y:

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

vertical nametable: bit 11

细Y行为

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

当细Y已经是 7 时,它会回绕到 0,同时粗Y递增。

细Y回绕后的粗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 个可见图块行。值 30 和 31 不是正常的可见行,但它们可能存在,因为粗Y是一个五位字段,而 CPU 的地址操作可以载入这些值。

为什么正常的细Y递增要加 $1000?细Y从第 12 位开始,所以加上 1 << 12 就会让该打包字段递增 1。从概念上讲,这并不意味着在 PPU 内存中移动了 4096 字节。

重要不变量

  • 水平粗X和命名表选择保持不变
  • 无关的内部地址位保持不变
  • 垂直命名表位仅在第 29 行回绕时翻转
  • 该辅助函数是纯函数,不会修改 PPU

常见误解

垂直递增并不简单地与水平递增对称。水平移动推进一个图块列;垂直移动推进一个细像素行,并且只在某些时候推进粗图块行。

超出范围

  • 在点 256 调用该辅助函数
  • 垂直的 t 到 v 复制
  • 预渲染时序
  • 扫描线视口记录
  • 帧缓冲渲染

完整示例实现

# 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