353. Sprite zero hit использует viewport-маску

Использовать viewport-зависимую непрозрачность фона для обнаружения перекрытия sprite-zero-hit.

Урок 353 из 356 · tests/chapter_13_scrolling/test_353_sprite_zero_hit_uses_viewport_mask.py

Файл для обновления

emulator/rendering/sprite_zero_hit.py

Ссылки

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

Зачем нужен этот шаг

Sprite zero hit — это сигнал статуса PPU, генерируемый, когда непрозрачный пиксель спрайта из записи OAM 0 перекрывает непрозрачный пиксель фона. Игры могут опрашивать этот сигнал для синхронизации изменения регистра в середине кадра, например для разделения фиксированной области статуса и прокручиваемого геймплея.

Хелпер перекрытия индексирует непрозрачность фона по экранным координатам

mask_index = screen_y * 256 + screen_x

Следовательно, эта маска должна описывать фон, фактически видимый в тех же координатах. После тайминговой прокрутки экранный X может отображаться на другой столбец coarse-X или логический nametable на каждой строке. Старая фиксированная маска $2000 может проверять другой пиксель фона, хотя спрайт 0 остаётся на той же экранной позиции.

Пример

sprite 0 screen X:       100
scanline viewport X:      40
visible background X:    140

fixed mask inspects X:   100
viewport mask inspects X: 140

Если исходный X 140 непрозрачен, а исходный X 100 прозрачен, фиксированная маска пропускает перекрытие. Viewport-зависимый адаптер выбирает тайминговую композицию для полного кадра и сохраняет ранее протестированный запасной снимок, когда тайминговые данные недоступны.

Зачем использовать псевдоним импорта? Исторические тесты sprite-zero-hit используют monkeypatch для локального имени модуля ppu_background_to_opaque_mask. Замена каждого вызова на новое локальное имя нарушила бы этот устоявшийся тестовый шов. Импорт viewport-зависимого адаптера под историческим именем меняет продакшен-поведение, сохраняя старые тесты без изменений:

new viewport-aware function as old module-local name

Важные инварианты

  • sprite-zero-hit потребляет viewport-зависимый адаптер непрозрачности
  • историческое локальное имя зависимости остаётся доступным для monkeypatch
  • выбранная маска передаётся в чистый хелпер перекрытия без изменений
  • выбор таблицы паттернов CHR для спрайтов остаётся независимым от маскирования фона
  • код отрисовки не устанавливает PPUSTATUS напрямую
  • тайминг Console и PPU продолжает планировать и устанавливать попадание

Распространённое заблуждение

Sprite zero hit — это не обнаружение игровых столкновений и не означает, что Марио столкнулся с врагом или блоком. Это перекрытие при отрисовке, затрагивающее только запись спрайта 0 и непрозрачный пиксель паттерна фона, обычно используемое программно как сигнал синхронизации.

Стратегия тестирования

Один тест проверяет идентичность продакшен-зависимости, чтобы старый фиксированный производитель не мог незаметно вернуться. Другой проводит полный тайминг-кадр через реальный viewport-адаптер, заменяя только его ресурсоёмкий композитор строк, и затем проверяет, что результирующая маска доходит до существующей границы чистого перекрытия без изменений.

За рамками этого шага

  • изменение чистого поиска перекрытия
  • изменение выбора таблицы паттернов спрайтов
  • изменение планирования кадров Console
  • точное аппаратное поведение на левом краю, X=255 и OAM Y+1
  • полная вертикальная прокрутка исходной строки

Полный пример реализации

# emulator/rendering/sprite_zero_hit.py

# --- UPDATED BLOCK: VIEWPORT MASK WITH HISTORICAL LOCAL NAME ---
from emulator.rendering.ppu_background_renderer import (
    PATTERN_TABLE_0_ADDR,
    PATTERN_TABLE_1_ADDR,
    ppu_background_viewport_to_opaque_mask as ppu_background_to_opaque_mask,
)

...

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

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