Мы дважды удалили собственный машинный интеллект

Самый честный коммит в истории нашего проекта называется не «добавили нейросеть». Он называется «выпилили нейросеть». И таких коммитов было два.
Индустрия приучила нас к постам одного жанра: «мы прикрутили ML к X, и стало лучше». Пост из жанра «мы прикрутили ML, замерили, оказалось хуже, и мы его удалили» встречается примерно никогда — хотя по опыту разговоров с коллегами таких историй в геймдеве больше, чем успешных. Эта статья — как раз вторая категория.
Hexardia — пошаговая hex-тактика на Unity, и её боты дважды получали «настоящий» машинный интеллект. Оба раза мы его хоронили. Ниже — почему, с цифрами, и что в итоге выжило.
ML — не бесплатно. У нейросети в игровом AI есть цена: недетерминизм, неотлаживаемость, стоимость обучения и — главное — потолок качества, упирающийся в данные. На малом масштабе (десятки юнитов, сотни матчей обучающих данных, а не миллионы) хороший рукописный алгоритм бьёт нейросеть. Честный инженер обязан это признать — но признать можно только с цифрами на руках. А цифры сами себя не соберут.
Идея выглядела безупречно. У нас уже был сильный алгоритмический AI: детерминированный, с картой угроз, с пер-классовыми поведениями. Логичный шаг — обучить нейросетевого клона *копировать* его решения: собираем демонстрации «состояние → действие», тренируем, получаем ту же силу игры плюс обобщение и «живость». Классический imitation learning.
Реальность: клон получился **не лучше учителя, а хуже**. В прогонах против алгоритма он брал около 40% побед против 60% у оригинала. И это математически предсказуемо, если подумать заранее (мы не подумали): имитация в пределе сходится к учителю, а на конечных данных — к учителю с шумом. Каждая ошибка аппроксимации — это ход, который алгоритм бы не сделал. Шум не добавляет силы, он её только отнимает. Клон ≤ учитель — всегда, по построению.Плагин выпилен целиком.
«Ладно, — сказали мы, — имитация упирается в учителя. Но reinforcement learning через self-play потолка не имеет: AlphaZero же смог». Подняли тренера: бот играет сам с собой, получает награду за победу, обучается.
Результат: **те же 40 против 60** в пользу рукописного алгоритма. AlphaZero смог, потому что за ним стояли миллионы партий и дата-центр. У нас — сотни матчей в Unity-редакторе на разогнанном `Time.timeScale`. На таком масштабе RL успевает выучить грубую политику уровня «иди к врагу и бей», но не успевает открыть то, что в алгоритм вписано руками за вечер: точный расчёт добивания, отступление по абсолютному урону против HP, позиционирование дальника на границе дальности. RL заново изобретал наш алгоритм — медленно, дорого и не до конца.
Тренер выпилен вслед за плагином. Урок обоих похорон один: **на малом масштабе и имитация, и RL сходятся к «почти как алгоритм, но хуже»**. Разными путями — в одну и ту же точку.
Цифра «40 против 60» не появилась из ощущений после трёх тестовых партий. Ощущения как раз говорили обратное — нейросетевой бот *выглядел* интереснее, ходил «непредсказуемо», и очень хотелось считать это умом, а не шумом.
Спас нас editor-only соак-харнес `AutoBattleHarness` (`Assets/Scripts/Debugging/AutoBattleHarness.cs`): он без человека гоняет N матчей бот-против-бота на локальном хосте, задирает time scale, пишет каждый матч в `Logs/AutoBattles/match_NNN.log` и складывает строку-итог в `summary.jsonl`. Запуск — одна строка из консоли:
csharpAutoBattleHarness.Run(Matches: 100, TimeScale: 10f);
Именно на этом полигоне ML-варианты и тренировались, и умерли: сотни честных матчей — и распределение побед не оставляет места для самообмана. Отсюда мораль, которая важнее конкретных похорон: **без быстрого автоматического полигона нельзя доказать, что нейросеть реально лучше**. А если полигон есть — часто оказывается, что нет. Подозреваю, что немалая часть «успешных» ML-интеграций в чужих постмортемах просто ни разу не прогонялась против бейзлайна на статистически значимой выборке.
После двух похорон было бы легко объявить «ML в тактике не работает» и закрыть тему. Но неверен был не инструмент — неверна была *роль*. Оба покойника пытались **заменить** алгоритм. Выживший вариант его **подкручивает**.
`StyleNet` (`Assets/Scripts/Components/AI/StyleNet.cs`) — крошечный supervised-классификатор: один tanh-слой, softmax, **16 → 24 → 4**. Никакого RL, никакого self-play: чистое обучение с учителем на демонстрациях *побеждающего игрока-человека* — он учит бота играть «в твоём стиле», а не играть вообще. Четыре выхода — классы действий (подход / атака / манёвр / карта), и его подсказка принимается только тогда, когда softmax действительно уверен:
```csharp/// <summary>The suggest gate: the bot only takes the net's action when its softmax is at least this sure./// An unsure net yields to the ladder — overrides stay rare and DISTINCT instead of diluted coin flips.</summary>publicconstfloatMinSuggestConfidence = 0.55f;```
Не уверен — решает алгоритм. Комментарий в шапке слоя (`PlayerStyleModel.cs`) формулирует контракт дословно: *«The algorithm is the floor; this is only a bias on top»*. Алгоритм — пол, сеть — только смещение поверх. Любой сбой слоя переводит все его хуки в no-op, и бот продолжает играть как ни в чём не бывало.
А рядом с сетью — вторая, ещё более отрезвляющая половина выжившего: **стилевые оси**, которые вообще не нейросеть. Дешёвая агрегатная статистика по трём измерениям — предпочтение целей (добивает ли игрок раненых), спейсинг дальников (ближний или дальний край дальности), осторожность (когда отступать). Каждая ось «просыпается», как только её статистика становится достоверной — примерно после 3–5 побед, — и параметризует алгоритм, не заменяя его. Половину «обучаемости» дала считалка средних. Это тоже часть честности.
И последний штрих: рукописный движок `Assets/Scripts/Neuro/` — свои `Brain.cs`, `Node.cs`, `BrainCodec.cs`, `State.cs`, `Rng.cs` — пережил все ML-плагины, под которые строился. Свой детерминированный `Rng` с фиксированным seed, свой текстовый кодек, который сериализует мозг в плоский TextAsset и грузится под IL2CPP без единой внешней зависимости. Инфраструктура оказалась долговечнее моделей, которые на ней тренировали: она предсказуема и дёшева, а это в проде ценится выше «интеллекта».
Потому что у детерминированного алгоритма есть свойства, которые нейросеть не даёт по определению. Он **отлаживаем**: «бот сделал странный ход» превращается в стек-трейс и конкретную строку скоринга, а не в разведение руками над тензором весов. Он **правим**: дизайнер говорит «дальники слишком смело лезут вперёд» — и это правка константы, а не недельный цикл переобучения. Он **стабилен**: завтра играет так же, как сегодня, что для пошаговой тактики — где игрок строит план на читаемости противника — не ограничение, а фича.
Нейросеть всё это ломает, и ломает за деньги: пайплайн обучения, сбор данных, регрессионные прогоны на каждое изменение баланса. Платить эту цену имеет смысл только за то, что алгоритмом не выражается. У нас таким оказалась ровно одна вещь — *стиль конкретного живого игрока*. Его руками не запрограммируешь, потому что заранее не знаешь. Вот там сеть и живёт — в 16 входах и 4 выходах, за конфиденс-гейтом, с алгоритмом в качестве пола.
Нейросеть — приправа, а не повар. На малом масштабе имитация и RL сходятся к «как алгоритм, но хуже» — мы проверили оба пути и оба удалили. Если прикручиваете ML к игровому AI, сначала постройте полигон, который гоняет сотни матчей против рукописного бейзлайна без вашего участия. Дальше возможны два исхода: либо вы получите право на пост «стало лучше» с цифрами, либо — что вероятнее — сэкономите себе месяцы, похоронив модель молодой. Мы хоронили дважды. Не жалеем ни об одних похоронах: выживший надслой оказался лучше обоих покойников именно потому, что перестал претендовать на их место.