322. Позиция sprite zero hit в PPU

Извлечь позицию перекрытия спрайта 0 из текущего состояния PPU.

Урок 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)
  • выбрать таблицу шаблонов спрайта, используя бит 3 PPUCTRL
  • прочитать PATTERN_TABLE_SIZE байт через PpuBus
  • делегировать выполнение 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,
    )

Важное различие

Непрозрачность фона использует путь выбора таблицы шаблонов фона, основанный на бите 4 PPUCTRL. Данные CHR спрайта 0 используют бит 3 PPUCTRL. Эти два выбора могут указывать на разные таблицы шаблонов.

Важная граница

Эта функция только возвращает позицию. Она не устанавливает PPUSTATUS и не вызывает PPU.set_sprite_zero_hit_position(). Подключение в Console остаётся более поздним шагом.

Вне рамок

  • подключение в Console
  • автоматическое планирование
  • правила включения рендеринга PPUMASK
  • правила отсечения левого края
  • аппаратное исключение x=255
  • коррекция OAM Y+1
  • проверка на Super Mario Bros.

Запустить этот урок

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