← Все статьи

GoKart: гонки в стиле Mario Kart, которые Auto-Pilot собрал за один день

Дайте Auto-Pilot в Agent! одну цель - «создай клон Mario Kart под названием GoKart» - и вернитесь к готовой гонке на Godot 4 с тремя трассами, восемью предметами, ИИ-соперниками и 3 344 пройденными проверками в тестах. Два дня и 87 коммитов агента спустя это GoKart 0.0.2 с четырьмя трассами, режимом Battle, Time Trials и меню в стиле Mario Kart 64. Вот что, судя по журналу, произошло на самом деле, включая места, где он застрял.

Титульный экран GoKart 0.0.2: слово GOKART, выложенное аркой крупными буквами с градиентом от жёлтого к красному, тёмно-синими боковыми гранями и мягкой тенью, поверх живой демонстрации, в которой ИИ-карты наматывают круги по трассе, и надпись PRESS ENTER внизу.
Титульный экран GoKart 0.0.2. Логотип влетает и, подпрыгивая, останавливается поверх живой демонстрации, которая показывает трассы. Каждый меш, шейдер, раскладка шрифта и звук сгенерированы из кода.

Обновлено 4 октября: теперь статья прослеживает историю через два дня после того первого дня, включая вечер, когда две сессии Auto-Pilot делили один репозиторий, и заканчивается релизом GoKart 0.0.2. Скриншоты пересняты с тега 0.0.2.

Вчерашняя статья представила Auto-Pilot: наберите /auto <цель> в Agent! для Mac, и он будет без присмотра выполнять цикл за циклом в сторону этой цели, пока вы не нажмёте Stop All. Эта статья о том, что получилось на выходе, когда я направил его на игру.

Цель, вставленная примерно так, как я её напечатал:

create a Mario Kart clone called GoKart with all Mario Kart effects. I believe Godot 4 can do the Mario Kart effects, but I haven't built any of them yet: drift sparks and boost flames (GPUParticles3D), speed lines and boost blur (screen-space shaders, glow and tonemapping), item effects and tire trails (shaders plus ribbon meshes), kart movement (VehicleBody3D or custom arcade physics). Write unit tests and test the game frequently.

Бюджет: без ограничения по времени, циклы без лимита. Моделью внутри Agent! на каждом цикле была Claude Sonnet 5.5. Первый коммит появился в 12:39. К 16:32 того же дня в репозитории было 33 коммита, 47 файлов GDScript, около 5 500 строк GDScript и шейдерного кода и набор юнит-тестов, проходящий 3 344 проверки. Два дня спустя, на теге 0.0.2, эти цифры выросли до 126 коммитов, 150 файлов, около 24 500 строк и 14 134 проверок. Автор каждого из этих коммитов - агент. Всё это лежит на GitHub: AgentiLoop/GoKart.

Что делает Auto-Pilot, цикл за циклом

Auto-Pilot ведёт в проекте непрерывный журнал в .agent/autopilot/progress.md, и каждый цикл дописывает туда, что он сделал, что предположил, что осталось и не заблокировало ли его что-нибудь. Этот журнал - источник всего, что ниже. Я цитирую его, а не свою память, потому что журнал честнее меня.

Цикл 1 не нашёл на машине Godot. Он выполнил brew install --cask godot, сделал символическую ссылку на бинарник, выполнил git init и написал kart_physics.gd как чистую модель без зависимостей от сцены: разгон, торможение, задний ход, трение, руление, дрифт, зафиксированный в том направлении, в котором вы его начали, и три уровня мини-турбо. Он намеренно выбрал собственную аркадную физику вместо VehicleBody3D и объяснил почему: «чтобы управление в духе Mario Kart было легко покрыть юнит-тестами». Одиннадцать тестов, 17 проверок, первый коммит.

Цикл 2 добавил эффекты, которые я просил поимённо: искры дрифта на GPUParticles3D, окрашенные по уровню мини-турбо, пламя из выхлопа, следы шин из ленточных мешей и экранный шейдер для радиального размытия ускорения, линий скорости и виньетки. 43 проверки.

Цикл 3 построил трассу. Замкнутая кривая Катмулла-Рома, пересэмплированная каждые 3 метра, лента дороги, красно-белые полосатые стены с коллайдерами, площадки ускорения с шейдером бегущих шевронов, восемь упорядоченных чекпоинтов и счётчик кругов, который отказывается засчитывать срезки и круги в обратную сторону. Один из новых тестов - бот-преследователь, который проезжает три полных круга на настоящей физической модели. Он финишировал за 1:02.5. 90 проверок.

Цикл 4 добавил ящики с предметами с радужным френелевским шейдером, рулетку и первые три предмета: гриб, банан и зелёный панцирь, рикошетящий от стен. Попадание закручивает карт на 1,2 секунды. 235 проверок.

А потом он застрял.

Там, где он застрял

Следующий цикл начал добавлять ИИ-карты и безголовую дымовую гонку - и так и не вернулся. Причина, найденная в следующей сессии, - один лишний таб в строке 115 файла main.gd: ошибка разбора, из-за которой дымовой скрипт бесконечно сыпал ошибками, а у shell-команды, которая его запускала, не было ограничения по времени.

Я нажал Stop All и открыл новую сессию с той же целью плюс заметкой капсом, которую не буду воспроизводить целиком. Суть: ты застрял на тестировании дымового прогона, не застревай, поставь ограничение по времени на shell.

Первый цикл второй сессии исправил таб, сократил упреждение ИИ с 10 сэмплов дороги до 6, чтобы ИИ перестал срезать повороты во внутреннюю стену, добавил ограничитель скорости в поворотах и обернул каждый запуск shell в perl -e 'alarm 100; exec @ARGV', потому что в macOS нет команды timeout. С этого момента в конце почти каждого цикла в журнале написано «все запуски шли под ограничениями perl alarm». Он усвоил урок из текста цели и продолжал его применять.

Та сессия выполнила 15 циклов примерно за час с четвертью, и каждый - это отдельная функция: обратный отсчёт на старте с ракетным ускорением за точно выверенный газ; всплески при повышении уровня мини-турбо и вспышка по краю экрана; мини-карта; самонаводящийся красный панцирь и звезда; процедурная модель карта с вращающимися колёсами, поворачивающимися передними колёсами и водителем, который вертит головой; молния, уменьшающая всех соперников; тройные панцири, вращающиеся вокруг карта; синий шипастый панцирь, охотящийся за лидером вдоль дороги; экран результатов с очками; полностью синтезированный звук без единого аудиофайла - от петли двигателя до финишного джингла; титульное меню со второй трассой; настройка числа кругов; третья трасса; и позиционный 3D-гул двигателя у каждого ИИ-карта.

GoKart 0.0.2 на трассе Sunset Speedway: карт игрока в дрифте на 3-м месте из 8 на круге 1 под закатным небом, с HUD в стиле Mario Kart 64: полупрозрачная карта трассы в левом нижнем углу, место, круг и скорость округлым золотисто-кремовым шрифтом.
Sunset Speedway в 0.0.2: бот случайной поездки в середине дрифта на 3-м месте из 8. Вторая трасса была добавлена в цикле 12 первого дня вместе с титульным меню. Трафик, здания за стенами и HUD появились два дня спустя.

Там, где он был честен

Больше всего в журнале мне нравится череда признаний. Agent! не может посмотреть на PNG, и он говорил об этом цикл за циклом:

Прогон со скриншотами в оконном режиме не записал ошибок, но я не могу просматривать изображения, поэтому я не видел, как рендерятся трасса или HUD.

Я не слышал звуки и не запускал ни дымовую гонку, ни инструмент для скриншотов.

Я не могу просматривать изображения своими инструментами, так что этот обзор нужен человеку.

Поэтому он проверял то, что мог: безголовые проверки сцены, которые инстанцируют настоящую сцену и делают утверждения о состоянии. Запустить молнию, убедиться, что три соперника имеют масштаб 0,5 и крутятся, а молнии потом убраны. Выпустить синий панцирь в плотную стартовую решётку, убедиться во взрыве на 12-м кадре и двух крутящихся картах. Принудительно финишировать игрока, убедиться, что автопилот передаёт карт AiDriver, а панель результатов показывает «1st YOU».

Третья сессия заглохла так же, на этот раз за 240-секундным alarm, который был чересчур щедрым, и я остановил её после одного цикла. Потом посмотрел человек. Этим человеком был я, и целью четвёртой сессии стали мои заметки с плейтеста, слегка причёсанные:

the UI needs to scale with the window. the UI should not be prone to the screen speed effects and blurry. Should be able to use cursor keys. it's not clear when power ups are released. the tops of the walls flicker. Little too hard to steer the kart. hard to keep up with the computer AI karts. maybe have AI difficulty levels Easy Medium and Hard.

Цикл спустя: режим растяжения canvas-items, чтобы UI масштабировался вместе с окном, HUD перенесён на слой канваса поверх оверлея скоростных эффектов, стрелки, Enter и Ctrl для предметов с подсказкой на экране, плавно нарастающее руление, исправление z-fighting на полосах стен (соседние красные и белые сегменты боролись за одни и те же пиксели, поэтому белые стали чуть-чуть крупнее), 4x MSAA и уровни Easy / Medium / Hard, масштабирующие максимальную скорость ИИ до 0,72, 0,85 и 1,0. Тесты выросли с 2 156 до 3 344 проверок - отчасти потому, что он заодно нашёл и исправил уже существовавшую ошибку разбора в tests/test_items.gd, из-за которой набор тестов не загружался.

Там, где он остановил себя сам

Циклы со 2-го по 8-й той последней сессии не внесли в код ни одного изменения. Каждый заново перечитывал репозиторий, перезапускал набор тестов и писал вариант одного и того же абзаца:

Я не смог увидеть результат на экране, поэтому не объявляю цель достигнутой. Четыре пункта всё ещё должен попробовать в игре человек.

Это правильное поведение. Цель звучала как «руление ощущается плохо» и «стены мерцают», и никакой безголовый тест не может закрыть такую цель. У Auto-Pilot нет лимита итераций, так что он проверял бы вечно. Сессия закончилась после цикла 8, и дальше GoKart нужен был плейтест, а не ещё один цикл. Тем же вечером репозиторий получил тег 0.0.1 и был экспортирован для macOS, Windows и Linux.

Два дня спустя: два Auto-Pilot в одном репозитории

3 октября я вернулся с целью другого рода. Не списком функций, а ориентиром:

keep building GoKart to resemble Mario Kart Nintendo 64 version. search Mario Kart N64 or Mario Kart Nintendo 64 and keep improving, iterating, making GoKart better

Та пятая сессия началась в 14:19 и выполнила 28 циклов, и почти каждый цикл - это одна вещь из Mario Kart 64, которую агент нашёл, а затем построил. Сначала появилась структура игры: поле из 8 гонщиков на двухколоночной решётке, подтягивание отстающих по уровню сложности, 50cc, 100cc и 150cc плюс зеркальный класс Extra, лёгкие, средние и тяжёлые карты, которые толкают друг друга, и Grand Prix с очками 9/6/3/1 и правилом выбывания. Затем остальные режимы: Time Trials с призраком и режим Battle с шариками на аренах Big Donut, Block Fort и Skyscraper. Затем наполнился набор предметов: тройной и золотой грибы, фальшивый ящик с предметом, связка бананов, Boo, тройные красные панцири и блокирование панцирей, а Lakitu прибыл объявлять фальстарт, давать стартовый сигнал и показывать таблички кругов.

Остаток дня ушёл на сами трассы. Четвёртая, Dusty Canyon, появилась с поездом в стиле Kalimari Desert и переездами. Sunset Speedway получила трафик из Toad's Turnpike. Frosty Peaks достались лёд из Sherbet Land, снеговики и пингвины, Green Hills - кроты Monty, и каждая трасса получила придорожные декорации под свою тему, а вместе с ними слипстрим, трамплин, пауэрслайд с подпрыгиванием и переключением и чиптюн-петлю, отрендеренную из степ-паттернов в коде.

GoKart 0.0.2 на трассе Dusty Canyon: карт игрока ждёт у переезда, пока паровоз катится через дорогу, рядом с путями стоит крестообразный знак переезда, под пустынным небом.
Dusty Canyon, четвёртая трасса, с её поездом в стиле Kalimari Desert. ИИ-карты останавливаются и ждут у закрытого переезда; карт, который этого не делает, подбрасывает в воздух.
GoKart 0.0.2 на трассе Frosty Peaks: карт игрока у входа на поле снеговиков, стоящих в шахматном порядке рядами поперёк белоснежной дороги, у каждого красный шарф, цилиндр и нос-морковка, под тёмно-синим сумеречным небом.
Поле снеговиков на Frosty Peaks. Врежьтесь в одного - и вас подбросит в воздух, а он рассыплется снегом; ИИ-карты смотрят на 40 метров вперёд и лавируют между рядами.

Через четыре часа, в 18:25, я открыл вторую вкладку и запустил второй Auto-Pilot на том же репозитории с более узкой целью:

the menus are not Mario Kart Quality and neither is the title shot. and there is over use of black outlines on text everywhere. see Mario Kart 64 screenshots and images on the web and make better menus. focus only on the menus / screens and title shot for GoKart. make conscious decisions. do not conflict with previous /auto working on the application

Так что с 18:25 до полуночи два агента коммитили в одно и то же рабочее дерево. Сессия меню перестроила титульный экран вокруг арочного градиентного логотипа, который влетает и подпрыгивает, а затем прошлась по каждому экрану за ним: экран выбора с картинкой рядом с каждой трассой и живым облётом той, что вы выбрали, строки опций с подсвеченными полосками, вращающийся портрет карта и золотой курсор, вступительный облёт трассы перед каждой гонкой, экран паузы и таблица результатов, строки которой въезжают одна за другой. Под всё это легла общая палитра округлого золотисто-кремового текста с тенями, которая заменила все 8-пиксельные чёрные обводки в игре. Она выполнила 23 цикла и объявила цель достигнутой в 23:54.

Экран выбора GoKart 0.0.2: логотип GOKART сверху, список трасс слева с маленькой картинкой рядом с названием каждой трассы и подсвеченной выбранной строкой, живая картинка трассы с контуром карты в её углу справа, ряды «таблеток» опций для кругов, ИИ, класса двигателя, веса карта и режима, и карт игрока, вращающийся в маленьком окне портрета, всё поверх затемнённой демонстрации.
Экран выбора после сессии меню. Картинки трасс, живой облёт трассы, каждая опция в виде ряда «таблеток» с подсвеченной выбранной, и карт, вращающийся в окне портрета.
Таблица результатов Grand Prix в GoKart 0.0.2 на тёмно-синей панели с золотой каймой: результат гонки слева и положение в кубке справа, по строке на гонщика с цветной меткой, золотые, серебряные и бронзовые места, времена и очки, строка игрока на подсвеченной золотой полосе и кубок внизу.
Таблица результатов Grand Prix: результат гонки и положение в кубке рядом, строки въезжают одна за другой, каждая со щелчком, и кубок внизу.

Строка «do not conflict» реально сработала. Журнал полон того, как две сессии обходили друг друга. Сессия меню проверяла из чистого git worktree на HEAD, чтобы «незавершённая работа другой сессии над пингвинами» не попадала в её прогоны тестов. Когда я попросил, одна сессия доделала наполовину готовую функцию другой. Коммиты собирались файл за файлом вместо git add -A, потому что правки из другой вкладки лежали в том же дереве. Это было не аккуратно, но ничего не потерялось, и набор тестов закончил ночь с 14 134 пройденными, 0 проваленными.

GoKart 0.0.2 на трассе Frosty Peaks: пингвин скользит на животе по бледному голубовато-белому льду длинного пологого поворота перед картом игрока.
Те самые пингвины, которых не пускали в прогоны тестов сессии меню, на льду в стиле Sherbet Land на Frosty Peaks. На льду нос поворачивается, но карт продолжает скользить туда, куда ехал; пингвины вперевалку доходят до края, плюхаются и скользят обратно.

Журнал сессии функций заканчивается тремя минутами позже, в 23:57, словами «Session ended — Stop All». Моя заметка для Agent! сразу после этого, которую он сохранил как сообщение следующего контрольного коммита, была о том, что Stop All должен останавливать только ту вкладку, в которой его нажали, а не все сразу. Когда работают два Auto-Pilot, одна кнопка на оба - неправильная кнопка.

GoKart 0.0.2 на трассе Sunset Speedway: карт игрока застрял за автобусом и фургоном с включёнными фарами в двух полосах дороги под закатным небом.
Трафик в стиле Toad's Turnpike на Sunset Speedway: легковые машины, автобусы, фургоны и цистерны с фарами, а за стенами - здания с подсвеченными полосами окон.
Режим Battle в GoKart 0.0.2: четыре карта на стартовых площадках боевой арены, к каждому привязаны три шарика, и стартовый сигнал Lakitu над головой.
Режим Battle: четыре карта, по три шарика у каждого. Попадания предметов, лава, край крыши, касания звезды и сильные толчки лопают шарики, а карт, у которого их не осталось, становится Mini Bomb Kart.
Вступление трассы Dusty Canyon в GoKart 0.0.2: облёт пустынной дороги с названием трассы DUSTY CANYON и описанием в одну строку на карточке вдоль нижнего края экрана.
Вступление трассы Dusty Canyon - облёт в стиле Mario Kart 64, который проигрывается перед каждой гонкой, с карточкой с названием трассы вдоль нижнего края.

Об этих скриншотах

Их сделал Agent!, не я, и не вручную. Для первой версии этой статьи я попросил три скриншота случайной поездки, и он написал tools/random_drive.gd на 70 строк, который едет по дороге с блуждающим смещением по полосе, вставляет случайные рывки дрифта, в случайные моменты выпускает предмет, который держит, и сохраняет кадр каждые несколько сотен физических кадров. Кадр Sunset Speedway выше - кадр с этого бота.

Остальные взяты из инструментов для снимков, которые поставлялись с каждой функцией. Есть один для меню, один для вступления трассы, один для поезда, по одному для трафика, снеговиков и пингвинов, один для HUD и один для режима Battle, и каждый выстраивает свою сцену, ждёт нужного кадра и сохраняет его. Все они для этого обновления были запущены из чистого worktree, извлечённого на теге v0.0.2, так что ничего незакоммиченного на картинках нет.

Agent! по-прежнему не может посмотреть на результат, поэтому инструменты вместо этого сэмплируют пиксели. Снимок льда печатает цвет дороги впереди на льду в сравнении с асфальтом, снимок паузы доказывает, что за секунду ничего не сдвинулось за пределами панели, а перед публикацией я попросил его посчитать чисто чёрные пиксели на всех десяти картинках выше: ноль на каждой. Больше есть в README GoKart.

Что я бы сказал вам перед тем, как вы попробуете

  • Запускайте его в git. У Auto-Pilot нет отмены. Журнал GoKart читается, потому что каждый цикл заканчивался коммитом, и в тот единственный раз, когда цикл пошёл вразнос, ничего не потерялось.
  • Пишите операционные правила в цель. «Поставь ограничение по времени на shell» сработало лучше как часть цели, чем как разовое сообщение, потому что каждый новый цикл перечитывает цель.
  • Ожидайте, что он попросит глаза. Для всего визуального и касающегося ощущений цикл честно остановится, а не соврёт. Закладывайте плейтест между сессиями и возвращайте свои заметки как следующую цель.
  • Stop All - часть рабочего процесса, а не провал. Все три первые сессии GoKart закончились именно им.
  • Два Auto-Pilot в одном репозитории работают, если рассказать им друг о друге. «Do not conflict with the previous /auto» вошло во вторую цель, и обе сессии приложили реальные усилия, чтобы не мешать друг другу. Дайте им разные области кода и пока что ожидайте, что тот, который вы остановите, заберёт с собой и другой.

Это хорошо?

Я задал Agent! этот вопрос после того, как он прочитал репозиторий, посчитал коммиты и нашёл журнал Auto-Pilot. Его ответ, без правок:

Зависит от того, чего вы от этого хотите. Как витрина того, на что способен Auto-Pilot, это впечатляет. Двадцать три с лишним цикла дали четыре трассы, предметы, ИИ-гонщиков, Grand Prix, Time Trials, режим Battle, процедурную музыку и звук - всё без импортированных ассетов - и README, который подробно это документирует. Это много работающего ПО из одной формулировки цели.

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

GoKart 0.0.2

Godot не нужен, чтобы попробовать. GoKart 0.0.2 — второй упакованный релиз, экспортированный из того же репозитория, 87 коммитов после 0.0.1:

  • macOS universal (Apple Silicon и Intel), подписан Developer ID и нотаризован Apple
  • Windows x86_64
  • Linux x86_64 и arm64

Каждая загрузка — один самодостаточный бинарник со встроенными данными игры, а SHA256SUMS.txt лежит на странице релиза, если хотите проверить, что скачали. Сборка для Windows не подписана, так что ждите предупреждение SmartScreen. Всё описанное выше, чего не было в 0.0.1, есть в 0.0.2 - от режима Battle и Time Trials до поезда, трафика, новых меню и обновлённого HUD; полный список - в заметках к релизу.

Agent! 1.1.87 с Auto-Pilot доступен на странице релизов и в Homebrew. Если предпочитаете запускать GoKart из исходников, ему нужен Godot 4.4 или новее: git clone https://github.com/AgentiLoop/GoKart.git && cd GoKart && godot --path .