NES TDD

287. Console: шаг до следующего кадра

Добавить Console.step_until_next_frame() для пошагового выполнения на уровне кадров.

Урок 287 из 356 · tests/chapter_05_rendering_pipeline/test_287_console_step_until_next_frame.py

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

emulator/console.py

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

У Console уже есть метод пошагового выполнения на одну инструкцию

console.step()

Это наименьшая операция машинного времени в этом эмуляторе. Она выполняет одну инструкцию CPU, продвигает PPU на количество циклов CPU * 3, а затем обрабатывает любой отложенный NMI.

Ручным исполнителям и будущим фронтендам обычно нужна более крупная операция

run emulation until one full PPU frame completes
then ask for a framebuffer explicitly

На этом шаге добавляется

console.step_until_next_frame(max_cpu_instructions: int | None = None) -> int

Пример реализации

def step_until_next_frame(
    self,
    max_cpu_instructions: int | None = None,
) -> int:
    start_frame = self.ppu.frame
    executed = 0

    while self.ppu.frame == start_frame:
        if max_cpu_instructions is not None:
            if executed >= max_cpu_instructions:
                raise RuntimeError("Frame did not complete before instruction limit")

        self.step()
        executed += 1

    return executed

Разница между step() и step_until_next_frame():

step()
    executes exactly one CPU instruction
    advances PPU by that instruction's cycles * 3
    returns CPU cycles for that instruction

step_until_next_frame()
    calls step() repeatedly until ppu.frame changes
    returns how many CPU instructions were executed

Пример использования

console.step_until_next_frame()
framebuffer = console.render_background_framebuffer()

Почему max_cpu_instructions необязателен: этот параметр не относится к поведению железа NES. Это защитный механизм эмулятора для отладки/тестирования.

При значении None искусственного ограничения по количеству инструкций нет. Это полезно для реального или ручного выполнения:

console.step_until_next_frame()

При целочисленном значении помощник выбрасывает исключение, если указанное количество инструкций CPU выполнилось без появления нового кадра. Это полезно для тестов и отладки, поскольку предотвращает бесконечные циклы, если CPU завис, опкод отсутствует или кадр никогда не завершается:

console.step_until_next_frame(max_cpu_instructions=10)

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

step_until_next_frame()
    advances emulation time

render_background_framebuffer()
    observes current PPU memory and returns Framebuffer data

Не выполняйте рендеринг автоматически внутри step_until_next_frame().

Вне рамок этого шага

  • отображение pygame
  • спрайты
  • OAMDMA
  • точная задержка NMI
  • динамические штрафы циклов CPU
  • ввод с контроллера

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

uv run pytest tests/chapter_05_rendering_pipeline/test_287_console_step_until_next_frame.py -v