Документация

Как устроен NES Player

Агент учится играть в NES-игры, получая на вход только то же, что человек: картинку и звук. На выходе — нажатия геймпада. Цель проекта не в том, чтобы пройти конкретную игру, а в том, чтобы навыки переносились на игры, которых агент не видел.

Полное техническое задание, журнал экспериментов со всеми таблицами и описание каждого модуля лежат в репозитории, в папке docs/.

Принципы

Никакого чтения памяти

Политика не имеет доступа к памяти эмулятора. Всё, чем она пользуется, извлекается из пикселей и звука. Память открыта только обучающему контуру — как эталон, чтобы проверять правильность того, что агент вычитал с экрана.

Звук — полноценный источник

Удар, урон, подбор предмета, смена сцены часто слышны раньше, чем видны, а иногда происходят вне экрана. Замер показал: добавление звука повышает точность предсказания действий на 7 п.п. на всех четырёх проверенных сидах.

Переносимость важнее рекорда

Мы учим не спрайты конкретной игры, а понятия: управляемый объект, угроза, поверхность, снаряд, прогресс. Главная метрика — насколько меньше опыта нужно на новую игру.

Как это работает

Эмулятор (headless)
 ├─ кадр 240×224
 ├─ звук 32040 Гц
 └─ история нажатий
        ↓
Восприятие без нейросети
 ├─ трекер: скролл камеры, движущиеся объекты, кто управляем
 ├─ память объектов: опасен / полезен / неизвестен
 ├─ слух: события по спектру, кластеры звуков
 └─ чтение экрана: цифры счётчиков, подсказки меню
        ↓
Политика (свёрточная сеть, видео + звук)
        ↓
Модель мира → планер
        ↓
Геймпад

Восприятие

Объекты по движению

Кадры сравниваются с поправкой на скролл камеры, движущиеся пятна собираются в объекты с устойчивыми номерами и скоростями. Отдельная ветка ловит мелочь вроде пуль: она проходит, только если имеет собственную скорость относительно фона — иначе мерцающие звёзды на фоне засоряют картину.

Кто из объектов — я

Управляемый объект определяется корреляцией его мировой скорости с нажатиями. Тонкость: когда камера едет за героем, его экранная скорость около нуля, и без поправки на скролл агент не узнаёт сам себя.

Смысл объектов и звуков

Столкновения и их последствия накапливаются: объект, после касания которого терялась жизнь, получает метку опасного. Звуки кластеризуются и связываются с событиями — например, джингл смерти звучит за две секунды до того, как счётчик жизней в памяти изменится.

Чтение экрана

Цифры агент выучивает без разметки. Идея: у младшего разряда любого счётчика переходы образуют кольцо из десяти состояний — 0→1→…→9→0. Кольцо находится само, остаётся понять, где в нём ноль.

Якорь берётся по форме: единица — самый тонкий глиф любого шрифта, а ноль отличается от двойки тем, что у него внутри замкнутая пустота. Топология оказалась надёжнее статистики: в шрифте Castlevania двойка жирнее нуля, и выбор по толщине промахивался.

Проверка против памяти эмулятора: таймер читается с совпадением 95.8% и корреляцией 1.000. Буквы даются алфавитным приором — человек тоже приходит в игру, уже умея читать; этого хватает, чтобы понять «PRESS START» и нажать то, что просит экран.

Обучение

Откуда берутся данные

Инстинктивная политика играет сама, без обучения: калибрует управление прямо в игре, ищет прогресс, прыгает при застревании, интересуется незнакомыми объектами. Её прогоны headless идут в тысячу кадров в секунду, так что сотни эпизодов собираются за минуты. Дальше на них обучается сеть.

Клонирование поведения

Стек из четырёх кадров и окно звука в 260 мс → свёртки → решение. Плюс подсказка вниманию: карта активаций штрафуется за взгляд мимо рамок объектов. Без неё сеть смотрит на декорации — измерено.

Самоподражание

Агент играет с элементом случайности, оставляет лучшие прогоны и доучивается на них. Награду можно брать из памяти эмулятора, а можно по пикселям: накопленный скролл камеры коррелирует с истинным прогрессом на 0.87.

Модель мира и планер

Первые версии предсказывали будущее в общем латентном пространстве и игнорировали действия: эффект нажатия — пара пикселей смещения героя — тонул в фоне и скролле. Рабочей оказалась эго-центричная модель: кроп вокруг управляемого объекта, его скорость и действие → смещение через шаг.

Планер перебирает шаблоны поведения на 16 шагов вперёд и выбирает лучший по предсказанию. Важный урок: на слабой модели планирование вредило — план систематически проигрывал простой реакции. Выиграл только когда качество модели выросло.

Результаты

Первое знакомство с игрой

На Castlevania, которой не было ни в одном обучении: инстинкты набирают 700 очков, случайный агент и перенесённая нейросеть — ноль. Очки прочитаны с экрана, сверены с памятью.

Перенос

Few-shot по трём эпизодам на невиданных играх: Gradius 0.707 → 0.787, Battletoads 0.594 → 0.671. На Balloon Fight перенос вредит: воздушная физика чужда базе.

Обученные игры

Balloon Fight 0.944, Contra 0.943, Gradius 0.872, Double Dragon 0.864, Super Mario Bros. 0.708, Battletoads 0.702. Мультиигровая база — 0.944.

Ядра эмуляции

Ядро переключается настройкой; кадр и звук приводятся к единому виду, поэтому модели не требуют переобучения при смене.

  • fceumm — по умолчанию; все модели обучены на нём;
  • nestopia, quicknes — работают; на части чужих записей quicknes держит синхронизацию вдвое дольше;
  • mesen — не поддержан, требует сервисов фронтенда;
  • FCEUX — непригоден: сборка падает на трёхсотом кадре воспроизведения.

Железо и платформы

Всё работает на обычном ноутбуке: эмуляция на процессоре, нейросети — на том ускорителе, который найдётся. Обучение модели — минуты, не часы. Вся модель занимает 16.5 МБ, причём 89% веса приходится на последний полносвязный слой, а на зрение — всего 2%.

  • macOS, Apple Silicon — основная площадка разработки, PyTorch через Metal; все цифры на сайте получены здесь;
  • Linux x86_64 — ставится теми же командами, готовые сборки эмулятора есть; ускорение через CUDA, если карта есть, иначе процессор;
  • Windows — через WSL2: своей сборки эмулятора под Windows у stable-retro нет.

Ничего мак-специфичного в коде нет: выбор устройства (CUDA → Metal → процессор) и загрузка ядер эмуляции определяются платформой автоматически. Видеокарта не обязательна — на процессоре всё то же самое, только медленнее.

Живое окно держит 60 кадров в секунду с чистым звуком: игра, восприятие и отрисовка в одном потоке, нейросеть в другом, звук в третьем. Иначе пики вычислений рвут аудио.