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

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

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

Что за игра

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

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

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

Как делил работу

Роль

Кто

Что делал

Ведущий

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

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

Разработка

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

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

Звук

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

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

Выкладка

скилл деплоя

сборка образа, Dokploy, домен с HTTPS

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

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

Концепция до кода

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

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

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

Стек

  • 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" }
  ]
}

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

"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 лет, фальшиво-вежливая, '
                      'высокомерная. Говори очень быстро, звонко, по-офисному...'),
}

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

Первая версия озвучки получилась медленной и слишком низкой, её ускорили и подняли. Голос диктора записан локально, через 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.