328. PPUCTRL 临时名称表位
将 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缺失的部分是逻辑基础名称表选择
PPUCTRL bits 0-1 -> temp_vram_addr bits 10-11没有这个连接,temp_vram_addr 的行为就好像卷动总是从逻辑名称表 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 separatelyPPUCTRL 映射
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 位?游戏可能会从任意一个逻辑名称表切换到任意另一个逻辑名称表。仅使用 OR 运算只能设置新位,而无法清除旧位。先清除再插入的操作可以替换之前的选择,同时保留粗略 X、粗略 Y 和精细 Y。
为什么不使用旧的 scroll 字段?$2005 会接收两次写入,而兼容性 scroll 字段只存储最近的一个字节。temp_vram_addr 加上 fine_x 才能保留完整的硬件风格状态。
重要区别
本步骤只是记录逻辑名称表的选择。之后卡带镜像会将该逻辑选择映射到物理名称表 RAM 上。这两个操作本身都不会移动可见视口;视口提取/渲染将在之后的步骤中实现。
不在本步骤范围内
- 推导视口像素 X/Y
- 渲染相邻名称表
- 在精确的硬件时钟点将 t 传输到 v
- 修改 PpuBus 的镜像
- 商业 ROM 测试夹具
运行本课
uv run pytest tests/chapter_13_scrolling/test_328_ppuctrl_temp_nametable_bits.py -v