344. 递减水平 vram 地址

在一份复制的地址上逆转一次水平背景获取递增。

344 / 356 · tests/chapter_13_scrolling/test_344_decrement_horizontal_vram_addr.py

要更新的文件

emulator/ppu/ppu.py

参考资料

https://www.nesdev.org/wiki/PPU_scrolling#Details

为什么需要这一步

点 321-336 会为下一条扫描线预取前两个背景图块。每次图块获取都会推进水平 v,因此在点 1,该地址已经比背景移位寄存器中存储的像素超前了两个图块列:

intended visible coarse X = 5
prefetch tile 5 -> v advances to 6
prefetch tile 6 -> v advances to 7

dot 1:
    shifters begin displaying tile 5
    v already contains coarse X 7

稍后的扫描线记录会复制 v 并将该副本回退两次

7 -> 6 -> 5

该辅助函数逆转一次递增。它不是定时的 PPU 操作,而且绝不能让真实的 PPU.vram_addr 向后移动。

正常行为

coarse X 31 -> 30
coarse X 7  -> 6
coarse X 1  -> 0

边界行为

coarse X 0 -> 31 and toggle horizontal nametable

重要不变量

  • 只有粗X以及回绕时的水平命名表可能改变
  • 所有垂直字段和无关字段都保持不变
  • 递减是已测试的水平递增的逆操作
  • 该函数是纯函数,不会修改 PPU

常见误解

NES PPU 不会在渲染期间执行这种递减。它存在的唯一目的,是为现有的高层渲染器把超前于显示的获取地址换算成可见的视口地址。

超出范围

  • 从 PPU.step() 调用该辅助函数
  • 记录 240 个扫描线位置
  • 帧缓冲或不透明度掩码的变更

完整示例实现

# emulator/ppu/ppu.py

# --- NEW BLOCK: REVERSE ONE HORIZONTAL FETCH INCREMENT ---
def decrement_horizontal_vram_addr(vram_addr: int) -> int:
    coarse_x = vram_addr & 0b1_1111

    if coarse_x == 0:
        vram_addr = (
            (vram_addr & ~0b1_1111)
            | 0b1_1111
        )
        vram_addr ^= 0b000_01_00000_00000
        return vram_addr

    return vram_addr - 1

运行本课

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