За четыре дня с перерывами я собрал XENOFARM — кооперативную космическую ферму‑выживание, где игрок вообще не дерётся. Модели генерировали нейросети, код писала Claude, звук синтезировал Python. Это не история успеха и не реклама инструментов: это подробный отчёт о том, что реально сработало, сколько заняло и где нейросети просто перенесли работу из моделирования в отладку.
Игра лежит здесь: xmen2026.itch.io/xenofarm — Windows, “плати сколько хочешь”, статус беты.

Вот так это выглядит. Ферма днём: грядки с боевыми растениями, база‑телепорт в центре, робот‑сборщик справа. Планета в небе, рельеф, вся флора и почти каждая модель на экране — сгенерированы.
Статья длинная, поэтому вот содержание:
Идея и почему игрок не дерётся
Как устроена работа: Claude в облаке, игра на моём ПК
Пайплайн ассетов: ChatGPT → Mage → Tripo3D
Цена AI‑генерации: три истории отладки
Как Claude пишет код
Как Claude тестировала игру — и где упёрлась в потолок
Godot: почему он и что сделано кодом
Мультиплеер: где всё ломалось тихо
Баги, которые нашлись только вычиткой
Звук: почему Python, а не нейросеть — и что я сделал бы сейчас
Оптимизация ради температуры
Где нейросеть бессильна
Сколько это заняло и что я понял
1. Идея и почему игрок не дерётся
Формула родилась из трёх игр: Slime Rancher + Plants vs. Zombies + Lethal Company. Космическая ферма, которую надо защищать ночью, и напарник, с которым вы обязательно облажаетесь.
Главный принцип я сформулировал сразу, и всё остальное выросло из него:
Игрок НИКОГДА не дерётся. Вся оборона — растения. Игрок — это логистика.
Звучит как ограничение, но это не ограничение, а генератор геймплея. Если у игрока нет оружия, единственное, чем он влияет на бой, — это скорость и приоритеты. Из принципа вытекла центральная механика:
Носить можно только один предмет за раз.
Одна строчка правил, а последствия расходятся по всей игре. Сбегал за водой — значит, не унёс заряд. Побежал заряжать дальнее растение — ближнее осталось сухим. Ночью, когда таймер волны тикает, каждая ходка — это ставка. Получается не шутер, а постоянная гонка с самим собой.
Цикл простой: день — сбор ресурсов и стройка, ночь — атака монстров. Сломают базу — проиграл.
Ресурсы:
Ресурс | Роль |
|---|---|
💧 Вода | Заливается либо в энергию (без неё растение не стреляет), либо в ХП |
⚡ Заряд | Патроны. Кончились — растение молчит |
🟪 Материя | Валюта. Падает с монстров, копится генератором на ферме |
★ Протеин | Временный баф атаки на 20 секунд, только из магазина |
Обратите внимание на воду: она заливается либо в энергию, либо в ХП. Ещё одна развилка, ещё одно решение под таймер. Ресурсов всего четыре, но каждый из них — это выбор, а не ползунок.
Шесть растений: четыре простых (покупаются за материю) и два сильных (требуют чертежа из магазина). У каждого свой боевой профиль — дальность, скорострельность, урон, нужен ли заряд вообще. Бонсай с мечом и Жнец работают в ближнем бою: они не стреляют, они рубят подошедших.
Пять типов врагов: обычный монстр, стрелок, гигант, летающий босс и — мой любимый — пират. Пират не дерётся. Его нельзя атаковать. Он просто подходит к растению и уносит его. Прогнать можно только вручную, клавишей E, подбежав вплотную. И вот вы стоите ночью, на вас идёт волна, а сбоку кто‑то молча ворует вашу оборону. Отвлечься или нет — снова решение.
Постройки: растения, ящики‑склады на 5 предметов, капканы, заборы с ХП, которые монстры честно ломают.
Помощники покупаются в магазине, максимум по два каждого вида. Защитник патрулирует ферму и отстреливает монстров. Робот‑сборщик сам таскает ресурсы с карты мира к растениям — то есть частично снимает с игрока ту самую логистику, ради которой всё затевалось. Это осознанный размен: игра длинная, и во второй половине партии игрок должен получить рычаг, иначе беготня превращается в рутину.

Весь магазин на одном экране. Чертежи здесь — не расходник, а постоянная разблокировка для всей команды: купил один раз, дальше платишь только за установку. Это принципиальное решение для кооператива — иначе второй игрок вынужден покупать то же самое заново, и вместо командной игры получается два параллельных огорода.
2. Как устроена работа: Claude в облаке, игра на моём ПК
Это часть, которую обычно опускают, а зря — именно она определила скорость всего проекта.
Claude работала в облаке. Ни одна модель не крутилась на моём ноутбуке. Это принципиально: локальная LLM съела бы всю видеопамять и весь процессор, и мне пришлось бы выбирать между «нейросеть думает» и «игра запускается». А поскольку игра — это Godot с процедурной генерацией рельефа на 36 тысяч вершин, выбор был бы очевиден не в пользу нейросети.
Разделение получилось такое:
В облаке: чтение и запись файлов проекта, генерация кода, Python‑скрипты для обработки ассетов и синтеза звука, парсинг
.glb, ревью кода, ведение списка задач.На моём ПК: только сама игра — Godot, компиляция, запуск, рендер.
То есть ноутбук нагружался ровно тем, чем он и должен был нагружаться: запуском игры. Всё остальное считалось где‑то ещё и приезжало готовым.
И вот здесь появился неожиданный герой истории — температура.
Ноутбук грелся всерьёз. Пики CPU во время игры — 85–91 °C, после закрытия окна падение до 51–61 °C. Это не абстрактное число: на таких температурах процессор начинает троттлить, а замеры производительности превращаются в мусор, потому что вы меряете не код, а нагрев.
Поэтому я задал жёсткое правило, и оно определило ритм всей работы:
«Во время работы поглядывай на температуру всего ПК, так же после выполнения одной операции ты посмотрела изменения в игре И ПОСЛЕ ЭТОГО ЗАКРЫВАЙ ОКНО САМОЙ ИГРЫ»
Рабочий цикл стал таким:
Claude пишет код блоками
Запускает игру из редактора
Делает скриншот, смотрит результат
Закрывает окно игры
Проверяет температуру в HWiNFO
Идёт к следующему пункту
Пункт 4 выглядит мелочью, но без него окна копятся, каждое рендерит свои 60 кадров в секунду, и через час работы ноутбук превращается в обогреватель. Пункт 5 — это то, что нейросеть обычно не делает вообще: следит не за кодом, а за железом, на котором код исполняется.
Часть решений в игре принята ради температуры, а не ради красоты. Лимит 60 кадров, урезанные тени, отключённое сглаживание — всё это появилось не потому, что «оптимизация — это хорошо», а потому, что конкретный ноутбук конкретно грелся. Об этом подробнее в разделе 11.
Отдельная морока — работа с чужим графическим интерфейсом. Диалог «файлы изменены вне Godot» перехватывал F5 и ломал автоматический запуск. Окна прятались за другими. Один раз Claude кликнула по уже выделенному узлу в дереве сцены и случайно переименовала World — Godot воспринял второй клик как «переименовать». Заметили по сломавшейся карте мира, вернули имя. Это, кстати, отличная иллюстрация: нейросеть в чужом GUI медленнее и ошибается чаще, чем в коде. Всё, что можно было сделать скриптом, надо было делать скриптом.
3. Пайплайн ассетов: ChatGPT → Mage → Tripo3D
Я не 3D‑художник. Совсем. Поэтому весь визуал делался конвейером из трёх инструментов, и порядок здесь важнее самих инструментов.
Шаг 1. ChatGPT — концепт‑арт и определение стиля
Сначала я генерировал 2D‑картинку персонажа: боевое растение в горшке, космонавт, монстр. На этом этапе решается всё, что потом невозможно поменять дёшево: освещение, палитра, пропорции, уровень детализации, «мультяшность» против реализма.
Ошибиться тут дешевле всего. Перерисовать картинку — минуты. Перегенерировать готовую 3D‑модель, у которой уже настроен риг и которая уже вставлена в сцену, — это заново пройти весь конвейер.
Шаг 2. Mage — вариации по референсу
Когда стиль найден, нужны остальные персонажи в том же стиле. Тут картинка из ChatGPT становится референсом, а Mage делает по ней варианты.
Это ключевой момент всего пайплайна, и его чаще всего пропускают. Стиль задаётся один раз и переносится, а не изобретается заново для каждой модели. Если каждого персонажа генерировать независимо — с нуля, новым запросом, — вы получите зоопарк. Каждое растение будет выглядеть так, будто пришло из другой игры: разная толщина обводки, разная насыщенность, разная степень детализации листьев. По отдельности все красивые, вместе — каша.
Референс решает эту проблему буквально бесплатно.
Шаг 3. Tripo3D — картинка в 3D‑модель
Финальные 2D‑концепты уходят в Tripo, оттуда приходят готовые меши с текстурами. То, на что моделлеру нужны дни, приходит за минуты.
И — важное, что стоит знать всем, кто сейчас смотрит в эту сторону: Tripo умеет не только геометрию. У сервиса есть автоматический риггинг и генерация анимаций: модель приезжает уже со скелетом и набором готовых движений — бег, атака, покой. Именно оттуда в XENOFARM пришли анимации монстров, Защитника и космонавта. Никто не расставлял кости руками, никто не делал ключевые кадры.

Библиотека ассетов игры. Всё на этом экране сгенерировано за считаные часы.

Игровой персонаж. Путь: картинка из ChatGPT → 3D‑модель с ригом и анимациями из Tripo. Именно этот космонавт потом упорно летал над землёй — история в следующем разделе.

А это боевая единица игрока. Сам игрок не стреляет — стреляют растения.
Выглядит как магия. Дальше начинается расплата.
4. Цена AI‑генерации: три истории отладки
Это главный вывод всего проекта, и я хочу, чтобы он прозвучал прямо:
Нейросети не убрали работу. Они перенесли её из моделирования в отладку.
Автоматический риггинг — это чудо ровно до того момента, пока вам не нужно, чтобы персонаж стоял на земле. Три истории по делу, с диагнозом и лечением.
4.1. Космонавт летал над землёй
Симптом: во время анимации сбора и на бегу персонаж парил в воздухе. Не сильно, сантиметров на двадцать — но достаточно, чтобы игра выглядела сломанной.
Гипотеза первая. Это root motion: анимация двигает корневую кость, движок этого не знает, надо указать дорожку позиции корневой кости в root_motion_track у AnimationTree. Сделали — не помогло вообще.
Гипотеза вторая. Ладно, зайдём с другой стороны: найдём самую низкую кость скелета в текущем кадре и опустим модель на её высоту. Логика железная: самая низкая кость — это и есть ступня, ступня должна быть на земле. Сделали. Замер стабильно давал 0,000. Эффект нулевой.
Два промаха подряд. И вот тут случилось то, что оказалось главным методологическим уроком проекта: мы перестали гадать и вывели в лог реальные числа. Сколько в анимации дорожек, какого они типа, какие кости где находятся.
Ответ пришёл мгновенно и оказался совсем не тем, что мы предполагали:
В анимации 40 дорожек, и все до одной — повороты. Дорожек позиции ноль.
Персонаж не «поднимался». Ему нечем было подниматься — в анимации физически нет ни одного смещения. Он поджимал ноги вращениями, а таз оставался ровно там, где был. Визуально это неотличимо от парения, но причина противоположна той, что напрашивается.
А почему «самая низкая кость» всегда давала ровно ноль? Потому что в риге есть корневая кость, намертво прибитая к началу координат. Она никогда никуда не двигается, поэтому при любой позе она и оказывается самой нижней. Мы честно измеряли высоту кости, которая по определению всегда на нуле.
Решение нашлось за минуты, когда стало понятно, что искать. Мерить подъём не по «самой низкой кости», а именно по костям ступней — по тем, чьи имена содержат foot или toe. И опускать модель ровно на столько же, с поправкой на масштаб скелета. Элегантность в том, что, когда нога действительно стоит на земле, поправка сама равна нулю — поэтому бег и покой не портятся, лечится только то, что сломано.
Ту же болезнь потом поймали у стрелка и у Защитника. Лечили одинаково, уже за минуты вместо часов.
4.2. Имена анимаций бесполезны
После прогона моделей через Blender все дорожки называются NlaTrack, NlaTrack.001, NlaTrack.002, NlaTrack.003. Где бег, где удар, где смерть — по имени не понять никак.
Руками это решается за пять минут: открыл, посмотрел, переименовал. Но моделей много, а Claude не может «посмотреть» анимацию — она видит только файл.
Пришлось парсить .glb скриптом, буквально читая бинарный формат, чтобы вытащить имена дорожек и их длительности. И тут выяснилось смешное.
У обычного монстра, стрелка и Защитника первая дорожка длится ровно 1,29 секунды. У всех трёх. С точностью до сотых.
Это один и тот же стоковый цикл бега на общем скелете — Tripo берёт готовую базу и натягивает на неё сгенерированную геометрию. Отсюда правило: первая дорожка = движение, остальные = действия.
Исключение нашлось сразу: у гиганта и пирата первой идёт не бег, а действие — удар у гиганта и захват растения у пирата. Пришлось вводить в таблицу видов явное поле move_anim с индексом дорожки движения. Некрасиво, но честно — если данные неоднородны, лучше это признать в конфиге, чем городить эвристику, которая однажды угадает неправильно.
4.3. Текстура планеты с запечённой шахматкой
Картинка планеты пришла с нарисованным «прозрачным» фоном. Той самой серо‑белой шахматкой из фотошопа — но не как альфа‑канал, а запечённой прямо в пиксели. Модель добросовестно нарисовала то, что видела в обучающих данных.
Чистили скриптом на Python (PIL + scipy):
Находили нейтральные пиксели ровно со значениями 230 и 255 — это стандартные цвета шахматки
Заливали их прозрачностью
Закрывали мелкие дырки, которые появились там, где эти же значения случайно встречались в самой планете
Размывали край альфы, чтобы не было пиксельной каймы
Бонусом всплыла вторая проблема: текстура, натянутая на сферу, выглядела криво — планета же плоская картинка, а не развёртка. Заменили сферу на квад‑билборд, всегда повёрнутый к камере. Игрок никогда не видит планету сбоку, значит, объём ей не нужен.
Вывод по ассетам
AI‑генерация 3D экономит недели на контенте, но приносит собственный класс проблем:
безымянные анимации и произвольный порядок дорожек
риги, на которые нельзя опереться привычным способом
артефакты, запечённые в текстуры
анимации без дорожек позиции — то есть формально рабочие, фактически нет
Если вы готовы лезть в кости и парсить бинарники — это огромное ускорение. Если не готовы — проверенный ассет‑стор может выйти дешевле по нервам. Промежуточного варианта нет.
5. Как Claude пишет код
Отдельный раздел, потому что чаще всего спрашивают именно про это. Не «может ли нейросеть написать функцию» — может, все видели. А как выглядит работа, когда проект больше одного файла.
Она не начала с кода
Я дал идею голосом: межгалактическая ферма, смесь Slime Rancher, Plants vs. Zombies и Lethal Company. Первое, что сделала Claude, — не написала ни строчки. Вместо этого:
Исследовала референсы. За счёт чего эти игры выстрелили, что можно взять, а что для команды из одного человека и одной нейросети неподъёмно.
Написала одностраничный дизайн‑док. Ресурсы, цикл дня и ночи, роль игрока. Чтобы у идеи появился скелет до того, как появится код.
Проверила экономику площадки. Комиссия Steam, порог выплат, стоимость размещения страницы приложения.
Третий пункт меня удивил больше всего. Это не то, что я просил, и не то, чего ждёшь от «инструмента для написания кода». Но логика верная: экономика площадки влияет на решения не меньше, чем технический стек. Нет смысла проектировать игру под модель, которая не окупит взнос за страницу.
И только после этого выбрали движок.
Разбивка на этапы
Однажды я надиктовал огромный список всего, что хочу видеть в игре: типы монстров, помощники, магазин, чертежи, фауна, планета в небе, разбитый корабль. Одним куском это неподъёмно — и для человека, и для нейросети, потому что нет критерия «готово».
Claude разложила список на десять этапов, M1–M10, каждый со своей проверяемой целью. Дальше шла по ним по порядку, отмечая выполненное. Когда я добавлял новое посреди работы — а я добавлял постоянно — оно становилось отдельной задачей в том же списке, а не вклинивалось в текущую.
Это оказалось важнее, чем кажется. Во‑первых, у нас обоих была общая картина: видно, что сделано, что в работе, что впереди. Во‑вторых — и это главное — новые хотелки перестали ломать текущую работу. Классический сценарий «я тут ещё придумал» больше не превращал наполовину сделанную систему в наполовину сделанные две.
Как выглядит сам код
Три вещи, которые я отметил бы, если бы нанимал человека:
Комментарии объясняют «почему», а не «что». Не # увеличиваем счётчик, а # пересчитываем раз в 0.35 с со случайным разбросом, чтобы монстры не считали цель в один кадр. Через два дня, когда возвращаешься к файлу, ценность у этих двух видов комментариев отличается на порядок.
Комментарии на русском. Мелочь, но я просил — и это соблюдалось весь проект. Читать свой проект на родном языке банально быстрее.
Код пишется блоками, а не построчно. Не «добавь одну функцию», а «вот система магазина целиком, вот её интеграция с HUD, вот RPC для покупки». Причём согласованно: если меняется сигнатура метода, она меняется во всех вызовах сразу, в десятке файлов. Это то, что нейросеть делает заметно лучше человека — человек забудет один вызов из пятнадцати и найдёт его через час.
Самопроверка через ревью
Вот это оказалось самым ценным и самым неожиданным.
Claude не может протестировать игру руками. Поэтому вместо тестирования она опиралась на статическую вычитку: после каждого крупного блока код отправлялся на отдельное ревью — сверить арность вызовов, существование узлов и групп, имена методов, соответствие RPC‑сигнатур, порядок инициализации.
Формально это скучно. Практически — этот механизм поймал баги, которых не видно в игре вообще. Им посвящён раздел 9, и там есть как минимум один, который мог бы дожить до релиза.
Один пример прямо сюда: код, где деньги списывались раньше выдачи товара. Покупка с полными руками или попытка купить третьего робота (лимит — два) съедала бы материю впустую. Игрок жмёт кнопку, деньги уходят, товара нет. Поймано на собственном ревью, до первого запуска. В игре этот баг воспроизводился бы редко и выглядел бы как «мне кажется, у меня пропадает материя» — то есть как жалоба, которую невозможно отладить.
6. Как Claude тестировала игру — и где упёрлась в потолок
Инструментов было три: файлы проекта, Linux‑терминал для скриптов и управление моим экраном — мышь, клавиатура, скриншоты.
Что она могла: запустить игру, сделать скриншот, посмотреть на него. Проверить, что объект появился, что HUD показывает правильное число, что растение стоит на грядке, а не под ней. Прочитать логи. Убедиться, что механика работает так, как описано в дизайн‑доке.
Чего она не могла — и это не мелочь:
Играть. Она не держит мышь как игрок. Не чувствует, отзывчиво ли управление, не липнет ли камера, успевает ли игрок добежать. Всё, что касается ощущений, — темп, сложность, вес движения — недоступно в принципе.
Слышать. Вообще. Все 20+ звуков синтезированы вслепую: работу аудио приходилось проверять по коду, файлам и логам. Играет ли звук — видно в дереве узлов. Как он звучит — нет.
Видеть, что некрасиво. Модель на месте, текстура натянута, полигоны не торчат — формально всё хорошо. То, что растение выглядит уныло или что ночь неотличима от дня, — это оценка, а не проверка.
Из этого выросло главное ограничение всей работы:
Claude могла проверить, что механика работает так, как описано. Она не могла оценить, весело ли играть.
Поэтому реальное разделение труда сложилось такое: она делает и проверяет формальную корректность, я играю и говорю, что чувствуется не так. И почти все самые ценные правки в игре пришли из второй половины этого разделения — от игры руками. Об этом раздел 12.
Про сбои — честно
Однажды после смены версии модели прямо посреди работы сломался захват экрана: скриншоты приходили чёрными. Claude не сразу это признала и какое‑то время гоняла бессмысленные команды по кругу — запускала, снимала, анализировала пустоту, пробовала снова. Мне пришлось несколько раз повторить, что она глючит.
Это важный момент, и я не хочу его сглаживать. Пока инструменты работают, нейросеть очень быстрая. Когда ломается канал обратной связи, она склонна не остановиться, а продолжать попытки. Правильное поведение — сразу сказать «я не вижу экран» и передать действие человеку. Она этого не сделала, и полчаса ушло впустую.
Если вы работаете так же — держите это в голове. Признак: ответы становятся всё более уверенными при всё менее осмысленных действиях.
7. Godot: почему он и что сделано кодом
Движок — Godot 4.7.1, язык GDScript.
Три причины выбора:
Бесплатность и отсутствие роялти. Для инди с прицелом на Steam это принципиально: комиссия площадки уже 30%, добавлять сверху процент движку не хочется.
Лёгкость. Godot запускается на слабом железе и не требует получаса на импорт проекта.
В Godot удобно писать кодом. Вот это оказалось решающим именно в связке с нейросетью.
Третий пункт стоит развернуть. В Godot сцены, ноды, материалы, генерация мира — всё это можно делать из скрипта, не притрагиваясь к редактору. А значит, Claude может строить игру, не работая через GUI — тот самый GUI, где она медленная и где случайно переименовывает узлы. Каждая операция, переведённая из мышки в код, — это ускорение в разы и минус один класс ошибок.
Что построено кодом в рантайме
Почти весь мир:
Рельеф —
FastNoiseLite, карта 340×340 метров, шаг сетки 1,8 м (около 36 тысяч вершин). Кратеры, горная гряда, поднятый край‑«чаша», чтобы игрок не убежал с карты, и ровная площадка под ферму в центре.Шейдер рельефа — цвет считается по высоте и уклону: трава в низинах, мох, пыль, камень на крутых склонах, снег на вершинах. Ни одной текстуры, всё в шейдере.
Небо — процедурный шейдер со звёздами и туманностью.
Растительность и камни —
MultiMesh(одна отрисовка на тысячи экземпляров), около 4300 объектов.Космический мусор — обломки кораблей собираются из примитивов (плиты, трубы) со случайным поворотом, полузарытые в грунт. Дёшево и читается как декорация.
База‑телепорт — платформа со светящимися вращающимися кольцами и лучом вверх, тоже целиком из примитивов.
События: раз в 35–70 секунд падает метеорит (урон по площади, вспышка, тряска камеры) либо в небе пролетает корабль с мигающими огнями. Мир не должен выглядеть неподвижным.
Урок, который стоил четырёх мегабайт
Генерацию обязательно нужно гейтить:
if Engine.is_editor_hint(): return
Иначе редактор Godot запекает сгенерированные меши прямо в файл сцены. У нас main.tscn разрастался до 4 МБ, пока это не поймали: четыре мегабайта текстового описания процедурно сгенерированной травы, лежащие в репозитории. Каждое открытие сцены становилось медленнее, каждый коммит тащил за собой мусор.
Мелочи, стоившие часа каждая
Transform3Dв файлах.tscnзаписывается row‑major, а не column‑major. Если править сцену текстом (а Claude правит именно текстом), камеры из‑за неверного порядка чисел смотрят в небо. Час на понимание, секунда на исправление.Автолоады резолвятся на этапе компиляции в том порядке, в котором перечислены в
project.godot. ОбращениеSteamworks.enabledиз автолоадаNetпадало сIdentifier not found, потому чтоNetгрузится раньше. Лечитсяget_node_or_null("/root/Steamworks")— то есть отказом от статической ссылки в пользу динамической.
8. Мультиплеер: где всё ломалось тихо
Сеть — GodotSteam (Steam Relay P2P) плюс ENet как запасной вариант для локальной сети и тестов.
Отлаживали на App ID 480 — это Spacewar, публичный тестовый ID Steam, который позволяет работать с лобби, не имея своей страницы приложения. Нюанс: в этом App ID сидят все, кто что‑то тестирует, поэтому список лобби — это помойка. Помечали свои тегом game:XENOFARM и фильтровали.
Архитектура хост‑авторитетная: хост считает состояние мира, клиенты только отображают присланное. Спавн игроков, растений, врагов, дропов, ящиков и построек идёт через MultiplayerSpawner со spawn_function — это автоматически доносит объекты и тем, кто подключился позже, что бесплатно решает половину проблем с поздним подключением.
Два паттерна RPC:
@rpc("authority", ...)— хост → клиентам, рассылка состояния@rpc("any_peer", "call_local", "reliable")— клиент → хосту, запросы вроде «посади растение» или «купи товар»
Звучит стройно. На практике выяснилось главное свойство сетевого кода Godot:
Сетевой код Godot полон тихих отказов. Пакет не приходит, ошибки нет, объект просто не обновляется.
Никакого исключения, никакого предупреждения в консоли. Просто число на экране не меняется. И половина сетевых багов проявлялась только со вторым подключённым игроком — в одиночной игре всё выглядело исправным, потому что там некому было не получить пакет.
Три истории и горсть мелочей.
8.1. RPC в режиме authority не доходит до отправителя
Симптом: у хоста ХП и заряд растений на метке стояли намертво. Выглядело так, будто урон вообще не наносится — растение горит, монстр бьёт, число не меняется.
Причина: pushstate.rpc(...) рассылается клиентам, но до самого отправителя не доходит. Хост честно менял числа у себя в памяти, но никогда не вызывал у себя обновление метки. Данные были правильные, отображение — нет.
Решение: обёртка _broadcast(), которая делает и rpc(), и локальный вызов обновления. Тот же баг до этого ловили на генераторах ресурсов — там у хоста «замирал» отсчёт таймера.
8.2. authority на узле, которым владеет клиент
Симптом: второй игрок не видел ни своей ноши, ни ХП, ни телепорта к кровати. Всё критичное для него лично.
Причина — самая красивая в проекте. Узел игрока принадлежит клиенту: set_multiplayer_authority(peer_id). А состояние (_set_hp, setcarried) шлёт хост.
Проверка Godot для режима authority звучит так: «я не authority И отправитель == authority узла». Разберём по ролям:
У владельца узла (клиента) ложно первое условие — он и есть authority
У всех остальных ложно второе — прислал‑то хост, а authority узла — клиент
Пакет отбрасывался у всех получателей. Хост же менял состояние напрямую, в обход RPC, — поэтому он один и видел правильные числа. Ошибки — ноль.
Решение: режим any_peer плюс ручная проверка, что get_remote_sender_id() равен 0 (локальный вызов) или 1 (хост).
8.3. Массив при call_local передаётся по ссылке
Ящик стирал всё, что в него кладут. Предмет из рук исчезал, в ящике не появлялся.
Причина: pushitems.rpc(items) при call_local передаёт тот же самый объект массива, а не копию. items.clear() внутри обработчика очищал и аргумент тоже. Цикл заполнения после этого шёл по пустому списку и не выполнялся ни разу.
Решение: items.duplicate(). Одна строчка.
Это классическая ловушка ссылочной семантики, но в сетевом контексте она особенно коварна: при настоящем RPC по сети массив сериализуется и приходит копией, всё работает. При call_local — не сериализуется. То есть баг проявляется только у отправителя и только локально.
8.4. Мелочи, но злые
multiplayer_peerпо умолчанию неnull, аOfflineMultiplayerPeer. Он притворяется сервером с id 1 и статусом «подключён». То есть наивная проверка «мы в сети?» всегда возвращает правду. Пришлось писать собственную проверку на настоящий пир.request_plantтребовалcall_local. Хост подтверждает посадку черезrpc_id(1)самому себе — и безcall_localспавн у хоста не отрабатывал. Материя списывалась, растение не появлялось. Опять же: молча.
9. Баги, которые нашлись только вычиткой
Отдельный жанр — те, которые невозможно увидеть, просто побегав по ферме. Их поймало ревью кода, а не тесты.
Монстры физически не могли навредить игроку
Самый показательный баг проекта.
В функции выбора цели база стояла раньше игроков в списке приоритетов. А база есть всегда — она не умирает до конца партии и не исчезает. Значит, до перебора игроков дело не доходило никогда.
Последствие: весь контур урона по игроку, система ХП, кровать‑респаун и лечебница были мёртвым кодом. Написанным, отлаженным, вызываемым — и никогда не исполняемым. В игре это выглядело как «монстры почему‑то не бьют игрока», что легко списать на баланс или на то, что просто не успели добежать.
Робот‑сборщик бродил кругами
Цель пересчитывалась каждые 0,33 секунды, и решение «чего не хватает больше — воды или заряда» переворачивалось от любого изменения на грядке. Робот шёл за водой, на полпути пересчитывал, решал, что заряд нужнее, разворачивался, снова пересчитывал, снова передумывал.
Выглядело как поломка ИИ. Было — отсутствие гистерезиса.
Решение: цель выбирается один раз и держится до конца ходки. Скучное правило, полностью решающее проблему.
Гигант мог залипнуть в атаке навсегда
Тонкая цепочка. Цикл бега замедлялся до 0,12 от нормальной скорости, когда монстр стоит на месте, — чтобы он не перебирал ногами впустую. Анимация удара наследовала это замедление. Замедленный удар не успевал доиграть до следующего замаха, сигнал animation_finished не приходил, флаг «атакую» не сбрасывался. Гигант замирал в позе удара навсегда.
Три независимо разумных решения, вместе дающие мёртвый объект.
Фон меню не совпадал с кнопками
Расхождение около ста пикселей. TextureRect по умолчанию считает своим минимальным размером размер картинки (1672×941) и не даёт ужать себя до окна 1280×720. Фон рисовался в натуральную величину, кнопки позиционировались по размеру окна.
Лечится expand_mode = EXPAND_IGNORE_SIZE.
10. Звук: почему Python, а не нейросеть — и что я сделал бы сейчас
Все звуки в игре — больше двух десятков — синтезированы скриптом на Python (numpy + wave). Шум, синусы, огибающие, фильтры. Никаких сэмплов, никаких библиотек.
Что сделано: выстрелы (разные для каждого вида растения), взмах клинком, рык и удар монстра, смерть, три варианта шагов, прыжок, приземление, копание, подбор предмета, скрип ящика, покупка, отказ, сирена перед ночью, звуки интерфейса.
Почему так, а не иначе:
Нет вопросов с лицензиями. Совсем. Ни одного чужого сэмпла в проекте, ни одной строчки в титрах, ни одного риска при релизе в Steam.
Любой звук перегенерируется изменением пары чисел. Выстрел слишком звонкий? Поменял частоту среза, пересобрал все 20+ файлов за секунду.
Это можно делать вслепую. Claude не слышит звук — но она может рассчитать огибающую.
Тонкости, которые пришлось учесть:
Шаги отмеряются по пройденному пути, а не по таймеру. На бегу они сами учащаются, при остановке — прекращаются, и нигде не нужно синхронизировать таймер со скоростью. Три варианта шага, чтобы ходьба не звучала метрономом.
Отдельные звуковые шины Master / Music / SFX — чтобы игрок мог приглушить эффекты, не выключая музыку.
И один провал: затухание звука по расстоянию сперва выкрутили слишком резко. Выстрел в двадцати метрах падал почти в тишину, и мне казалось, что звука нет вовсе. Формально всё работало правильно — по коду, по логам, по проверкам. Просто подбирал его тот, кто не слышит результат.
Что я сделал бы сейчас иначе
Вот здесь честно: процедурный синтез был правильным решением для этого проекта, но уже не единственным.
За то время, что прошло с тех пор, генерация звука и музыки нейросетями стала полноценным рабочим инструментом. Suno, Udio и ElevenLabs Music генерируют музыкальные треки по текстовому описанию; у ElevenLabs есть отдельный генератор звуковых эффектов, заточенный ровно под то, что я синтезировал руками — удары, шаги, интерфейсные щелчки. Появились и специализированные сервисы под геймдев.
Что поменялось бы в XENOFARM: в игре появилась бы музыка. Сейчас её нет вообще. Ни в меню, ни на ферме, ни в момент ночной волны — и это заметная дыра, которую невозможно закрыть numpy. Синтезировать эффект — реально. Написать атмосферный эмбиент про одинокую ферму на краю галактики — нет.
Так что, если вы повторяете этот путь сейчас: эффекты можно синтезировать (дёшево, лицензионно чисто, полностью управляемо), музыку — генерировать. Только проверьте лицензию конкретного сервиса на коммерческое использование, особенно если целитесь в Steam.
11. Оптимизация ради температуры
Напомню контекст из раздела 2: пики CPU 85–91 °C во время игры, 51–61 °C после закрытия. За температурой приходилось следить на всём протяжении проекта.
Оптимизировали строго то, чего игрок не заметит:
1. Троттлинг обходов групп. Каждый монстр перебирал все растения и всех игроков 60 раз в секунду — то есть каждый кадр. При десятке монстров и десятке растений это сотни проверок в кадр ради ответа, который не меняется.
Перевели на пересчёт раз в ~0,35 секунды со случайным разбросом — чтобы монстры не считали цель в один и тот же кадр и не давали периодический провал FPS. То же самое сделали для роботов, HUD и панели магазина.
2. Дальность видимости с растворением. Растительность дальше 110 метров не рисуется, а на последних метрах плавно растворяется. Ключевое здесь — именно растворение: без него объекты «выскакивают» на границе, и это гораздо заметнее, чем их отсутствие.
3. Тени. Мелкая трава и цветы теней не отбрасывают вообще. Дальность теней солнца сокращена до 70 метров, карта теней уменьшена вдвое, до 4096.
4. Отключены сглаживание, SSAO, SSIL, отражения экранного пространства. В мультяшной стилистике их отсутствие почти не читается, а стоят они дорого.
5. Engine.max_fps = 60. Без лимита видеокарта рендерила на максимуме впустую и тянула за собой общий нагрев — в меню игра выдавала сотни кадров и грелась ровно так же, как в бою.
Главный принцип: ни один игрок не заметит, что монстр думает 3 раза в секунду вместо 60. Процессор заметит.
12. Где нейросеть бессильна
Самое честное наблюдение за весь проект. Повторю мысль из раздела 6, потому что она заслуживает повторения:
ИИ отлично проверяет, что механика работает так, как описано, и никак не может проверить, весело ли играть.
Первая играбельная версия была формально безупречна. Все системы работали. Все проверки проходили. Я запустил, побегал десять минут и сказал: «игра сырая, это надо исправлять».
Вот что родилось из этой фразы — и ни одна из этих правок не пришла из анализа кода:
Заметная смена дня и ночи. До этого ночь читалась только словом в HUD. Механически всё работало: волна начиналась, монстры спавнились, таймер шёл. Атмосферно — ничего не происходило.

Та же планета ночью. Сравните с первым скриншотом — это одна и та же точка карты. Никакого нового контента: плавно гаснет и синеет солнце, темнеет небо, разгораются звёзды. Ноль новых ассетов, а игра изменилась сильнее, чем от любой новой механики.
Полоски здоровья над растениями и базой. Раньше состояние обороны надо было угадывать.
Разные снаряды у растений. Тонкий луч‑пуля, шарик плазмы, толстый лазер, дуга клинка — каждый со своим цветом. Под протеином цвет уходит в оранжевый, а звук становится ниже: игрок должен видеть, что баф работает, а не помнить, что он его покупал.
Отклик на события: тряска камеры от метеорита и ударов по базе, сирена и баннер перед ночью.
Обучение новичка. Без него игрок просто стоит и не понимает, что первый шаг — взять канистру.
Отдельная находка того же ряда — процедурное оживление моделей без анимаций. Растения и босс качаются и «дышат», у каждого своя фаза. Если фазу не разводить, вся грядка колышется синхронно и выглядит хуже, чем если бы вообще не двигалась — как одна деталь, а не как живой огород. Сильные растения собраны из нескольких частей, и каждая шевелится отдельно.
Заметьте: почти всё в этом списке — не механика. Это обратная связь, читаемость и атмосфера. Ровно та область, где нужен человек, который сел и поиграл.
13. Сколько это заняло и что я понял
Четыре дня с перерывами — от голосовой идеи до играбельной беты: кооператив, процедурная планета, шесть растений, пять типов врагов, магазин с чертежами, помощники, больше двух десятков синтезированных звуков.
Пять лет назад в одиночку это заняло бы месяцы. Но не обманывайтесь формулировкой: быстрым стало производство контента, а не разработка. Отладка осталась ровно такой же медленной, как была. История с летающим космонавтом заняла больше времени, чем генерация всех моделей вместе взятых.
Шесть вещей, которые я бы сказал себе на старте:
1. AI‑генерация 3D экономит недели на контенте, но переносит работу в отладку. Модели приходят с безымянными анимациями, произвольным порядком дорожек, артефактами в текстурах и ригами, на которые нельзя опереться привычным способом. Заложите на это время — оно будет.
2. Задавайте стиль один раз. Референс из ChatGPT → вариации по нему в Mage → и только потом Tripo. Иначе персонажи будут выглядеть выходцами из разных игр, и вы поймёте это, только когда соберёте их в одну сцену.
3. Ошибки нужно доказывать замером, а не гипотезой. Дважды подряд «очевидная» причина парения персонажа оказывалась неверной, и оба раза ответ дал отладочный вывод реальных чисел. Это правило работает и для человека, и для нейросети, но для нейросети — сильнее: она умеет генерировать правдоподобные объяснения быстрее, чем вы успеваете их проверять.
4. Статическая вычитка кода находит то, чего не видно в игре. Мёртвый контур урона по игроку, стирающий вещи ящик и списание денег до выдачи товара нашлись при чтении кода, а не в тесте. Тестирование проверяет то, о чём вы подумали; вычитка — то, о чём не подумали.
5. Оптимизируйте то, чего не видно. И следите за температурой железа, а не только за FPS: на 90 градусах вы меряете нагрев, а не производительность.
6. ИИ не заменяет игрока. Всё, что касается ощущений, — темп, сложность, «липкость» управления, атмосфера — приходит только от человека, который сел и поиграл. Нейросеть построит вам полностью рабочую игру, в которую скучно играть, и не заметит противоречия.
Игра сейчас в бете и лежит на itch.io по схеме «плати сколько хочешь» — можно и ноль. Разработка на паузе из‑за денег; в планах — Steam.
Скачать и поругать: xmen2026.itch.io/xenofarm. Фидбек по балансу и механикам очень нужен, многое ещё поменяется.

