322. PPU 精灵零命中位置

从当前 PPU 状态中提取精灵 0 重叠位置。

322 / 356 · tests/chapter_11_sprite_zero_hit/test_322_ppu_sprite_zero_hit_position.py

需要更新的文件

emulator/rendering/sprite_zero_hit.py

该步骤存在的原因

现有的纯辅助函数需要显式传入渲染数据

find_sprite_zero_hit_position(
    sprite_zero,
    pattern_table,
    background_opaque_mask,
)

该 API 对于聚焦的测试很有用,但未来的 Console 步骤是从一个 PPU 开始的。本步骤新增一个小型适配器,从当前 PPU 状态中提取所需数据,并委托给现有的纯辅助函数。

在当前项目规模下,将这两个函数都保留在 sprite_zero_hit.py 中是清晰合理的:

find_sprite_zero_hit_position(...)
    explicit data -> overlap position

ppu_sprite_zero_hit_position(ppu)
    PPU state -> explicit data -> overlap position

第二个函数是一个适配器,而不是另一种重叠算法。

所需的提取内容

  • 只解码 OAM 条目 0
  • 通过 ppu_background_to_opaque_mask(ppu) 构建背景不透明度掩码
  • 使用 PPUCTRL 第 3 位选择精灵图案表
  • 通过 PpuBus 读取 PATTERN_TABLE_SIZE 字节
  • 委托给 find_sprite_zero_hit_position(...)

建议的实现示例

from emulator.ppu.chr_decoder import PATTERN_TABLE_SIZE, decode_chr_tile
from emulator.ppu.ppu import CTRL_SPRITE_PATTERN_TABLE, PPU
from emulator.rendering.ppu_background_renderer import (
    PATTERN_TABLE_0_ADDR,
    PATTERN_TABLE_1_ADDR,
    ppu_background_to_opaque_mask,
)
from emulator.rendering.sprite_renderer import (
    SpriteEntry,
    decode_sprite_attributes,
    decode_sprite_entry,
)

...

def ppu_sprite_zero_hit_position(
    ppu: PPU,
) -> SpriteZeroHitPosition | None:
    sprite_zero = decode_sprite_entry(
        oam=ppu.oam,
        sprite_index=0,
    )

    background_opaque_mask = ppu_background_to_opaque_mask(ppu)

    sprite_pattern_table_base = (
        PATTERN_TABLE_1_ADDR
        if ppu.ctrl & CTRL_SPRITE_PATTERN_TABLE
        else PATTERN_TABLE_0_ADDR
    )

    sprite_pattern_table = bytes(
        ppu.ppu_bus.read(sprite_pattern_table_base + offset)
        for offset in range(PATTERN_TABLE_SIZE)
    )

    return find_sprite_zero_hit_position(
        sprite_zero=sprite_zero,
        pattern_table=sprite_pattern_table,
        background_opaque_mask=background_opaque_mask,
    )

重要区别

背景不透明度使用基于 PPUCTRL 第 4 位的背景图案表选择路径。精灵 0 的 CHR 数据则使用 PPUCTRL 第 3 位。两者的选择可能指向不同的图案表。

重要边界

该函数仅返回一个位置,不设置 PPUSTATUS,也不调用 PPU.set_sprite_zero_hit_position()。Console 接线仍是之后的步骤。

不在本步骤范围内

  • Console 接线
  • 自动调度
  • PPUMASK 渲染使能规则
  • 左边缘裁剪规则
  • x=255 硬件例外
  • OAM Y+1 修正
  • 《超级马里奥兄弟》验证

运行本课

uv run pytest tests/chapter_11_sprite_zero_hit/test_322_ppu_sprite_zero_hit_position.py -v