352. 选择定时不透明度掩码路径

仅当有一个完整帧可用时,才选择定时不透明度掩码合成。

352 / 356 · tests/chapter_13_scrolling/test_352_select_timed_opaque_mask_path.py

待更新文件

emulator/rendering/ppu_background_renderer.py

参考资料

https://www.nesdev.org/wiki/PPU_rendering
https://www.nesdev.org/wiki/PPU_scrolling

为什么需要这一步

第 351 步构建了一种机制,用于从与 RGB 帧缓冲区行相同的定时状态中合成布尔背景不透明度行。现在,公共掩码适配器必须启用该机制,同时不破坏启动或不完整帧的行为。

选择边界

exactly 240 completed states -> timed opacity-mask composition
any other tuple length       -> existing t + fine-X snapshot fallback

这个确切的门槛条件与帧缓冲区适配器完全一致。仅做真值判断是不够的,因为只有一个条目或 239 个条目的元组虽非空,但无法描述所有可见行。

为什么要保留回退方案?一个新的 PPU 起始时的已完成元组为空。在发布第一个完整的定时帧之前,既有的快照路径仍然是一种确定性的兼容机制。它还包含了此前已测试过的逻辑对及镜像边界。

控制流

ppu_background_viewport_to_opaque_mask(ppu)
                     |
                     v
         completed length == 240?
                /                               yes              no
               |                |
               v                v
      timed mask helper   existing snapshot mask
               |                |
               +------ return --+

重要不变量

  • 恰好 240 个条目时选择定时辅助函数
  • 定时分支会立即返回
  • 旧的源掩码构建和全帧合成不会同时执行
  • 空的、部分的以及超量的元组都会选择旧的回退方案
  • 回退方案仍会读取 ppu.temp_vram_addr 和 ppu.fine_x
  • 帧缓冲区路径和 sprite-zero-hit 代码保持不变

常见误解

不要捕获来自定时辅助函数的错误并悄悄重试回退方案。该门槛条件处理的是预期的数据可用性。一旦完整帧选择了定时辅助函数,出现异常就代表某个不变量被破坏,应当保持可观察。

实际会有什么变化?控制台已经在精灵/背景合成中使用这一支持视口的掩码。RGB 与视觉上的精灵优先级现在可以使用完全相同的定时坐标,从而修正诸如蘑菇错误地出现在不透明图块前方之类的情况。独立的 sprite-zero-hit 辅助函数仍使用其较旧的掩码依赖,将在后续课程中进行对齐。

不在本步骤范围内

  • 改变定时行的合成方式
  • 改变帧缓冲区门槛条件
  • 改变 sprite-zero-hit 对掩码的消费方式
  • 完整的垂直源行滚动

完整示例实现

# emulator/rendering/ppu_background_renderer.py

def ppu_background_viewport_to_opaque_mask(
    ppu: PPU,
) -> BackgroundOpaqueMask:
    # --- NEW BLOCK: USE TIMED DATA ONLY FOR A COMPLETE FRAME ---
    if (
        len(ppu.completed_scanline_scroll_states)
        == NAMETABLE_PIXEL_HEIGHT
    ):
        return _timed_scanlines_to_opaque_mask(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,
    )
    ...

运行本课

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