Как я создал браузерную игру за 2 часа 37 минут, разделив задачи между Fable 5.2 и Opus 5.5

Прокомментировать Просмотры: 2

Недавно обсуждали интервью HR-директора крупной IT-компании: принимая сложные решения, она прислушивается к ощущениям в теле. Примерно тогда же вышел Fable 5.2. Я решил воспользоваться поводом, испытать модель и создать игру о собеседовании в вымышленной корпорации, где оффер выдают по натальной карте.

От первого сообщения модели до запуска игры на собственном домене прошло 2 часа 37 минут. Рассказываю, как распределял задачи между моделями, как устроен код, где возникли ошибки и что пришлось исправить вручную.

Игра: Rдкс, собеседование в корпорацию. Прохождение занимает 2–3 минуты. Игра работает в браузере и ничего не отправляет на сервер.

Что это за игра

Игра стилизована под российские FMV-квесты начала 2000-х: вместо графики — фотографии, вспышка прямо в лицо, резкие переходы от общего плана к крупному и намеренно некачественная озвучка.

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

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

Как распределялась работа

Роль

Исполнитель

Задачи

Ведущий

Fable 5.2, основная сессия

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

Разработка

два субагента на Opus 5.5

параллельная разработка движка расчёта по дате и движка сцен

Звук

скилл саунд-дизайна

озвучка реплик через GPT Audio, локальная запись диктора, сведение

Публикация

скилл деплоя

сборка образа, Dokploy, подключение домена с HTTPS

Логика распределения была такой: Fable как фронтир-модель лучше всего справляется с творческими задачами — концепцией, тоном, визуальным стилем и разветвлённым сценарием. Opus хорошо пишет код, а доступные лимиты позволяют запускать сразу нескольких агентов. Sonnet я обычно использую для рутины и миграций, но в этом проекте он не понадобился.

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

Сначала концепция, потом код

Первые 56 минут мы потратили не на код, а на документ. Я надиктовал идею и предоставил два референса: скриншот FMV-игры начала 2000-х и рилс с «ночной экскурсией по цеху», снятой на мыльницу со вспышкой. Fable изучил материалы и подготовил концепцию из 10 разделов: тон и границы юмора, визуальный язык, сцены, финалы, формула расчёта, 25 должностей, список необходимых материалов, техническая часть и подзадачи.

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

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

Стек

  • Vite + TypeScript, без игрового движка. Сцена состоит из DOM-элементов.

  • Примерно 2 700 строк TypeScript, включая тесты.

  • Бэкенда нет. Прогресс и открытые финалы сохраняются в localStorage.

  • Аналитика — собственный Umami. Отслеживаются старт, сцены, прохождение или провал вайбометра, финал, шеринг и переход по ссылке.

Сценарий в формате данных

Ключевое архитектурное решение — хранить сценарий в JSON отдельно от движка диалогов. В script.json содержатся 50 узлов, 6 сцен и 7 финалов. Вот как выглядит узел с выбором:

"g_hey": {
  "bg": "guard",
  "speaker": "guard",
  "text": "Эй. Сударь или сударыня?",
  "choices": [
    { "text": "Сударь",   "set": { "gender": "m" }, "to": "g_form" },
    { "text": "Сударыня", "set": { "gender": "f" }, "to": "g_form" }
  ]
}

Условные переходы тоже описаны в JSON, без вмешательства в код:

"g_check": {
  "note": "Возрастной фильтр. Порог правится здесь.",
  "speaker": "guard",
  "text": "На собеседование. Ну-ну. Дай-ка анкету.",
  "next": [
    { "if": { "ageGte": 50 }, "to": "g_old" },
    { "to": "g_vibe_intro" }
  ]
}

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

Натальная карта по Миусу

Поскольку игра связана с гороскопами, хотелось рассчитывать карту по-настоящему. Субагент на Opus реализовал упрощённые вычисления по книге Жана Миуса «Астрономические алгоритмы»: долготу Солнца — по главе 25, долготу Луны — по основным членам таблицы 47.A. Точность составляет около 0,1°. Время рождения игра не запрашивает, поэтому расчёт ведётся на полдень по Москве.

export function julianDayNoonMsk(y: number, m: number, d: number): number {
  return Date.UTC(y, m - 1, d, 9, 0, 0) / 86400000 + 2440587.5
}

const jd = julianDayNoonMsk(y, m, d)
const sunLon = sunLongitude(jd)
const moonLon = moonLongitude(jd)

const moon = Math.floor(moonLon / 30) as Sign
const elong = norm360(moonLon - sunLon)
const phase = Math.floor(norm360(elong + 22.5) / 45) % 8   // 8 фаз, 0 = новолуние

Дальше вступает в силу простая игровая логика. Знак Солнца определяет пару должностей, а стихия Луны выбирает одну из них: огню и воздуху достаётся «громкая», земле и воде — «тихая». Сумма цифр даты, сведённая к числу от 1 до 9, определяет грейд. Новолуние приводит к отказу. Если Солнце и Луна находятся в одном знаке, а число даты равно 9, игрок получает легендарную 25-ю должность с совместимостью 146%.

Распределение проверили тестом, перебрав все 22 280 дат с 1950 по 2010 год. Отказ выпадает в 10,96% случаев, легендарная должность — в 1,49%, а каждая из остальных 24 ролей — в 3,9–4,3%. Проверка показала, что распределение достаточно ровное и ни одна роль не появляется подозрительно часто.

Ссылка на финал без бэкенда

Результатом можно поделиться. Поскольку сервера нет, данные кодируются непосредственно в ссылке: должность, Луна, фаза, число, совместимость и флаг отказа занимают 22 бита. К значению применяется XOR с константой, после чего оно записывается пятью символами base36 и дополняется контрольным символом. Ссылка вида ?f=abc12x открывает чужой финал, а повреждённый код проверка отклоняет.

Изображения

Всего в игре 24 изображения: фоны, персонажи, крупные планы и карточки финалов. Их создавали через наш MCP-сервер изображений, который обращается к моделям через OpenRouter. Fable самостоятельно выбрал этот сервер, когда перешёл к визуальной части. Затем все кадры обработали одним скриптом «под 90-е», чтобы изображения из разных генераций выглядели так, будто сняты одной камерой.

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

Озвучка с помощью GPT Audio

Для озвучки реплик использовали openai/gpt-audio через OpenRouter. Скрипт извлекает текст из JSON и задаёт каждому персонажу голос и роль в системном сообщении:

VOICES = {
 'guard': ('onyx',    'Ты озвучиваешь охранника в дешёвой русской видеоигре 2000-х. '
                      'Мужчина 55 лет, грубый, подозрительный, хамоватый. Говори быстро, отрывисто...'),
 'alina': ('shimmer', 'Ты озвучиваешь рекрутера Алину... Девушка 25 лет, фальшиво-вежливая, '
                      'высокомерная. Говори очень быстро, звонко, по-офисному...'),
}

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

Первая версия озвучки звучала слишком медленно и низко, поэтому её ускорили и повысили высоту голоса. Дикторский текст записали локально с помощью VoiceStudio на Mac. Всего в игре 49 звуковых файлов.

Деплой

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

Хронология

Время

Событие

0:00

Идея и два референса

0:56

Утверждение концепции

1:01–1:21

Два субагента на Opus создали движки

1:15–1:40

Доработка визуала и текстов

2:05–2:17

Деплой, подключение домена, превью для соцсетей, исправление музыки

2:21–2:37

Озвучка через GPT Audio, ускорение голосов, релиз

Что пришлось исправить вручную

  • Превью для соцсетей. Первый вариант выглядел шаблонно, поэтому подпись я переписал сам.

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

  • Голоса. Они звучали слишком низко и медленно, поэтому их пришлось скорректировать и ускорить.

  • Ритм шуток. Модель предложила множество вариантов, но выбирать действительно смешные пришлось мне.

Итог

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

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

Автор: Илья Пушин, Pushers.

 

Источник

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов