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