327. PpuBus 水平/垂直镜像
在 PpuBus 中应用水平或垂直名称表镜像。
第 327 / 356 · tests/chapter_12_mirroring/test_327_ppu_bus_horizontal_vertical_mirroring.py
需要更新的文件
emulator/bus/ppu_bus.py该步骤存在的原因
镜像位现在会通过以下路径传递
INesHeader
-> Cartridge
-> Mapper000
-> PpuBus.mapperPpuBus 终于可以在将四个逻辑名称表映射到两个物理名称表 RAM 区域时使用该元数据了。
逻辑名称表
table 0: $2000-$23FF
table 1: $2400-$27FF
table 2: $2800-$2BFF
table 3: $2C00-$2FFF每个表占 $400 字节。完整的逻辑窗口为 $1000 字节,而当前的物理支持窗口为 $800 字节。
垂直镜像
logical: 0 1 2 3
physical: 0 1 0 1
[A B]
[A B]水平镜像
logical: 0 1 2 3
physical: 0 0 1 1
[A A]
[B B]建议的实现示例
# emulator/bus/ppu_bus.py
NAMETABLE_SIZE = 0x800
NAMETABLE_BYTES_PER_TABLE = 0x400
NAMETABLE_LOGICAL_SIZE = 0x1000
def normalize_nametable_addr(self, addr: int) -> int:
if 0x3000 <= addr <= 0x3EFF:
addr -= 0x1000
logical_offset = (
addr - NAMETABLE_START
) % NAMETABLE_LOGICAL_SIZE
logical_table = logical_offset // NAMETABLE_BYTES_PER_TABLE
offset_inside_table = logical_offset % NAMETABLE_BYTES_PER_TABLE
is_vertical_mirroring = (
True
if self.mapper is None
else self.mapper.is_vertical_mirroring
)
if is_vertical_mirroring:
physical_table = logical_table % 2
else:
physical_table = logical_table // 2
return (
NAMETABLE_START
+ physical_table * NAMETABLE_BYTES_PER_TABLE
+ offset_inside_table
)为什么在没有 mapper 时也要保留垂直行为?历史上的教程测试直接构造 PpuBus,并且在卡带镜像出现之前就引入了旧的固定 $800 环绕行为。带 mapper 的总线使用真实的卡带元数据;不带 mapper 的总线则保留这种历史测试模型。
重要区别
镜像决定的是内存别名,而不会移动可见视口。当前的渲染器仍绘制固定的名称表区域,因此水平卷动是之后单独的一章内容。
不在本步骤范围内
- 提取卷动位置
- 跨相邻名称表裁剪视口
- 四屏镜像
- mapper 控制的动态镜像
- 商业 ROM 测试夹具
运行本课
uv run pytest tests/chapter_12_mirroring/test_327_ppu_bus_horizontal_vertical_mirroring.py -v