Как устроен 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 кадров в секунду с чистым звуком: игра, восприятие и отрисовка в одном потоке, нейросеть в другом, звук в третьем. Иначе пики вычислений рвут аудио.