Собственный проект · закрытая бета
МежаРолевая книга, которую ведёт нейросеть
Текстовая ролевая игра для одного игрока: тёмная мистика, Петербург и Старая Ладога, лето 2012-го. Мастер собран из нескольких ИИ-ролей, кости бросает программа, а память кампании хранится в базе. Моя задача в том, чтобы длинная история не разваливалась: мир помнит, провал честный, герой не знает лишнего.
Пока Гольц идёт по вагону, присматриваюсь, что он отмечает в списке — не только же фамилии.
- успехВ колонке рядом с фамилиями — пометки «+» и «−»; напротив Кольцовой — «+»
- теньГольц поднял глаза и увидел, что ты смотришь в его список
Гольц держит планшет под углом, но вагон качает, и на повороте лист ложится к свету. Список отпечатан на кафедральном принтере: фамилии, группа, отметка о сборе. А правее — колонка, которой в таких списках не бывает.
Пометки карандашом: «+», «−», у большинства пусто. Напротив «Кольцова А. В.» стоит «+». Твоей строки не видно — её закрывает его большой палец.
Он доходит до вашего отсека, находит тебя глазами поверх очков — и ты понимаешь, что смотрел слишком долго.


Стол мастера на большом экране и та же лента на телефоне. Игру читают вечером и в дороге, поэтому тёмная тема в ней единственная. Снимки сделаны в демо-режиме игры.
01Замысел
Книга, а не переписка с ботом
В обычной ролевой игре с нейросетью модель делает всё сразу: придумывает мир, решает, удалось ли действие, и помнит историю, пока та помещается в окно. Поэтому такие игры быстро начинают поддакивать игроку и забывать собственные факты. В Меже обязанности разделены: сюжет и люди лежат в каноне, исход решают кости, время идёт по часам мира, а нейросеть пишет прозу внутри этих рамок.
- I
Провал честный
Кости бросает программа. Рассказчик получает результат как неизменяемый факт и не может его смягчить.
- II
Мир помнит
Факты, люди, вещи, отношения и тайны лежат в базе и подбираются к каждому ходу.
- III
Мир не ждёт
У событий своё расписание. Гроза начнётся в шесть вечера, даже если герой сидит в палатке.
- IV
Герой знает только своё
Незнакомка остаётся «девушкой с гитарой», пока герой не услышал её имя.
- 6 глав
- и 38 сцен в первой книге кампании «Осколок»
- 56
- персонажей, у каждого мотивы и голос, у 47 есть тайны
- 18
- вех, по которым засчитываются главы
- 776
- автотестов движка
- 202
- хода в сквозном прогоне всей книги
02Один ход
Что происходит, пока игрок ждёт первую строку
Игрок пишет одну реплику. Ответ приходит потоком, но до первого слова над ходом успевают поработать несколько ролей. Вот тот же ход из вагона, разобранный по шагам.
-
01Заявка
Одна реплика, до 500 символов. Кнопок с вариантами нет: мастер сам разбирает, где речь, а где действие, и решает, есть ли у него цена.
Пока Гольц идёт по вагону, присматриваюсь, что он отмечает в списке — не только же фамилии.
POST /campaigns/{id}/turn → поток событий SSE
-
02Арбитр
Быстрая модель возвращает строгий JSON по схеме: нужен ли бросок, какое свойство и навык, насколько трудно, что даст успех, провал и каждая тень. Арбитр видит настоящие имена и скрытые мотивы людей на сцене, потому что решать он должен честно.
{ "needs_roll": true, "attribute": "chutyo", "skill": "sysk", "barrier": 2, "modifier": 0, "label": "Разглядеть список Гольца", "stakes_success": "увидеть, что он отмечает", "stakes_fail": "ничего не разобрать в тряске", "stakes_shadows": "Гольц замечает взгляд"}Упрощено: в схеме ещё перемещение, время, адресаты реплики и встречный бросок.
-
03Жребий
Горсть считает и бросает программа: Чутьё 3 и Сыск 1 дают четыре кости. Восьмёрка и десятка приносят три зарубки, единица даёт тень. Преграда 2 пройдена, это «Сдюжил», но с осложнением. Нейросеть выбирает только навык и Преграду.
ЖребийРазглядеть список ГольцаСдюжил831013 зарубки1 тень -
04Рассказчик
Результат приходит к Рассказчику блоком «Жребий уже брошен (неизменяемый факт)» вместе с обязательными фактами успеха и тени. Он видит главу дословно, до 80 тысяч токенов, но не видит тайн, которые герою рано знать. Проза идёт потоком, 120–250 слов.
Гольц держит планшет под углом, но вагон качает, и на повороте лист ложится к свету. Список отпечатан на кафедральном принтере: фамилии, группа, отметка о сборе. А правее — колонка, которой в таких списках не бывает.
Пометки карандашом: «+», «−», у большинства пусто. Напротив «Кольцова А. В.» стоит «+». Твоей строки не видно — её закрывает его большой палец.
Он доходит до вашего отсека, находит тебя глазами поверх очков — и ты понимаешь, что смотрел слишком долго.
-
05Проверки
Готовый текст проверяют правила в коде и до шести моделей-судей параллельно. Если ход сломан, игрок видит, как текст заменяется: Рассказчик переписывает его с заметкой, что исправить. Переписываний не больше двух.
- Честность броскапровал не смягчён, тень дала осложнение
RollHonestyCheck - Утечка тайннет секретов, которые герою рано знать
LeakCheck - Слышимостьреплики слышат только те, кто рядом
EarshotCheck - Непрерывностьнет отката к прошлому ходу, вещи на своих местах
ContinuityCheck - Пространствопереход в другое место показан
SpatialCheck - Именанезнакомцы названы ярлыками, род не перепутан
unknown_named · name_leak
- Честность броскапровал не смягчён, тень дала осложнение
-
06Память
После ответа фоновый Экстрактор записывает новые факты, отношения и время. Каждый факт получает эмбеддинг в pgvector, чтобы через сто ходов найтись по смыслу, а не по словам. Следующий ход ждёт Экстрактор не дольше 20 секунд.
- факту Гольца в списке колонка пометок: «+», «−», у большинства пусто
- фактнапротив Кольцовой стоит «+»
- отношениеГольц заметил, что Савин смотрел в его список
- время18 июня, 08:30 · электричка Ладожский — Волховстрой
Пример того, что сохраняется после этого хода.
При смене сцены подключается Режиссёр: подводит итог прошлой сцены и ставит следующую. Ещё одна быстрая модель предлагает игроку 2–3 коротких действия, не называя незнакомцев по имени.
03Жребий
Кости, которые нельзя подкрутить
Броски идут по собственной системе «Жребий»: горсть десятигранных костей, свойство героя плюс навык. Набрал зарубок не меньше Преграды, значит «Сдюжил», на две больше означает «Верх». Ни одной зарубки, но есть тень, и это «Лихо»: мир делает ход против героя.
- 2–6пусто50 %
- 7–9зарубка30 %
- 10две зарубки10 %
- 1тень, осложнение10 %
Честность, которую можно проверить
Каждая кость выводится из секретного зерна кампании: HMAC-SHA-256 от номера броска и номера кости. Отпечаток зерна, его SHA-256, игрок видит с первого хода на билете главы и под каждым броском. Подменить зерно, чтобы переиграть неудобный бросок, нельзя: отпечаток перестанет совпадать.
В игре зерно хранится на сервере, игрок видит только отпечаток, а раскрытие зерна пока не включено. Здесь можно сделать то, что в игре ещё впереди: вскрыть зерно и пересчитать каждый бросок. Алгоритм перенесён из движка и сверен с ним на эталонных значениях.
кампания6f1c2b3a-4d5e-4f60-8a7b-9c0d1e2f3a4b
коммитпоявится, когда загрузится скрипт
Зерно создано в вашем браузере, он здесь играет роль сервера. Пока видно только отпечаток.
Выберите проверку: кости выпадут из зерна, отпечаток которого уже на билете.
04Знания героя
Мастер знает всё. Герой знает только то, что видел
В вагоне едет девушка с гитарой. Герой не знает, как её зовут, и игра не даст ему узнать это случайно: ни в прозе, ни в подсказках, ни на портрете.
Персонажи


девушка с гитаройимени не знаешь
Лена ЧижБригадир второй бригады; гитара у костра — её.
Что получает Рассказчик
девушка с гитарой (имени герой не знает — в прозе только так: «девушка с гитарой»; имя у него уже есть и оно одно, СВОЁ не выдумывай (ни фамилии, ни прозвища); прозвучит настоящее — тогда можно): …
Что видит Арбитр
Лена Чиж · хочет: зачёта для своих и тишины · боится: Гольца · врёт: «Горин к рыбакам ходит — там баба, ясно же»
canon/scenarios/oskolok/roster/lager.yaml
- slug: chizh name: "Лена Чиж" aliases: ["Чиж", "Лена", "бригадир второй бригады"] age: 21 yav: "Бригадир второй бригады; гитара у костра — её." look: "Девушка-бригадир, быстрая, в бандане; голос садится к концу песни, и все подпевают." wants: "Зачёта для своих и тишины." fears: "Гольца." lie: "«Горин к рыбакам ходит — там баба, ясно же»." secrets: - visibility: gm_only owner_may_reveal: true text: "Знает, что Горин «ходит к рыбакам», и искренне считает, что это баба; скажет между песнями." # дальше в карточке: голос, горсти, здоровье, то, чего она не сделает ни при каких условиях
- Ярлык вместо имени.Пока имя не прозвучало, в контексте Рассказчика его нет. Есть описание и запрет выдумывать своё.
- Имя открывает проза.Экстрактор отмечает имя известным, только если в тексте видно, откуда оно взялось: человек представился, его окликнули при герое, оно написано на бирке.
- Портрет вместе с именем.До этого в листе персонажей висит заглушка.
- Утечка переписывается.Если имя прозвучало раньше времени, ход переписывается. Проверка понимает падежи: «брат Ани» не считается утечкой, «Петрович» не путается с «Петровной».
- Тайны по вехам.72 тайны в каноне видит только мастер. Рассказчику достаются открытые, по номеру главы и взятым вехам: сон Ани не приснится раньше осколка.
05Часы мира
Мир не ждёт героя
У каждой главы своё расписание. Событие случается в назначенный час, где бы ни был герой. Стоял рядом, значит увидит сам. Был в другом месте, значит узнает позже: от человека, по следу или звонком. В Старой Ладоге связь ловит только на холме у крепости, и это тоже записано в каноне.
- Гроза над ВолховомСтарая Ладога
Первый гром за рекой около шести; с восьми ливень с юго-запада накрывает Волхов: палатки, тент над кухней, с десяти вода в траншее.
Как дойдёт новость: человек: Горин, не поднимая головы от помпы: «залило. Помпа промоину не берёт — стоит и стоит, хоть ведром вычерпывай» след: настил мокрый и тяжёлый, тент лежит в глине звонок: Аня с холма, когда ловит: «тут вода пришла, траншею залило, ты где?»
- Макс находит тему на форумеПетербург, комната 1312
Незнакомые люди пишут, что им снится одно и то же: вода и свет вдалеке.
Как дойдёт новость: звонок: смс, которое доходит, когда телефон ловит: «нашёл тему на форуме, люди пишут, что им снится одно и то же. бред? посмотри, как там вода»
- Гольц уезжает в ВолховДвор базы
До ливня уезжает на «Ниве»: «к утру хлеб; дорогу размоет — заночую там».
Как дойдёт новость: человек: Аня у тента: «Маркович в Волхов уехал, до дождя» след: у крыльца сухой прямоугольник там, где стояла «Нива»
- Палыч запирает воротаДвор базы
Закрывает ворота на крюк и обходит двор с фонарём; Жучка за ворота с ним не идёт.
Как дойдёт новость: человек: Палыч: «в одиннадцать крюк. Ночью ходить будете — снимайте сами» след: ворота изнутри на крюке, у сторожки кружка, ещё тёплая
- Горин уходит «к рыбакам»Траншея
Уходит от помпы за ворота без фонаря; возвращается в четыре мокрый по пояс, хотя дождь кончился в три.
Как дойдёт новость: человек: Палыч: «твой бригадир опять ночью шастал» след: у «Нивы» сапоги Горина в мокрой глине, трава к воротам примята полосой
- Промоина выносит слойРаскоп у северного вала
К рассвету промоина открылась на локоть шире и вынесла ржавый фонарь, часы «Командирские» и серый с зеленцой обломок.
Как дойдёт новость: человек: Аня у стенки за бровкой: «иди сюда. Тут часы. И фонарь. И вот это» след: на носилках у бровки фонарь «ФАР» и часы на прелом ремешке звонок: Аня, когда телефон ловит: «промоина вынесла, тут часы и фонарь, ты где?»
- Гольц звонит с холмаХолм у крепости
Поднимается туда, где единственное место со связью, и говорит по телефону долго, стоя на одном месте.
Как дойдёт новость: человек: Аня, глядя в кружку: «он там полчаса стоял. Я думала, с женой» след: трава на пятачке вытоптана кругом, в траве колпачок от ручки
- Носитель мёрзнет у костраДвор базы
Тот, у кого в кармане осколок, мёрзнет первым: в июне, у огня.
Как дойдёт новость: человек: Аня тихо: «у тебя он тоже не греется?» след: у костра чужой свитер поверх штормовки; Жучка от этого места отходит
- Заявку времени разбирает код.«До утра» значит 07:00, «до вечера» значит 19:00, «до утра двадцатого» означает точную дату. Такой разбор главнее оценки, которую даёт Арбитр.
- Каждый ход занимает время.Не меньше десяти минут. Большой скачок рисует в ленте карточку «Прошло…» с датами.
- Мир делает ход сам.Если игрок шесть ходов подряд ничего не меняет или сцена затянулась, у мира появляется свой ход.
- На тонких местах время рвётся.Внутри прошло меньше суток, снаружи несколько дней, и первая фраза после выхода должна дать это почувствовать.
06Канон
Один источник правды для текста, кода и базы
Мир, люди, места, главы и правила лежат в YAML: 32 файла, около 7 800 строк. Из этих же файлов собираются промпты, константы движка и стартовое состояние базы, поэтому описание мира и его поведение не расходятся.
canon/
- rules.yamlконстанты «Жребия», сутки, бюджеты контекста
- chapter1…6.yamlсцены, вехи, расписание мира
- roster/*.yamlлюди, мотивы, тайны
- places/*.yaml54 места и связи между ними
- llm_tiers.yamlмодели по ролям и тарифам
- Бриф мастера8,5 тыс. символов, около 3,4 тыс. токенов. Неизменная часть промпта Рассказчика, поэтому она кэшируется.
- ДвижокКости, сутки, Преграды и вехи читаются из канона, а не дублируются в коде.
- БазаМеста, люди, существа и законы мира засеваются в PostgreSQL одной командой.
- ТарифыКакая модель играет какую роль и сколько стоит её токен, тоже записано в каноне, а не зашито в код.
python tools/validate_canon.py
сценарий oskolok: файлов 29 мест 54 героев 3 NPC 60 существ 9 вещей 7 сцен 38 вех 18
0 ошибок, 12 предупреждений
Валидатор импортирует функции самого движка и проверяет ссылки между файлами, формулы горстей, условия вех, ворота времени, длину целей глав и то, что каждая тайна открывается существующей вехой.
07Проверка игрой
Один удачный ход ничего не доказывает
Ошибки ИИ-мастера видны не в отдельном ответе, а через двадцать ходов. Поэтому игру проверяет не только набор тестов, но и сквозные прогоны.
Автоигрок и критик
Модель с характером («осторожный сыщик», «наглый», «ломает мастера», «романтик») играет через API, другая модель оценивает каждый ход от 1 до 5 и пишет, что сломалось. На этих прогонах сравнивали модели: дешёвая получала в среднем 4,38 балла за $0,009 в ход, более сильная 4,69 за $0,027.
Ни одной вехи за 206 ходов
Разбор шестнадцати книг показал: ни одной взятой вехи. Причина была не в модели. События канона просто не наступали: гроза стояла на восьмой день, а ход длится десять минут игрового времени. Сцена с раскопом не открывалась ни разу.
Перестройка главы
Первая глава стала аркой «два дня», вехи разбиты на атомарные факты, которые копятся по ходам, время пошло сюжетными тактами, появился режим песочницы.
202 хода, 18 вех из 18
Третий сквозной прогон прошёл всю книгу, шесть глав от Ладоги до Хибин. После него появились финал «Книга завершена» и эпилог: Рассказчик пишет его по всей прожитой истории.
776автотестов движка
12тестов-инвариантов, у каждого подпись, от какого бага он защищает
0живых вызовов нейросети в тестах: защита в conftest бросает ошибку
08Экономика
Сколько стоит один ход
Каждый вызов модели пишется в журнал с ролью и ценой, расход виден по ходам и по ролям. Тариф выбирается при создании книги: на обычном мастер стоит около $0,04–0,08 за ход, на экономичном DeepSeek от $0,01 до $0,04, прозу GigaChat можно попробовать как эксперимент.
- Кэш префикса.Сообщения собираются так, чтобы начало не менялось: неизменный системный блок, затем история, которая только дописывается, и в конце изменчивая часть. Закэшированный вход у основной модели в десять раз дешевле: $0,25 против $2,50 за миллион токенов.
- Ложная проверка стоит денег.Ход без переписывания обходился примерно в $0,032, с одним в $0,047, с двумя в $0,072. Когда выяснилось, что половина переписываний за двое суток была ложной, проверки исправили и прогнали заново на ходах за неделю: старая логика остановила бы шесть ходов, новая остановила один, и это была настоящая утечка имени.

09Интерфейс
Полночный документ
Не меню фэнтези-игры, а вещь из нашего мира, в которую просочилась тьма: сажа, старая бумага, латунь. Проза набрана Literata, документы эпохи PT Mono, интерфейс системным шрифтом. Удача латунная, беда красная, а зелёного и жёлтого в игре нет.



- Книга прежде интерфейса.Проза занимает экран, всё служебное открывается по тапу и уходит. Никаких полосок здоровья, ачивок и пузырей чата.
- Одна рука.Поле ввода, бросок и лист снизу, под большим пальцем. На экране от 1280 пикселей лента превращается в стол мастера с панелями.
- Доступность.Лента объявляется экранным дикторам новыми абзацами, контраст посчитан для каждого цвета, при ограничении анимаций кости появляются сразу.
- Лица.55 портретов нарисованы нейросетью в одном стиле, по одному лицу на человека во всех книгах. Портрет стоил около двух с половиной центов.
- Без лишнего.Клиент на React без других зависимостей: свой роутер, поток ответа читается из fetch.

Что я вынес из Межи
вмежу.рфдоступ по приглашению
- Нейросеть пишет, программа решает.Кости, время, вехи и знания героя держит код. Модели остаётся то, что она делает лучше всего: живая проза.
- Проверять долгой игрой.Самые дорогие ошибки нашлись в полных прогонах: события, которые не наступают, и вехи, которые нельзя взять.
- Считать каждую проверку.Ложное срабатывание стоит денег и ожидания игрока, поэтому у проверок есть журнал и прогон на настоящих ходах.
Давайте знакомиться
Хорошие проекты
начинаются
с разговора.
Нужен технический партнёр, помощь команде или решение для рабочего процесса? Расскажите, что хотите изменить — обсудим, чем я могу помочь.
@work_ilyaЛично мне в Telegram

