350. 选择定时帧缓冲路径
仅当一个完整的帧可用时,才选择定时帧缓冲合成。
第 350 / 356 · tests/chapter_13_scrolling/test_350_select_timed_framebuffer_path.py
要更新的文件
emulator/rendering/ppu_background_renderer.py参考资料
https://www.nesdev.org/wiki/PPU_rendering
https://www.nesdev.org/wiki/PPU_scrolling为什么需要这一步
私有的定时合成器要求每个可见行都有一个 BackgroundScanlineState。这样的数据在启动期间不可用,并且在帧不完整之后也可能缺失,因此公共适配器需要一个明确的兼容边界:
exactly 240 completed states -> use timed row composition
any other tuple length -> use the existing t + fine-X snapshot path为什么要求精确长度而不是真值性?空元组是假值,但只含一项或 239 项的部分元组是真值。不完整的定时数据仍会留下未知的行,绝不能进入要求全部 240 个状态的合成器。决定哪种机制安全的,是结构不变量,而不是元组是否非空。
为什么要保留旧路径?新创建的 PPU 没有已完成的帧。历史调用方和启动渲染已经具有基于 temp_vram_addr 加 fine_x 的确定性行为。保持该主体不变,可以在第一个完整的定时帧发布之前提供一条安全的备用路径。
控制流
ppu_background_viewport_to_framebuffer(ppu)
|
v
completed length == 240?
/ yes no
| |
v v
timed row helper existing snapshot path
| |
+------ return --+重要不变量
- 恰好 240 个条目会选择定时辅助函数
- 定时合成立即返回
- 旧的源渲染器和全帧合成器不会同时运行
- 空元组、部分元组和超长元组会选择既有的备用路径
- 备用路径仍会解码 ppu.temp_vram_addr 和 ppu.fine_x
- 本课中不透明度掩码的选择保持不变
常见误解
不要捕获定时辅助函数的 ValueError 并悄悄重试备用路径。公共门控负责预期的可用性;在完整帧门控之后出现的错误表明不变量已损坏,在调试期间应当保持可见。
超出范围
- 定时的不透明度掩码合成
- 精灵/背景优先级修正
- 精灵零命中掩码整合
- 完整的垂直源行滚动
完整示例实现
# emulator/rendering/ppu_background_renderer.py
def ppu_background_viewport_to_framebuffer(ppu: PPU) -> Framebuffer:
# --- NEW BLOCK: USE TIMED DATA ONLY WHEN THE FRAME IS COMPLETE ---
if (
len(ppu.completed_scanline_scroll_states)
== NAMETABLE_PIXEL_HEIGHT
):
return _timed_scanlines_to_framebuffer(ppu)
# Existing temp_vram_addr + fine_x fallback remains unchanged below.
viewport_x, _ = decode_background_viewport_position(
temp_vram_addr=ppu.temp_vram_addr,
fine_x=ppu.fine_x,
)
...本课后的手动检查点
用你自己合法的《超级马里奥兄弟》ROM,你现在可以玩到足够远,一边观察定时的水平背景,一边尝试接住蘑菇。正是在这个里程碑处,蘑菇可能出现在一个本应在视觉上遮挡它的实心背景图块前面。这种现象是预期之中的:RGB 帧缓冲的各行现在使用定时扫描线状态,但用于精灵优先级的背景不透明度掩码,仍然使用一份旧的帧级快照。下一课将用同样的定时状态来合成不透明度掩码行,使颜色和优先级决策使用完全相同的屏幕坐标。
运行本课
uv run pytest tests/chapter_13_scrolling/test_350_select_timed_framebuffer_path.py -v