294. Ручной цикл загрузки ROM в core_validator

Добавление ручного цикла загрузки ROM core_validator.py.

Урок 294 из 356 · tests/chapter_08_manual_main/test_294_manual_core_validator_rom_loop.py

Файл для создания в корневой папке

core_validator.py

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

Теперь у эмулятора достаточно поведения для выживания при запуске, чтобы сделать полезной ручную точку входа для разработчика:

iNES parsing
Mapper000/NROM
CPU reset vector
Console frame stepping
APU/audio no-op for out-of-scope audio
OAMDMA $4014
controller port 1 through $4016

Этот шаг делает core_validator.py ручным местом для проверки локального ROM и поддержания работы эмулятора покадрово без pygame.

Почему core_validator.py, а не main.py? main.py зарезервирован для визуального запускателя на pygame. core_validator.py остаётся небольшим невизуальным запускателем, полезным при отладке ядра эмулятора без окна или цикла событий фронтенда.

Важное юридическое правило/правило для тестов

Репозиторий учебного курса не должен включать коммерческие файлы ROM. Автоматические тесты не должны требовать MarioBros.nes. Разработчик, желающий вручную запустить Mario Bros., должен предоставить собственную легальную локальную копию:

MarioBros.nes

Эталонный хеш, использованный при разработке курса [Mario Bros. (World).nes]:

MD5 5d7bcc400a2fb5fa27346da345d3bb62  MarioBros.nes
SHA1 314b6e46e814f955b52ac954f67dab849582fe77

Этот хеш является лишь ручным ориентиром. Тесты не должны требовать этот файл или этот точный хеш, поскольку у пользователей могут быть другие легальные дампы/ревизии.

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

from pathlib import Path

from emulator.bus.cpu_bus import CpuBus
from emulator.cartridge.cartridge import Cartridge
from emulator.console import Console
from emulator.cpu.cpu import CPU


ROM_PATH = Path("MarioBros.nes")
debug_mode = True


def main() -> None:
    if not ROM_PATH.exists():
        raise FileNotFoundError(
            "MarioBros.nes not found. Provide your own legal local copy. "
            "This file is intentionally not included in the tutorial repository."
        )

    cartridge = Cartridge.from_ines_bytes(ROM_PATH.read_bytes())

    cpu_bus = CpuBus(cartridge=cartridge)
    cpu = CPU(cpu_bus)
    console = Console(cpu=cpu, ppu=cpu_bus.ppu)

    cpu.reset()

    print(f"Loaded {ROM_PATH}")
    print(f"CPU reset PC = ${cpu.pc:04X}")
    print("Starting frame loop. Press Ctrl+C to stop.")

    try:
        while True:
            executed = console.step_until_next_frame()

            if debug_mode:
                print(
                    f"frame={console.ppu.frame} "
                    f"pc=${cpu.pc:04X} "
                    f"instructions={executed}"
                )
    except KeyboardInterrupt:
        print("

Stopped by user.")

if __name__ == "__main__":
    main()

Ручная команда

uv run python core_validator.py

Ожидаемое ручное поведение

Эта команда не завершается сама по себе. Она продолжает выполнять кадры, пока разработчик не нажмёт Ctrl+C. Это ожидаемо, поскольку core_validator.py — это ручной, длительно работающий инструмент выполнения ROM, а не автоматический тест.

Почему этот тест не вызывает main()

core_validator.py — это ручной бесконечный цикл. Автоматические тесты должны оставаться конечными и не должны требовать локальный коммерческий файл ROM. Эти тесты проверяют только структуру.

Почему debug_mode проверяется только на существование: debug_mode — это ручной регулятор управления. Будущие шаги курса могут устанавливать его в True или False в зависимости от того, что изучается. Тесты не должны фиксировать это значение.

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

  • отображение pygame
  • сопоставление клавиш
  • закоммиченные ROM-файлы для тестов
  • проверка того, что core_validator.py даёт корректный игровой процесс
  • вызов main() из pytest

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

uv run pytest tests/chapter_08_manual_main/test_294_manual_core_validator_rom_loop.py -v