Как я пишу RTS про Марс на Rust вместе с ИИ-агентами: архитектура, ошибки и 400+ тестов

от автора

Несколько месяцев назад я поставил себе довольно неудобный эксперимент. Можно ли одному человеку собрать классическую RTS со всеми своими хотелками, если вместо команды программистов использовать несколько ИИ-агентов? Не сделать ролик, не собрать красивое меню и один демонстрационный уровень, а довести проект до состояния, где есть экономика, навигация, бой, сохранения, сетевой режим, Windows- и Android-сборки, сервер и сотни автоматических проверок.

Для чистоты эксперимента я выбрал задачу, которая плохо прощает фокусы. Игровая карта здесь не прямоугольник, а Марс в масштабе 1:1. Камера должна за один непрерывный зум пройти путь от вида планеты из космоса до ног отдельного робота. Поверхность имеет настоящий крупный рельеф, а юниты должны не только красиво ходить, но ещё строить, добывать, возить ресурсы, воевать и не проваливаться в склон.

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

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

Дневной вид колонии Robosoma

Дневной вид колонии Robosoma

Текущий игровой кадр: колония уже живёт, но первый час и объяснение её систем ещё требуют работы.

Что это вообще за игра

Игра называется Robosoma, по-русски «Робосома». По жанру это смесь классической RTS, симулятора автономной колонии и немного транспортного симулятора.

Партия начинается не с готового города и не с десятка рабочих. В лоре к месту высадки приходит транспортный корабль «Стриж», а на поверхности остаётся одна универсальная машина. В нынешнем игровом контуре первые минуты ещё упрощены, но принцип уже тот же: одна робосома, небольшой стартовый запас и пустой участок Марса.

Игрок выбирает место для штаба, ставит источник энергии и склад. Потом начинается нормальная производственная цепочка:

залежь реголита или руды          ↓      экстрактор          ↓         цех          ↓   металл и компоненты          ↓         склад          ↓  фабрика новых робосом

Робосомы сами идут к залежам, занимают очереди у портов зданий, выгружают груз, обслуживают производство и выполняют боевые приказы. Игрок занимается размещением колонии, энергией, логистикой, разведкой, обороной и расширением. При желании можно выбрать одну машину и перейти в режим от первого лица. У «Стрижа» тоже есть кабина и ручной полёт, хотя основное управление остаётся стратегическим.

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

Фабрика робосом

Фабрика робосом

Фабрика превращает накопленные ресурсы в новые универсальные машины. Просто таймера производства недостаточно: сырьё сначала нужно добыть и привезти.

Почему Робосома и почему робот один

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

Отсюда и фраза, вокруг которой строится лор: «Один Марс. Миллионы клеток. Одна робосома начинает деление».

Я сознательно не сделал зоопарк из рабочего робота, грузовика, боевой машины, ремонтника и разведчика. На Земле специализация удобна. На Марсе каждый новый несовместимый класс означает отдельные батареи, приводы, разъёмы, запасные части, диагностику и способы ремонта. Ближайший магазин всё равно на другой планете.

В вымышленной программе колонизации поэтому принимают «Стандарт Робосомы»: единое тело, единая энергетическая ячейка, единая вычислительная архитектура, одинаковые силовые и механические разъёмы и общий протокол команд. Меняются инструменты, полезная нагрузка и программная роль, но не базовая машина.

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

В игре это не только художественное объяснение. Тот же принцип есть в коде: объекты построены по схеме Kind + Spec + Instance. Вид задаёт неизменную «ДНК», экземпляр хранит состояние, а общая логика не копируется для каждого нового здания или юнита. Лор и архитектура в этом месте говорят об одном и том же.

Робосома крупным планом

Робосома крупным планом

Один корпус для четырёх ролей: строитель, сборщик, носильщик и защитник.

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

Из этого же появляется конфликт. Хороший лёд, ровная площадка, удобный радиогоризонт и богатая руда распределены неравномерно. Колонии не обязаны быть злыми, чтобы начать спорить за одно и то же место.

Зачем писать свой игровой слой

Проект написан на Rust. Графика идёт через wgpu, окно и ввод через winit, математика через glam, данные и инструменты используют image, serde_json, zstd и другие обычные библиотеки.

То есть фраза «без движка» не означает «без библиотек». Я не вижу смысла заново писать доступ к Vulkan, Metal и DirectX. Но Unity или Unreal здесь действительно нет. Причина не в том, что готовые движки «не потянут». Они умеют гораздо больше, чем мой проект. Причина в модели мира.

Мне одновременно были нужны:

  • сфера радиусом 3389,5 км;

  • настоящий рельеф поверх этой сферы;

  • непрерывная камера от орбиты до высоты нескольких метров;

  • координаты, которые не начинают дрожать далеко от условного начала мира;

  • один и тот же путь рендера для Windows и Android;

  • детерминированная симуляция для повторов, сетевых матчей и соревнований ИИ;

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

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

Координаты: Марс нельзя притворить плоскостью

Главный инвариант проекта выглядит просто:

p = n * (R_MARS + h(n))

Здесь n — нормаль, то есть направление от центра планеты, R_MARS — радиус Марса, а h(n) — высота рельефа в этой точке. Именно на этой поверхности стоят здания, ходят робосомы, ищутся препятствия, строится луч мыши и держится камера.

Если хотя бы один алгоритм использует гладкую сферу R_MARS, ошибка иногда почти незаметна, а иногда объект оказывается под склоном на десятки метров. Особенно весело это проявляется возле Олимпа, где «почти правильно» уже совершенно неправильно.

Марс из космоса в игровом движке

Марс из космоса в игровом движке

Та же сцена должна пережить непрерывный переход от вида планеты до камеры у поверхности.

Мировые расчёты выполняются в f64. Но отправлять в видеокарту абсолютные координаты порядка миллионов метров неудобно: в f32 рядом стоящие вершины начинают терять точность. Поэтому GPU получает камеро-относительные координаты:

let gpu_position = (world_position - camera_origin).as_vec3();

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

Для ближнего и дальнего вида используются разные уровни детализации. Далёкая планета не должна нести геометрию каждого камня, а участок под ногами не должен выглядеть как растянутая карта высот. Переходы между уровнями пришлось отдельно проверять при панораме и зуме: статичный кадр легко скрывает «плавание» микрорельефа, которое сразу видно в движении.

Район Олимпа в игре

Район Олимпа в игре

На крупных формах рельефа ошибка «считаем по гладкой сфере» сразу перестаёт быть маленькой.

Откуда взялся настоящий Марс

Первый источник высот в игре был сравнительно компактным: глобальная карта MOLA превращалась офлайн в 16-битную mars_dem.png. Для каждой точки сферы движок переводит направление в долготу и широту, берёт высоту и возвращает уже не гладкую нормаль, а реальную точку поверхности.

Позже появился отдельный научный пакет Mars Truth Pack. Его основа:

  • цвет: Mars Viking Colorized Global Mosaic, около 232 м на пиксель;

  • высоты: смешанная карта HRSC/MOLA, 200 м на пиксель;

  • запасной глобальный рельеф MOLA, около 463 м на пиксель;

  • яркостная фактура THEMIS IR Day;

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

HRSC/MOLA особенно интересна тем, что это не художественная «карта Марса». USGS смешала более детальные участки стереокамеры HRSC с глобальной основой лазерного альтиметра MOLA. В основе MOLA лежат сотни миллионов измерений времени возврата лазерного импульса. Там, где доступна HRSC, переход в глобальную основу сглажен, чтобы не получить ступени на границах наборов данных.

Исходные GeoTIFF нельзя просто открыть в игровом кадре. Офлайн-инструмент mars_cook делает примерно следующее:

GeoTIFF в простой цилиндрической проекции        ↓ чтение блокамидолгота/широта каждой выборки        ↓координаты грани кубосферы        ↓пирамиды mip-уровней        ↓страницы фиксированного размера        ↓квантование, сжатие, индекс и контрольные суммы        ↓MarsPack

Почему кубосфера, а не обычная сетка широта/долгота? У простой цилиндрической карты возле полюсов быстро растут искажения, а соседство через шов становится отдельной ловушкой. Куб даёт шесть граней и удобное квадродерево. Та же адресация потом пригодилась для областей интереса сетевой игры.

Полная мозаика CTX имеет разрешение около 5 м на пиксель и выглядит очень соблазнительно. Но официальный набор занимает примерно 5,6 ТБ в сжатом виде и 11,484 ТБ после распаковки. В мобильную игру такое не положишь.

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

Важное ограничение: во время матча карта не скачивается из сети. Если подробной страницы нет, движок берёт более грубый уровень. Загрузка и обновление научного пакета происходят отдельно. Это хуже для мгновенного «увидеть всё», зато исключает зависимость партии от сервера тайлов.

Симуляция живёт отдельно от кадра

Один из ранних соблазнов был обычным: посчитать немного логики прямо перед отрисовкой. Потом «немного» превратилось в поиск пути, столкновения, добычу, очереди у ворот, строительство, производство, бой и ИИ-командира.

Теперь игровая симуляция живёт в отдельном потоке. Поток мира отвечает за окно, ввод, камеру и рендер. Он читает последний неизменяемый SimSnapshot и не ждёт, пока одна робосома закончит дорогой поиск пути.

команды игрока / сети / ИИ             ↓        очередь BUS             ↓  поток симуляции, фиксированный тик             ↓        SimSnapshot             ↓  мир, камера, интерфейс, рендер

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

Для локального матча, повтора и режима ИИ против ИИ весь мир получается из WorldConfig: начальное зерно, параметры рельефа, точка боя и остальные величины реплицируются. Реальное время определяет только скорость показа кадров, но не содержание игрового тика.

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

BUS: интерфейс как протокол, а не побочный слой

Самое необычное решение проекта выросло из желания подключать внешние ИИ. Я не хотел давать боту скрытый метод build_reactor(x, y), если человек должен открыть каталог, выбрать реактор, провести призрак по поверхности и подтвердить место.

Так появилась единая шина команд BUS. Текущая версия протокола — BUS/20.

мышь ─┐тач  ─┤сеть ─┼──> Command { tick, source, payload } ──> BUS ──> мир / sim / UIИИ   ─┤                                              │тест ─┘                                              ↓                                      Query / Reply / кадр / состояние

Каждый интерактивный элемент имеет стабильный идентификатор: например, menu.play, build.reactor, camera.zoom, select.clear. Мышь и сенсорный экран не меняют состояние напрямую, а только переводят жест в команду. Сетевой клиент, автоматический тест и внешний ИИ идут по тому же пути.

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

BUS двусторонняя. Наблюдатель может запросить состояние или изображение, но получает только то, что доступно игроку. Для кадра сохраняются время и координаты камеры. Виртуальный командир не имеет права читать массив всех противников за туманом войны только потому, что он находится внутри процесса.

Такой подход дал неожиданный побочный результат: интерфейс перестал быть декорацией вокруг логики. Он стал публичным контрактом игры. Когда в настройки добавлялась ссылка на политику конфиденциальности, даже она вошла в BUS как универсальные команды OpenUrl и CopyText с белым списком допустимых схем.

Как подключается ИИ-командир

Над BUS расположен коннектор с единым контрактом:

observation -> команды

Предусмотрены три канала.

Первый — WASM-плагин. Он подходит для компактного бота, которому нужен песочничный доступ, ограниченная память и детерминированный лимит вычислений на тик.

Второй — внешний процесс. Там может жить Python, тяжёлая модель на видеокарте или удалённый сервис. В игре уже есть OpenAI-совместимый путь, поэтому можно подключать разные шлюзы и модели, а не привязываться к одному поставщику.

Третий — Lua для простых сценариев и модификаций. Тяжёлые рантаймы WASM и Lua включаются отдельными возможностями сборки и не раздувают обычный Android-клиент без необходимости.

Главное правило одинаково для всех каналов: модуль видит не внутреннюю истину симуляции, а отфильтрованное наблюдение. Сначала учитываются радиус камеры, горизонт сферы, рельеф и туман войны, и только потом строится объект данных для ИИ. Это важно не только против читов. Мне интересен режим соревнований ИИ, где модель должна играть в ту же игру, что и человек, а зритель видит каждое её действие на экране.

Один источник формы и ошибка, спрятанная в воротах

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

В Robosoma меши зданий идут двумя путями. Часть геометрии генерируется кодом. Модели из Blender экспортируются в компактный JSON, где имена объектов помечают створки, буры, рампы, порты и фазы строительства. Недавно поверх этого был сделан единый источник физической формы: коллизионные примитивы рождаются из тех же чисел или прямо из связных частей меша.

На последнем завершённом этапе одиннадцать зданий содержали 63 313 вершин и были представлены 924 физическими формами. Худшее отклонение вершины от своей формы составило 4 см при допуске 5 см. У корабля 10 080 вершин и 145 форм. У самой робосомы 8 611 вершин, но всего пять форм: корпус-коробка и четыре ноги-капсулы. Для юнита, который проверяется каждый тик в тысячах экземпляров, цена формы важнее миллиметровой точности декоративной панели.

Робосома на рельефе

Робосома на рельефе

Визуальный меш сложный, но физическое тело юнита намеренно состоит всего из пяти дешёвых форм.

Проверка вершин прошла, но следующая проверка нашла гораздо более неприятную ошибку. Корпус здания описывался как монолит. Внешне всё совпадало, створка поднималась, однако за ней оставалось до 1,2 м невидимого твёрдого тела. Восемь из одиннадцати входов были фактически заперты.

Решением стала отрицательная форма SurfaceKind::Void: коридор, который вырезает стену по данным порта. Первая версия вырезала заодно и закрытую дверь, поэтому она переставала удерживать проём. Правильное правило получилось таким: пустой коридор отменяет стену, но не створку внутри неё.

Потом я попробовал уменьшить лишний объём форм цилиндрами. На бумаге всё выглядело очевидно: коробка вокруг круглой детали содержит лишние углы. Замер показал обратное. У реактора доля лишней твёрдости выросла с 38 до 65 процентов. По одному облаку вершин кольцо неотличимо от сплошного цилиндра, и полые обода превращались в пробки. Идею выкинули не потому, что она показалась сложной, а потому что числа стали хуже.

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

Производительность: сначала измерить, потом оптимизировать

Цель проекта нарочно завышена: 100 кадров в секунду на мобильном устройстве и 200 на ПК при бою 1000 на 1000. На всех устройствах она пока не достигнута. Зато погоня за ней уже несколько раз заставила переделать архитектуру правильно.

На Android первая гипотеза обвиняла процессор и симуляцию. Тайминги кадра показали другое: около 35 мс уходило на ожидание GPU, а процессорная часть занимала считанные миллисекунды. Главным потребителем оказался полноэкранный шейдер микрорельефа.

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

На одном и том же телефоне спокойная сцена выросла примерно с 26 до 40 кадров в секунду, а панорама с зумом с 33 до 50.

График результата уменьшенного рендера на телефоне

График результата уменьшенного рендера на телефоне

Один и тот же телефон и стенд, микрорельеф включён. Интерфейс при этом остаётся в полном разрешении.

Позже похожая история повторилась на ПК со встроенной графикой AMD. В профайлере было видно 15,3 мс в surface.get_current_texture(). Сначала это выглядело как загадочная задержка API. На деле процессор просто ждал, пока видеокарта освободит следующий кадр свопчейна.

Контрольный опыт с масштабом рендера дал:

Масштаб 3D-сцены

FPS

Ожидание захвата кадра

1,00

39

14,0 мс

0,60

67

5,1 мс

0,35

80

3,1 мс

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

Отдельной ловушкой оказался шум измерений. На живом сохранении три запуска подряд давали разницу до восьми кадров: за время теста ИИ успевал построить разные здания и увести разные отряды в кадр. Поэтому появился MARS_PERFSTAND: симуляция замораживается до первого шага, камера и время суток фиксируются, 600 кадров уходят на прогрев, следующие 400 измеряются. Мелкая оптимизация без такого стенда для меня теперь просто недоказуема.

Сеть: тест на одном компьютере оказался почти бесполезен

Первый сетевой режим — детерминированный матч колоний 1 на 1 через небольшой relay-сервер. На одном ПК два клиента показывали стабильную задержку около половины секунды и не теряли команды. Казалось, фундамент готов.

Потом я запустил настоящий сценарий: ПК и телефон по Wi-Fi через боевой сервер. Команды стали приходить через 1–5 секунд, некоторые выглядели потерянными. Восемь последовательных исправлений улучшали отдельные симптомы, но не убирали выбросы. Среди них были меньшие пакеты тиков, общий момент старта, отдельный поток отправки и локальное выделение без ожидания сети.

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

UDP:  [неподтверждённый N-2][N-1][N]  -> быстро, с избыточностьюTCP:  тот же поток                         -> медленнее, но гарантированно

Я специально оставляю это в статье как незакрытую часть. Локальный сетевой тест больше не считается доказательством. Для приёмки нужен минимум реальный телефон, реальный Wi-Fi, потеря пакетов и долгий прогон.

Дальний план гораздо больше матча 1 на 1: одна постоянная планета, где живут многие колонии. В коде уже есть S2-подобный геодезический индекс на кубосфере. Нормаль превращается в CellId, соседи правильно переходят через грани куба, а область интереса собирается обходом соседних ячеек. Сервер постоянного мира хранит агрегаты колоний и отдаёт клиенту только область вокруг взгляда. Но сетевой LOD, бесшовная передача сущностей между шардами и горячие бои ещё требуют большой работы.

Колония с близкой камеры

Колония с близкой камеры

В постоянном мире клиенту нельзя отправлять всю планету: только область интереса вокруг его взгляда и владений.

Физика и производство без лишней магии

Научные данные в игре нужны не только как текстура. Марсианская тяжесть, тонкая атмосфера и доступные на месте вещества определяют механику.

«Стриж» — суборбитальный транспорт для переброски робосом между удалёнными базами. Его полёт считается по сфере, у него есть масса, запас топлива, тяга, отклонение двигателя, опоры и аварийное состояние. В RTS-режиме игрок задаёт точку, видит дугу и время. В кабине можно управлять тягой вручную. Посадка не обязана быть идеальной: опрокинутый аппарат можно буксировать группой робосом.

Транспортный корабль Стриж

Транспортный корабль Стриж

«Стриж» связывает удалённые колонии и позволяет переключиться из RTS в кабину.

Топливная цепочка опирается на реальные идеи использования местных ресурсов. Эксперимент MOXIE на Perseverance доказал получение кислорода из марсианского углекислого газа. Карты SWIM показывают районы вероятного неглубокого водяного льда. В игре компрессор, электролизёр, реактор Сабатье и криогенное производство образуют понятную цепочку, а не дают ресурсы из воздуха по таймеру.

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

Топливное производство

Топливное производство

Производство топлива сделано цепочкой взаимозависимых узлов, а не одной кнопкой.

Автономность робосом тоже имеет реальную опору. Perseverance AutoNav строит карту местности, находит препятствия и планирует маршрут без непрерывного управления с Земли. Из-за задержки радиосигнала оператор не может вести марсоход джойстиком в реальном времени. В лоре игры человек задаёт намерение, а машина самостоятельно выполняет локальные шаги. Для связи дальнего мира мне близки идеи Delay/Disruption Tolerant Networking: данные должны переживать разрывы, а не считать постоянный канал естественным состоянием природы.

Как несколько ИИ-агентов ведут один проект

Теперь о самом эксперименте. Разработка действительно почти полностью ведётся с помощью кодовых ИИ-агентов. В разное время и для разных задач я использую Claude Code, Codex, DeepSeek V4 Flash и другие модели. Это не один чат, которому раз в неделю говорят «сделай игру».

Моя роль ближе к владельцу продукта, архитектору требований и приёмщику. Я решаю, какой должна быть игра, формулирую ограничения, смотрю живую сборку, нахожу то, что ощущается неправильно, и не принимаю ответ «тест зелёный», если на экране робот едет сквозь ворота. Агенты пишут и перерабатывают код, строят инструменты, добавляют тесты, анализируют профили и ведут документацию.

Самая большая проблема такой работы не генерация кода. Модель довольно быстро пишет функцию. Проблема — не потерять смысл проекта между сессиями и не позволить двум умным помощникам одновременно сделать несовместимые «улучшения».

Поэтому в репозитории появилась почти бюрократическая система:

  • docs/index.md — карта документации и маршрут чтения;

  • docs/ПРАВИЛА.md — инварианты проекта;

  • docs/обзор/текущее-состояние.md — журнал передачи смены;

  • отдельные документы по BUS, объектам, сети, рендеру, тестам и научным данным;

  • CLAUDE.md и AGENTS.md задают одинаковые общие правила разным агентам;

  • агенты работают строго по очереди в одной ветке;

  • смена не считается законченной с грязным деревом или неописанным хвостом.

В правилах есть намеренно жёсткие пункты. Любое действие сценарного теста идёт через BUS. Любой живой запуск обязан сам завершиться. Перед пересборкой нужно закрыть старый процесс, иначе Windows оставит заблокированный исполняемый файл и можно случайно тестировать вчерашний бинарник. Изменение документации требует синхронно обновить каталог, иначе следующая модель прочитает не ту правду.

На последнем чистом контрольном прогоне в библиотеке было больше четырёхсот тестов. Но модульные тесты не являются финалом. Для крупных функций есть полноэкранные BUS-сценарии с последовательностью команд, серией кадров и проверкой надписей, положения объектов и реальных переходов интерфейса.

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

Что ИИ делал плохо

Чтобы статья не превратилась в историю успеха, перечислю несколько характерных провалов.

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

В другой раз ИИ-командир и обычные робосомы по-разному оценивали допустимый склон. Каждый модуль был логичен сам по себе, но две логики постепенно разошлись. В результате командир отдавал приказ построить там, где симуляция потом отказывала. После этого подобные правила начали сводиться в один источник.

Был баг, когда робот заезжал в одни ворота, а открывалась створка напротив. Причина оказалась не в анимации, а в том, что номера маркеров отличались у разных зданий, а код молча предполагал общий порядок. Теперь таблица маркеров единая, а встречный тест проверяет и известные маркеры, и отсутствие неизвестных.

Самый дорогой класс ошибок появляется, когда агент видит зелёный тест и решает, что задача завершена. Синтетический кадр может не показать зависание. Один скриншот не показывает дрейф текстуры при панораме. Два клиента на одном ПК не воспроизводят Wi-Fi. Поэтому проект постепенно оброс не только тестами, но и правилами того, какое доказательство считается достаточным.

Где сейчас находится эксперимент

Игровые сборки работают на Windows и Android. Android-клиент ландшафтный, пакет один и тот же по смыслу, а платформенные модули ввода независимы и переводят действия в общий BUS. Версии для Linux, macOS, iOS и облегчённый веб-клиент пока находятся в планах. Полная кроссплатформенность — цель архитектуры, а не уже выполненная галочка.

Игра бесплатная, без рекламы и встроенных покупок. Android-релизы сейчас проходят процедуры Google Play и RuStore. Для тех, кому интересен именно текущий прототип, сборки и описание лежат на официальном сайте. Более подробный список научных опор собран на странице «Наука».

Но честный ответ на вопрос «готова ли игра» — нет. Готова большая технологическая основа и несколько связных игровых циклов. Не готов тот уровень объяснения, темпа, событий и содержания, при котором новый человек сам понимает, зачем провести в этом мире второй вечер.

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

Вопросы, которые мне действительно интересны

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

С продуктовой стороны:

  • что должно произойти в первые двадцать минут такой RTS, чтобы устройство колонии стало понятно без длинного обучения;

  • какая часть звучит сильнее как ядро игры: логистика одной универсальной машины, реальный Марс, соревнование ИИ или глобальная планета;

  • где техническому проекту без готового сообщества разумнее искать первых игроков, которым интересно ломать раннюю сборку и писать содержательные отчёты;

  • не перегружает ли серьёзная научная рамка обычный игровой процесс.

С технической стороны:

  • для постоянной сферы лучше дальше развивать собственный S2-подобный индекс или принять цену готовой реализации S2/H3;

  • имеет ли смысл сохранять UDP-окно команд плюс TCP-дубль или на следующем этапе перейти к QUIC и разделить надёжные и ненадёжные потоки;

  • где провести границу детерминизма: фиксированная арифметика для всей симуляции или достаточно воспроизводимых команд, снимков и серверной сверки;

  • как лучше стримить многогигабайтный офлайн-пак рельефа без чтения и декодирования на потоке рендера;

  • насколько оправданы пять грубых форм для робосомы при тысячах юнитов, и когда цена более точной узкой фазы начинает окупаться;

  • как безопасно давать внешним ИИ достаточно богатое наблюдение, не превращая коннектор в канал чтения всей памяти игры;

  • что вы бы измеряли следующим после доказанного ограничения по закраске грунта: стоимость конкретных ветвей WGSL, пропускную способность памяти, временной апскейл или перестройку представления микрорельефа.

Если у вас есть опыт в сетевой RTS, сферических мирах, wgpu, больших геоданных или интеграции игровых ИИ, буду рад именно разбору компромиссов. Спор «надо было взять Unity» тоже допустим, но особенно интересны конкретные места, где текущая архитектура упрётся в стену раньше задуманного масштаба.

Вместо вывода

Мой исходный вопрос звучал так: может ли один человек с несколькими ИИ-агентами сделать свою классическую RTS, а не только прототип интерфейса?

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

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


О роли нейросетей в этой статье. Проект действительно разрабатывается с участием Claude Code, Codex, DeepSeek V4 Flash и других моделей. Этот текст тоже готовился не без помощи ИИ: структура и формулировки редактировались вместе с Codex. Технические факты я сверял с кодом, журналом разработки, измерениями и официальными источниками, а окончательные решения и оценка результата остаются за мной.

ссылка на оригинал статьи https://habr.com/ru/articles/1068148/