323. Console планирует sprite zero hit на каждый кадр

Автоматически планировать sprite 0 hit при продвижении Console на кадр вперёд.

Урок 323 из 356 · tests/chapter_11_sprite_zero_hit/test_323_console_schedules_sprite_zero_hit_per_frame.py

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

emulator/console.py

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

Предыдущие шаги предоставляют все необходимые механизмы

ppu_sprite_zero_hit_position(ppu)
    -> extracts current PPU state and finds the overlap position

ppu.set_sprite_zero_hit_position(position)
    -> stores the future timing event

ppu.step(...)
    -> sets PPUSTATUS bit 6 when timing reaches that position

Этот шаг соединяет их с циклом кадра.

Архитектурное решение

Мы намеренно помещаем подготовку sprite zero hit внутрь Console.step_until_next_frame(). Вызывающий код, такой как main.py, должен просить Console продвинуться на один полный эмулируемый кадр, не зная, какие внутренние события тайминга PPU нужно подготовить заранее.

Это позволяет main.py оставаться сосредоточенным на обязанностях фронтенда:

input
display
FPS reporting
frame pacing

Console владеет координацией эмулятора на уровне кадра.

Предлагаемое изменение реализации

# --- NEW LINE ---
from emulator.rendering.sprite_zero_hit import ppu_sprite_zero_hit_position
# --- END NEW LINE ---


def step_until_next_frame(
    self,
    max_cpu_instructions: int | None = None,
) -> int:
    # --- NEW BLOCK ---
    position = ppu_sprite_zero_hit_position(self.ppu)
    self.ppu.set_sprite_zero_hit_position(position)
    # --- END NEW BLOCK ---

    start_frame = self.ppu.frame
    executed = 0

    while self.ppu.frame == start_frame:
        ...

Почему до цикла пошагового выполнения? CPU может опрашивать PPUSTATUS во время эмуляции кадра. Будущее срабатывание должно быть запланировано до того, как выполнение CPU/PPU достигнет перекрывающегося пикселя.

Контрольная точка ручной проверки совместимости

После этого шага студенты могут временно изменить локальный путь к ручному ROM в main.py на собственную легальную копию:

Предлагаемое изменение реализации в main.py:

ROM_PATH = Path("Super Mario Bros.nes")

Затем запустить с помощью PyPy

Linux/macOS

sh launcher.sh

Командная строка Windows

launcher.cmd

Ожидаемое улучшение при ручной проверке

Super Mario Bros. использует sprite 0 hit как сигнал тайминга PPU. Теперь, когда срабатывание обнаруживается, планируется и предоставляется через PPUSTATUS, титульный экран/меню должны продвигаться дальше, Марио должен появиться, а ввод с контроллера должен стать рабочим.

Известное оставшееся ограничение

Когда Марио движется по горизонтали, сцена может по-прежнему выглядеть некорректно, поскольку текущий рендерер фона ещё не применяет состояние прокрутки PPU для выбора и смещения видимой области nametable. Sprite 0 hit включает путь тайминга игры; он не реализует горизонтальную прокрутку фона.

Правило легальности/тестирования

Super Mario Bros.nes — это только ручной эксперимент совместимости, предоставляемый пользователем. Не коммитьте ROM и не требуйте его в автоматических тестах. Эти тесты используют поддельные объекты CPU/PPU и проверяют только поведение координации.

Вне рамок

  • горизонтальная/вертикальная прокрутка
  • тонкая прокрутка по X
  • композиция соседних nametable
  • точное поведение OAM Y+1
  • правила левого края PPUMASK
  • исключение sprite 0 hit при x=255
  • фикстуры коммерческих ROM

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

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