322. Ppu sprite zero hit position

Extract the sprite 0 overlap position from current PPU state.

Lesson 322 of 356 · tests/chapter_11_sprite_zero_hit/test_322_ppu_sprite_zero_hit_position.py

File to update

emulator/rendering/sprite_zero_hit.py

Why this step exists

The existing pure helper requires explicit rendering data

find_sprite_zero_hit_position(
    sprite_zero,
    pattern_table,
    background_opaque_mask,
)

That API is useful for focused tests, but a future Console step starts with a PPU. This step adds a small adapter that extracts the required data from current PPU state and delegates to the existing pure helper.

Keeping both functions in sprite_zero_hit.py is clear at the current project size:

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

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

The second function is an adapter, not another overlap algorithm.

Required extraction

  • decode OAM entry 0 only
  • build the background opacity mask through ppu_background_to_opaque_mask(ppu)
  • select the sprite pattern table using PPUCTRL bit 3
  • read PATTERN_TABLE_SIZE bytes through PpuBus
  • delegate to find_sprite_zero_hit_position(...)

Suggested implementation example

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,
    )

Important distinction

Background opacity uses the background pattern-table selection path, which is based on PPUCTRL bit 4. Sprite 0 CHR data uses PPUCTRL bit 3. The two selections may point to different pattern tables.

Important boundary

This function only returns a position. It does not set PPUSTATUS and does not call PPU.set_sprite_zero_hit_position(). Console wiring remains a later step.

Out of scope

  • Console wiring
  • automatic scheduling
  • PPUMASK rendering-enable rules
  • left-edge clipping rules
  • x=255 hardware exception
  • OAM Y+1 correction
  • Super Mario Bros. validation

Run this lesson

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