InquirerDespite economic problems, Undas flower vendors strive to survivePunchUganda begins troop withdrawal from SomaliaThe Jerusalem PostRapper Ja Rule comes to Tel Aviv, speaks on legacy, aging, and Tupac-Biggie row - interviewZDF heuteEntdecken Sie das ZDF-Nachrichtenstudio20 MinutenWie viel weisst du noch aus deiner Kindheit? Erzähls uns!The South AfricanHow the Springboks can lose World Rugby No 1 ranking this weekendNHK 社会鳴子峡近くの5人けが事故 「アクセルとブレーキ踏み間違えた」VanguardCatholic Priest leads congregation in song for Obi, KwankwasoPremium TimesUniversity cancels final-year students’ exam over ‘wild jubilation’GMA News2 sakay ng habal-habal, patay matapos mabangga at madaganan ng dump truck sa Negros OrientalEl ComercioResultados ONPE en Villa María del Triunfo al 99.676 %: sigue aquí el conteo oficial de las Elecciones Regionales y Municipales 2026GhaflaDCI Arrests Man Who Allegedly Spent KSh43,320 Mistakenly Sent to His Phone
The Daily Newsstand · Free, Always
Monday, November 2, 2026

Текстовая RPG в Telegram: порядок полей JSON, бой с кубиками и цена одного хода

Translate

Я делаю текстовую ролевую игру в Telegram: игрок пишет, что делает его персонаж, а нейросеть отвечает за весь остальной мир. Рассказываю, как порядок полей в JSON влияет на согласованность ответа, как устроен бой с серверными бросками кубиков и сколько стоит один ход.

Привет, Хабр! Меня зовут Саид, мне 29 лет. Больше пяти лет я разрабатываю игры на Unity: делал их на заказ и работал в нескольких компаниях.

Но идея этой игры пришла не с работы, а из времени, когда я сам был игроком. В 15–16 лет я играл в текстовые ролевые игры на сайтах вроде AnimeVIP. У каждого игрока там свой персонаж, а за бои и за всё, что происходит в мире, отвечает модератор. Меня всегда тянуло к песочницам, где можно делать что хочешь. Но у таких игр была беда: модератор мог не отвечать целый день, и игра сильно затягивалась. Недавно я вспомнил те времена и подумал: а что, если роль модератора отдать нейросети? Ей не нужно ждать до вечера.

Так появилась AniWorlds — текстовая ролевая игра в Telegram. Я делаю её с конца августа 2026 года. Первые четыре версии я делал с ChatGPT. Сейчас актуальна шестая, и разработку ведёт ИИ-ассистент Claude. Игрок создаёт персонажа, выбирает мир и пишет, что делает. Всё остальное — что ответили люди вокруг, чем кончился удар, кто пришёл на шум — пишет нейросеть. В этой статье — как это устроено внутри, где ИИ ошибается и что с этим пришлось делать.

Карточка Саске: речь, действие, чувство и мысль. Ниже — сообщение игрока с применением способности.

Что за игра

Это песочница. Первый мир — по «Наруто»: Коноха и другие деревни, 462 места на карте, 260 персонажей, 13 групп со своими рангами и должностями. Канонического сюжета нет. Мир задаёт только обстановку: кто где живёт, кто чем занимается, кто с кем дружит или враждует. Дальше игрок делает что хочет: идёт в Ичираку за раменом, берёт задание у секретаря Хокаге, вызывает кого-то на спарринг или ссорится со всей деревней.

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

Способности персонажа описаны текстом: что они делают и как действуют на цель.

Ход пишется по простой памятке: действие — в звёздочках, речь — обычным текстом, способность — в "кавычках" внутри действия. Например, складываю печати "Каменная стена" Не подходи! — это действие со способностью и слова вслух.

Всё работает в Telegram Mini App. Ответ на ход занимает примерно 5–10 секунд. Сейчас открыт один мир, ещё шесть ждут доводки до того же уровня.

Как устроен один ход

Обычный игровой ход — это один запрос к модели, без цепочки агентов и повторных запросов для исправления ответа. Запрос состоит из четырёх сообщений:

  1. Правила игры — одинаковые для всех ходов и всех миров.

  2. Остальной мир — места и люди вне поселения, где сейчас персонаж игрока.

  3. Данные хода — где персонаж, кто рядом, их раны и вещи, что помнит каждый, записи прошлых ходов.

  4. Сообщение игрока.

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

Ответ — один JSON по схеме: 38 полей. Сервер раскладывает их по таблицам (раны, место, вещи, память, отношения), а текст показывает игроку как есть. Здесь у меня есть жёсткое правило: сервер не переписывает ответ ИИ. Не обрезает, не «исправляет» исход, не просит модель ответить ещё раз. Если поле не легло в базу, это ошибка моего кода обработки, а не повод менять то, что увидел игрок.

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

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

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

У персонажа сохраняются отношения, связи, положение, душевное состояние и то, что он собирается делать.

На это ушло больше всего времени. Рассказчик у меня работает с выключенным режимом размышлений (reasoning). В ответе JSON поля появляются последовательно: каждое следующее может опираться на уже написанные выше. Отсюда два вывода, к которым я пришёл на собственных ошибках.

Решение должно стоять выше текста, который от него зависит. Если поле «попал ли удар» стоит ниже красивого описания боя, то описание уже написано, и решение на него не влияет. У меня так и было: исход рассказывал одно, а поле боя ниже решало другое. Лечится не уговорами в правилах, а порядком полей в схеме: сначала короткое скрытое решение, потом текст.

Одно решение — одно место. Если одно и то же решают два правила или два поля, модель выбирает одно из них. В ответе получается противоречие. Пример: правило о спутниках и правило о переходах по отдельности решали, кто идёт за персонажем игрока. В итоге люди то шли, то оставались. Теперь правило такое: каждое решение живёт в одном месте, а остальные правила только ссылаются на него.

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

Бой: кубики бросает сервер, рассказывает модель

Дерётся за каждого персонажа модель, по его данным, но удачу решает не она. У каждого четыре характеристики: сила, ловкость, выносливость и владение способностями. У каждой шесть ступеней, от «слабой» до «сверхчеловеческой». Сервер каждый ход бросает каждому участнику два кубика d6. Модель считает счёт и описывает результат.

  • Счёт — очки за ступень (слабая 2, обычная 4 … сверхчеловеческая 12) плюс бросок.

  • Преимущество — не плюс к очкам, а шанс: с ним берётся больший из двух кубиков, без него — первый.

  • Защищается только тот, кто в прошлой карточке встал в стойку. Остальных удар застаёт врасплох.

  • Исход — по разнице: 0 и меньше — защитился, 1–2 — задел, 3–5 — попал, 6 и больше — попал точно.

Пример: персонаж игрока с хорошей ловкостью бьёт Рока Ли с исключительной ловкостью. Ли в прошлом ходу встал в стойку.

Кто

Ступень

Очки

Кубики

Берётся

Счёт

Персонаж игрока (бьёт)

хорошая

6

4 и 2

4

10

Рок Ли (защищается)

исключительная

10

2 и 5

2 (преимущества нет)

12

Разница 10 − 12 = −2: Ли увернулся. Запас 2 — меньше трёх, поэтому ответить ударом в том же исходе он не успел. Игрок видит не цифры, а текст: Ли ушёл от удара и снова стоит в стойке.

Цена способностей, запас сил, обморок от истощения и откаты считает сервер. Модель получает уже готовую строку: сколько сил осталось и что ещё на откате.

Сколько стоит ход

Запрос большой: на входе 45–53 тысячи токенов. Если отправлять его целиком по полной цене, ход стоил бы дорого. Поэтому правила и «остальной мир» стоят в начале запроса и попадают в кеш. По 71 ходу с сервера за 27–30 октября:

Часть хода

Токены (медиана)

Цена

Из кеша: правила и остальной мир

41,3 тыс.

≈ 0,08 ₽

Без кеша: данные хода

7,3 тыс.

≈ 0,15 ₽

Ответ

794

≈ 0,08 ₽

Ход целиком

медиана 0,34 ₽, среднее 0,57 ₽

Среднее выше медианы из-за промахов кеша: ход без кеша стоит около 1,25 ₽, потому что правила и мир заново записываются в кеш. Таких ходов 19 из 71, и 14 из них — первый ход нового мира. В журнале в основном короткие тестовые миры, а чем дольше игра идёт в одном мире, тем ближе среднее к медиане.

Самая дорогая утечка оказалась глупой. В середине октября «остальной мир» выпадал из кеша в 21 ходе из 91. Одна из причин: номера групп в данных выдавались заново каждый ход, и Коноха была то «с1», то «с2». Одного изменившегося символа в начале хватало, чтобы кеш промахнулся. Теперь у каждой группы свой номер навсегда.

Цель — не дороже 0,50 ₽ за ход к концу разработки. Игроку ход стоит 1,50 ₽. Кроме модели, у игры есть расходы, которые не зависят от числа ходов: сервер и база данных оплачиваются каждый месяц, домен и сертификат — каждый год. Разница в цене покрывает их, и часть остаётся мне как плата за разработку.

Как я проверяю правила: 10 ответов, а не один

Одного удачного ответа мне недостаточно. Поэтому любая правка правил проверяется замером: тот же сохранённый запрос из живой игры отправляется модели 10 раз, и я считаю, в скольких ответах ошибка осталась. Раньше я брал по 20 ответов на один ход, но после нескольких прогонов это стало слишком дорого и сильно било по бюджету. Теперь 10, и ещё 10 — только если результат на грани. Для своей разработки я пока считаю такую систему проверки наиболее подходящей по затратам и пользе. Мой критерий приёмки — 10 ответов из 10 без проверяемой ошибки в выбранном сценарии. Это не гарантирует, что она не встретится в другом ходе. Если ошибка стала реже, записываю «было 6 ответов с ошибкой, стало 2», а не «исправлено». Таких замеров уже 415, каждый записан в журнал: что меняли, на каком ходу, сколько ответов вышло верными.

Самый болезненный урок был в конце октября. Я правил правила по замечаниям: нашли фразу, которая сбивает модель, — поправил фразу. По отдельности правки выглядели разумно, но вместе создали новые противоречия. В живой игре персонажи стали ходить за героем туда, куда их не звали. Пришлось откатить игру к версии двухдневной давности. Теперь порядок такой: перед любой правкой перечитать весь запрос целиком, найти все места, где решается то же самое, и переписать правило полностью, а не дописывать оговорку. Затем правку проверяют два «читателя» — новые сессии ИИ без контекста, которые читают запрос и пишут по нему ответ. Только потом выкладка и 20 живых ходов в новом мире. «Читатели» — это новые сессии того же Claude, который ведёт разработку.

Разработка с ИИ — это не «рассказал идею и жди»

Код я сам действительно не пишу. По ощущениям моя работа ближе к работе тимлида: объяснить задачу, проверить результат и показать, где он расходится с задуманным. Ассистент может уверенно написать «готово», хотя в игре ошибка осталась. Тогда нужно объяснить, что именно я увидел, почему это неверно и как должно быть.

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

Даже то, как ассистенту писать правила для рассказчика, пришлось оформить отдельным документом по руководствам Google, Anthropic и OpenAI. Поддерживать документацию и следить за тем, чтобы новые механики не ломали старые, приходится мне. Код пишет ассистент, а за то, что получилось, отвечаю я.

Мир собирается отдельно от кода

Код игры не знает, что такое Коноха. Все миры собираются в отдельном редакторе Studio: места, персонажи, группы, ранги, должности, виды существ. Мир проходит проверки, публикуется и импортируется на сервер. Правила игры общие для всех миров. Поэтому мир по «Магической битве» или совсем обычный современный город без магии будет работать по тем же правилам, что и «Наруто».

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

Что дальше

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

  • Доделать бой, чтобы он заканчивался сам: сейчас противники иногда бьются бесконечно.

  • Персонажи, которые живут своей жизнью вдали от игрока, и события, о которых знает вся деревня.

Итог

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

Если вы делали что-то похожее, расскажите в комментариях: как вы держите память персонажей через сотни ходов и не даёте модели решать одно и то же в двух местах?

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.