Тридцать лет я жил с банальной мыслью: когда-нибудь надо написать свою игру. Примерно со времён первого Fallout. Но одно дело — иногда думать об игре, и совсем другое — представлять объём работы. Я не геймдизайнер, не художник, не композитор и не разработчик игровых движков. Зато я много лет работаю web-инженером и достаточно хорошо понимаю, сколько разных задач скрывается за словами «сделать игру».
Потом появились Claude, ChatGPT и примеры продуктов, которые одиночки доводят до релиза с помощью AI. Я решил проверить, что получится у меня.
Через десять рабочих сессий у меня была браузерная игра с 3D-графикой, правилами, одиночным режимом, онлайн-мультиплеером, восстановлением после перезагрузки и продакшеном на hexnome.com. Было написано 245 промптов, сделано 99 коммитов в основной ветке и выброшена первая реализация бэкенда ещё на 30 коммитов.
Это не инструкция «как попросить нейросеть сделать AAA-игру». Это история о декомпозиции, проверке гипотез, неприятных архитектурных решениях и о том, где вайбкодинг заканчивается, а инженерная ответственность остаётся.

От настольной игры к своему прототипу
Наша любимая семейная настолка — Azul: Queen’s Garden. Мы сыграли в неё, кажется, сотню партий, придумали несколько домашних правил и даже отправили идеи автору. Ответа не получили, зато вопрос «а почему бы не сделать свою версию идеи?» остался.
Hexnome — не цифровая копия Azul. Я взял знакомую основу: общий набор фишек, личное поле каждого игрока и очки за комбинации. Дальше механика ушла в свою сторону.
Название складывается из hex и genome. В игре шесть цветов и шесть значений — всего 36 разных тайлов. Значения обозначены символами из молекулярной биологии: спираль ДНК (1), пара хромосом (2), кодон (3), четыре основания (4), пентоза (5) и бензольное кольцо (6).
Поле собирается из «пластин» — семиячеечных цветков с якорем в центре и шестью лепестками. На каждом ходе можно сделать одно из трёх действий:
Take — забрать из общего источника все разные тайлы одного цвета или одного значения.
Put — положить тайл или пластину на своё поле и заплатить за размещение другими объектами из своего резерва.
Pass — выйти из текущего раунда. До следующего раунда игрок больше не действует.
Цена размещения тайла со значением V равна V − 1. Единица бесплатна, шестёрка требует пяти объектов. Платить можно тайлами того же цвета или значения, а ещё «стемами» — жетонами-джокерами. Новые стемы выдаются, когда игрок замыкает кольцо из шести тайлов вокруг внутреннего или внешнего якоря.
В каждом раунде меняются цели: сегодня очки дают, например, все единицы, двойки и зелёные тайлы; в другом раунде — другие значения и цвета. В финале отдельно считаются связанные группы одного цвета и одного значения.
В мультиплеере игроки не ломают чужие поля и не воруют предметы из чужого резерва. Единственное место прямого взаимодействия — общий источник: можно раньше соперника забрать набор, который он присмотрел. Низкую конфликтность Azul я не стал «исправлять» — для семейной puzzle-игры она мне нравится. Заодно это оказалось полезным ограничением для первой разработки.

Почему браузер, а не Unity
До Hexnome тестировал Unity и Godot. Оба прототипа помогли проверить гексагональную сетку, камеру и drag-and-drop, но до игры не дошли.
В какой-то момент я понял, что сравниваю не только движки. Я сравниваю расстояние до работающего продукта.
Даже успешный Unity-прототип пришлось бы собирать, распространять, просить людей скачать и установить. Для первой игры это лишняя преграда между мной и обратной связью. Браузерная версия даёт другой сценарий: открыть hexnome.com — 2 клика, и ты уже за столом.
Поэтому я сознательно уменьшил область неизвестного и вернулся к знакомому стеку:
Vue 3 и Vite для SPA;
Three.js через TresJS для 3D-сцены;
Pinia и Vue Router для состояния и навигации;
отдельный пакет игровых правил на чистом TypeScript;
NestJS, Prisma и MySQL для сервера;
Vitest для тестов.
Трёхмерность здесь только визуальная. Правила остаются плоскими: гексагональные координаты, соседние клетки, группы и ограничения размещения. Толстые тайлы, фаски, блики и анимации принадлежат слою представления. Игровое ядро ничего не знает ни о Vue, ни о Three.js.
Это решение позже сильно помогло: один и тот же общий пакет правил на TypeScript используется и на бэкенде, и в браузере.
Вайбкодинг на практике
Основная разработка шла в Claude Code. Все мои сообщения автоматически складывались в session-logs, а коммиты делались небольшими этапами. К моменту MVP журнал насчитывал:
Метрика | Значение |
|---|---|
Рабочих дат с промптами | 10 |
Моих сообщений | 245 |
Слов в них | 18 478 |
Коммитов в | 99 |
Коммитов только в отброшенной ветке | 30 |
Уникальных коммитов во всех ветках | 129 |
245 промптов — это не 245 вариантов «сделай красиво». Описания правил, диагностические сценарии, длинные архитектурные постановки — в среднем 75 слов на промпт.
Например, поворотным для мультиплеера стал не запрос про WebSocket, а формулировка модели состояния:
Для игры на трёх игроков должны существовать один общий источник, три резерва и три поля. Состояние игры одно и содержит все предметы во всех местах. Личность игрока определяет только то, какое поле показывать по умолчанию и какие действия разрешать.
Это уже не «вайб» в смысле свободного набора пожеланий. Здесь есть инвариант, границы владения и способ проверить результат. AI быстро превращал такие решения в типы, функции, компоненты и тесты. Моя задача состояла в другом: решить, что именно должно быть истинно, разбить путь на проверяемые куски и остановить движение, если куски перестают складываться в систему.
Первый фронтенд получился быстро
Первый коммит появился 29 июля. Через два вечера уже были гексагональная сетка, 3D-тайлы, пластины, резерв и базовый одиночный цикл без сервера.
Дальше правила нарастали органически. Пластина стала неделимым объектом со встроенным тайлом. Появился общий источник: закрытая пластина и четыре открытых тайла поверх неё. Затем — выбор по цвету или значению, оплата размещения, запрет дублей в связанных группах, якоря, стемы, раунды и финальный подсчёт.
Каждое правило сначала формулировалось, затем становилось чистой TypeScript-функцией с тестами и только после этого получало визуальное поведение. Когда размещение запрещено, ядро возвращает причину, а интерфейс превращает её в понятное сообщение. Когда завершается раунд, игра не показывает магическое число: она подсвечивает подходящие тайлы и считает их по одному.
В этот момент я, пожалуй, впервые почувствовал, что делаю не технологический демо-проект, а игру. Правила начали влиять друг на друга, создавать неочевидные позиции и иногда конфликтовать с тем, как я представлял их в голове.
Бэкенд, который почти работал
После локального single-player нужно было решить, как перейти к онлайн-игре.
Первая идея казалась логичной: сначала сделать одиночную игру с сервером. Каждый ход записывается в append-only журнал; при перезагрузке браузер получает журнал и восстанавливает состояние. WebSocket передаёт только индекс последней записи стека, а пропущенные команды клиент догружает по REST.
Эта часть заработала. Появились NestJS, Prisma, MySQL, игры с GUID и seed, лобби, места игроков, восстановление после refresh и серверная генерация колоды. Можно было создать стол, отправить ссылку второму игроку и начать партию.
Проблема проявилась не в инфраструктуре, а в модели.
Изначально фронтенд имел одно поле и один резерв. При добавлении игроков поверх него появился seatView: обёртка, которая должна была задавать вопрос существующей модели от имени конкретного места. Поле seat во многих местах было необязательным. Это сохранило сотни старых single-player тестов зелёными — и одновременно спрятало ошибки.
В реальной партии выяснилось:
два поля рисуются друг поверх друга;
взятый одним игроком тайл появляется в чужом резерве;
после обновлений два тайла занимают один слот, а refresh всё «исправляет»;
подсветка допустимого хода проверяет поле первого игрока.
Каждый отдельный баг исправлялся. AI честно находил очередной неограниченный accessor, добавлял seat, писал тест и двигался дальше. Но набор исправлений не сходился. Вопрос «какие ещё методы рендерер вызовет без контекста игрока?» нельзя было надёжно закрыть инспекцией: следующий ответ находился только во время партии.
На 10-м баге было принято решение: пробуем ещё одно исправление, а если останется проблема — откатываемся. Проблема осталась.
Первая серверная ветка дошла до состояния «можно играть, нельзя доверять». Я сохранил её как backend-attempt1, зафиксировал разбор причин и вернулся к коммиту до бэкенда. В ветке осталось 30 уникальных коммитов и три вечера.
Важно, что это был не сбой AI. Ошибочным было моё исходное архитектурное решение. Ещё важнее другое: модель не устаёт исправлять баги по одному и не испытывает инженерного дискомфорта. Решение признать локально разумную работу системно ненадёжной и выбросить её осталось за человеком.

Вторая попытка: сначала полная игра, потом сеть
Во второй попытке я поменял порядок разработки.
Сначала появился локальный hot-seat мультиплеер. Если игроков трое, в состоянии с самого начала существуют три поля, три резерва, один общий источник и одна очередь команд. Переключение игрока меняет viewport, но не создаёт другую версию мира. Рассинхронизировать два состояния невозможно, потому что состояние одно.
После этого в микробекенд был вынесен единственный действительно секретный кусок — генератор колоды. Сервер колоды ничего не знает о раундах, ходах и очках. Он умеет создать детерминированный набор по скрытому seed, выдать N кодов, принять сброс и при необходимости воспроизводимо перетасовать его. Клиент знает идентификатор игры, но не может посмотреть будущий порядок тайлов.
И только когда локальная модель выдержала игру на локальном мультиплеере, поверх неё снова появился настоящий онлайн.
Каждая команда имеет ссылку на предыдущую (prevSeq), а уникальный индекс в базе не позволяет двум командам стать детьми одной вершины. cmdId делает повтор успешной команды идемпотентным. WebSocket не несёт состояние — только сообщает, что очередь команд сдвинулась; периодический polling остаётся страховкой.

Что сделал AI / что сделал я
AI написал подавляющую часть кода, тестов, документации и commit messages. Он быстро превращал правила в функции, находил конкретные причины графических артефактов, измерял координаты, проверял HTTP-сценарии и прогонял партии в браузере. Скорость такого цикла для одиночного разработчика ещё недавно была бы невозможной.
Но игра появилась не из одного промпта и не из набора скриншотов. За разработчиком остаются сложные человеческие задачи:
выбрать достижимый MVP и отказаться от лишней платформы;
сформулировать правила и их инварианты;
заметить, что серия локальных исправлений маскирует неверную модель;
решить, когда продолжать, а когда выкинуть три дня работы;
сыграть реальные партии и отличить «тесты зелёные» от «система надёжна»;
отвечать за то, что уехало в продакшен.
Поэтому мой опыт вайбкодинга — не «не нужно уметь программировать». Скорее наоборот: опыт позволил не писать каждую строку руками, а тратить внимание на границы, риски и решения, которые меняют сразу сотню строк.
MVP
11 августа игра заработала на hexnome.com. Есть одиночный режим, онлайн-столы на двух–четырёх игроков, настраиваемые правила, восстановление состояния, просмотр чужих полей, индикаторы присутствия, встроенная книга правил и анимированный подсчёт очков.
Первая настоящая мультиплеерная партия сразу нашла ещё один race condition: сортировка резерва одного игрока успевала сдвинуть журнал перед ходом другого. Ошибка воспроизвелась локально и была исправлена тут же. Это было хорошим напоминанием: MVP — не состояние без ошибок, а состояние, в котором ошибки уже можно находить на настоящем продукте.
Длинного onboarding пока нет, поэтому мне особенно нужен взгляд человека, который не видел эти 245 промптов. Насколько быстро понятен общий источник? Объясняет ли интерфейс запрещённое размещение? Возникает ли желание строить большие группы и замыкать якоря? Достаточно ли взаимодействия в мультиплеере?
Откройте hexnome.com, сыграйте несколько ходов и напишите, где вы впервые остановились с вопросом «а что от меня хотят?». Для меня сейчас это полезнее ещё одного списка фич.
Репозиторий открыт и включает исходники, prompt logs и отдельную ветку неудачного бэкенда: https://github.com/Kasheftin/hexnome. Мне хочется оставить её не как мусор, а как часть разработки: иногда лучший коммит — тот, после которого честно возвращаешься назад.
