347. 扫描线水平对
为一条被记录的扫描线选择水平逻辑命名表对。
第 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 和细X值。在后续渲染器能够复制该扫描线的像素之前,它必须知道是哪两个水平相邻的逻辑命名表构成了源对。
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 位、粗X和细X来选择该行内的水平像素位置。
重要不变量
- 只有 vram_addr 的第 11 位影响所选的对
- 第 10 位不改变该对
- 粗X、粗Y、细Y和细X不改变该对
- 返回值是逻辑 PPU 命名表地址
- 卡带镜像仍属于 PpuBus 的行为
- 该辅助函数是纯函数,不执行任何渲染或内存访问
常见误解
第 10 位并不在上、下两个水平对之间做选择。它标识该对内部左侧或右侧的水平命名表,并将在下一课中成为视口X计算的一部分。
超出范围
- 解码水平视口X
- 读取命名表或图案表字节
- 应用卡带镜像
- 合成帧缓冲行
- 在已完成的定时数据与旧的备用路径之间做选择
完整示例实现
# 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