17 KiB
RPG-режим: полный алгоритм запросов (шпаргалка)
Документ описывает, какой HTTP-запрос что получает и что делает, когда RPG включён полностью (rpg_enabled = 1) и в настройках активны все опции мастера:
{
"dice": true,
"narrator": true,
"quests": true,
"affinity": true,
"choices": true,
"stats": false
}
Шкалы (
lust/stamina/tension): по умолчаниюstats: false. Если включить чекбокс «Шкалы», после каждого хода добавляется веткаstats_delta(см. § «Вариант: stats включены»).
Пример проходит от создания чата до 3-го сообщения игрока, с перекрытием основных веток (диалог / d20 / кнопка выбора / сдвиг сюжета / факты / SD).
Модели и роли
| Роль | Env / модель по умолчанию | Где вызывается |
|---|---|---|
| Персонаж (чат) | CHAT_MODEL → mistralai/mistral-nemo |
POST /chat/stream → stream_message() |
| Нарратор pre/post | RPG_NARRATOR_MODEL → deepseek/deepseek-chat-v3 |
Перед/после ответа персонажа |
| Сюжетная арка | RPG_PLOT_MODEL → DeepSeek v3 |
Opening, fallback если арки нет |
| Факты | RPG_FACTS_MODEL → DeepSeek v3 |
После каждого ответа персонажа |
| SD-сцена | SD_PROMPT_MODEL или SYSTEM_MODEL |
Opening + конец каждого stream |
| Появление (prose) | SYSTEM_MODEL |
Только при сохранении тегов персоны (не в ходе RPG) |
Персонаж не пишет affinity/stats в БД — только читает их из runtime_suffix. Пишет только narrator post (affinity_delta, stats_delta, scene_update, …).
Слои промпта (что видит CHAT_MODEL)
llm_system = static_prompt + runtime_suffix [+ context_warning]
| Слой | Источник | Когда меняется |
|---|---|---|
| static_prompt | get_system_prompt() — persona + lorebook (последние 5 реплик + текущий user) + ROLEPLAY_GUARDRAILS |
Каждый stream; в messages хранится только static (без RPG) |
| facts_block | До 20 фактов из facts_json |
После extract_facts на прошлом ходу |
| PlotArc | title, phase, next_beat_hint |
При генерации/сдвиге арки |
| Status quo | sessions.status_quo |
narrator pre/post, opening |
| Scene | sessions.scene_json |
narrator pre/post, opening |
| Relationship | affinity + тон |
narrator post (affinity_delta) |
| Character state | lust/stamina/tension | Только если stats: true |
| Narrator directives | narrator pre (текущий ход) | Эфемерно, не в БД |
| Mechanics | d20 + outcome (текущий ход) | Только если была проверка |
| Context warning | ~N% of CHAT_CONTEXT_MAX |
Если оценка > 85% |
История в LLM: все user / assistant из БД + один объединённый system.
Фаза 0: создание RPG-чата (UI → API)
0.1 PATCH /sessions/{id}
Тело (пример):
{
"persona_id": "card_abc",
"rpg_enabled": true,
"genre": "fantasy",
"rpg_settings_json": "{\"dice\":true,\"narrator\":true,...}"
}
Действия: запись в sessions — флаги RPG, жанр, настройки. Сообщений ещё нет.
0.2 POST /chat/init
Тело: { "session_id", "persona_id", "first_mes_override"? }
Действия:
get_system_prompt(persona, [], "")→ static (персона, lorebook, guardrails).upsert_static_system_message— одна строкаrole=systemвmessages.- Если истории нет —
add_message(assistant, first_mes)из карты / override.
LLM: не вызывается.
БД после: messages = [system, assistant(first_mes)].
0.3 POST /chat/opening/process
Тело: { "session_id", "persona_id", "rpg": true }
Последовательность только на opening (без narrator_pre, без stream персонажа):
sequenceDiagram
participant UI
participant API as opening/process
participant Plot as RPG_PLOT_MODEL
participant Nar as RPG_NARRATOR_MODEL
participant SD as SD_PROMPT_MODEL
UI->>API: POST opening/process
API->>Plot: generate_plot_arc(first_mes, persona, genre)
Plot-->>API: plot_arc_json
API->>API: seed rpg_quests from beats
API->>Nar: narrator_post OPENING
Nar-->>API: status_quo, scene, affinity_delta, outfit, choices, quests
API->>API: apply_narrator_post → sessions
API->>SD: generate_sd_prompt + run_sd_for_message
API-->>UI: plot_arc, quests, affinity, choices, image_*
| Шаг | Модель | Вход | Выход в БД / UI |
|---|---|---|---|
| 1 | generate_plot_arc |
имя, description, scenario, first_mes, facts, genre | plot_arc_json; квесты из beats[].title |
| 2 | narrator_post(is_opening=true) |
контекст: assistant: {first_mes}; arc; facts |
status_quo, scene_json, outfit_json, affinity += delta, квесты, choices (в ответ API, не в messages) |
| 3 | generate_sd_prompt |
история, outfit, scene_json | картинка на last assistant id |
Особенности opening для narrator post:
- Просит заполнить
scene_updateиз greeting + scenario. - Просит non-zero
affinity_delta, если в first_mes явный тёплый/враждебный тон. outfit_update— начальная одежда из greeting (danbooru-теги).
UI после: reloadChatFromServer, панель квестов, 💖 affinity, кнопки choices, картинка SD.
Состояние сессии перед 1-м сообщением игрока
| Поле | Пример |
|---|---|
messages |
system, assistant(first_mes) |
plot_arc_json |
сгенерированная арка |
status_quo, scene_json, outfit_json |
из narrator opening |
affinity |
0 или ±1..2 после opening |
facts_json |
[] |
narrative_stats_json |
{"lust":0,"stamina":10,"tension":0} (если stats выкл. — всё равно дефолт в БД) |
Сообщение игрока №1 — обычная реплика (без d20)
Пример: игрок пишет: «Привет, как тебя зовут?»
UI: POST /chat/stream
{
"session_id": "...",
"persona_id": "...",
"message": "Привет, как тебя зовут?",
"is_narrator_choice": false
}
Этап A — подготовка (до stream)
- static_prompt =
get_system_prompt(persona, history, message)— lorebook по последним 5 репликам + guardrails. - RPG pre (narrator):
- Вход user: persona, user action, Global plot (= full arc JSON), Facts, Recent (до 8 реплик).
- Модель:
RPG_NARRATOR_MODEL. - Ожидание:
needs_check: falseдля чистого диалога. - Эффекты:
directives→ попадут вnarrator_extra.status_quo_update→UPDATE sessions.status_quo.scene_update(partial) → merge вscene_json.- Нет d20, нет bubble «Рассказчик».
- runtime_suffix =
build_rpg_runtime_suffix(session)+narrator_extra(directives только). upsert_static_system_message(static)— в БД system без RPG-блоков.add_message(user, message).- context_usage — если > 85%, в конец system добавляется
[Context: ~N% …]. - llm_messages = system(static+runtime) + вся история user/assistant.
Этап B — SSE stream
| Событие SSE | Когда |
|---|---|
{ "chunk": "..." } |
Поток CHAT_MODEL — ответ персонажа |
{ "done": true, ... } |
После сохранения assistant в БД |
Персонаж: один запрос stream, читает affinity/scene/status/arc/facts/directives, не меняет счётчики.
Этап C — post-process (после ответа персонажа, тот же HTTP stream)
Порядок внутри generate():
| # | Действие | Модель | Условие |
|---|---|---|---|
| C1 | generate_plot_arc |
PLOT | Только если plot_arc_json пуст (редко после opening) |
| C2 | should_advance_arc(user_message) |
код | Ключевые слова: отдых → event_driven:rest, путь → travel, помощь → help_request |
| C3 | pop_matching_beats + injection |
— | Если trig совпал с beat; choices из beat |
| C4 | advance_phase |
— | Если beats пусты — фаза opening→hook→… |
| C5 | extract_facts |
FACTS | Последние 10 реплик; merge до 80, в промпт 20 |
| C6 | narrator_post |
NARRATOR | Контекст: последние 8 реплик включая новый ответ |
| C7 | apply_narrator_post |
— | status_quo, affinity_delta, scene, outfit, quests, stats_delta* |
| C8 | generate_sd_prompt + Comfy |
SD | outfit + scene_json + последние 6 реплик |
| C9 | SSE done |
— | choices, affinity, quests, image_*, debug |
* stats_delta только при rpg_settings.stats === true.
Для реплики №1 (диалог): narrator pre → no check; post → facts, возможно choices, affinity ±, scene/status.
Сообщение игрока №2 — действие с проверкой d20
Пример: «Пытаюсь перепрыгнуть через пропасть»
Тот же POST /chat/stream. Отличия в этапе A:
flowchart TD
pre1[narrator_pre без броска]
need{needs_check AND dice?}
roll[d20 1-20]
pre2[narrator_pre с roll+outcome]
res[resolution_text + action_resolutions]
chat[CHAT_MODEL stream]
post[narrator_post + facts + SD]
pre1 --> need
need -->|да| roll --> pre2 --> res
need -->|нет| chat
res --> chat
chat --> post
| Шаг | Что происходит |
|---|---|
| pre (фаза 1) | needs_check: true |
| Бросок | random 1..20 → outcome: crit fail / fail / success / crit success |
| pre2 | Тот же user + Roll d20=N, Outcome=... → resolution_text обязателен |
| UI | SSE { "narrator": { roll, outcome, text } } до chunks — bubble «📖 Рассказчик» |
| runtime | + --- Mechanics --- (d20, outcome, «не противоречь») |
| БД | action_resolutions — intent, roll, outcome, resolution_text |
| Персонаж | Должен согласовать ответ с resolution + mechanics |
Post-process (C) — тот же, что на ходу 1. Facts могут записать «игрок не перепрыгнул», quest может обновиться.
Сообщение игрока №3 — кнопка выбора + сдвиг арки
Пример: игрок жмёт choice «Отправиться в лес» (label уходит как user message)
или пишет: «Идём дальше в лес»
UI: sendMessage(label, true) → is_narrator_choice: true
| Отличие | Поведение |
|---|---|
| Текст в БД | "[Player chose: Отправиться в лес]" вместо сырого label |
| pre/post | Как обычный ход; narrator видит метку выбора |
| should_advance_arc | Если в тексте есть «идем дальше», «в путь», … → event_driven:travel |
| beats | pop_matching_beats(arc, trig) — до 1 beat; injection в debug; choices из beat + post |
| phase | Если beats опустели → advance_phase (например hook → complication) |
Остальной pipeline = stream №1 или №2 (в зависимости от needs_check).
Сводная таблица: кто что обновляет (ход 1–3)
| Данные | Opening | narrator pre | CHAT_MODEL | narrator post | extract_facts | arc engine |
|---|---|---|---|---|---|---|
| messages | first_mes | — | user + assistant | — | — | — |
| plot_arc_json | create | — | read | read | — | advance/beats |
| status_quo | post | pre/post | read | post | — | — |
| scene_json | post | pre/post | read | post | — | — |
| outfit_json | post | — | read | post | — | — |
| affinity | post | — | read | delta | — | — |
| facts_json | — | read | read | post.facts* | merge | — |
| rpg_quests | seed | — | — | post | — | — |
| action_resolutions | — | d20 path | — | — | — | — |
| narrative_stats | default | — | read** | stats_delta** | — | — |
* facts из post в JSON схеме narrator — сейчас основной путь facts = extract_facts.
** только при stats: true.
Варианты настроек (перекрытие)
narrator: false
- Нет narrator pre/post, нет directives/mechanics bubble.
runtime_suffix= толькоbuild_rpg_runtime_suffix(facts, arc, status, scene, affinity).- Post-process: нет C6–C7; facts/arc/SD могут остаться (arc/SD/facts всё ещё в коде stream).
dice: false
needs_checkот pre игнорируется — всегда ветка без броска (как диалог).- pre2 и mechanics не вызываются.
affinity: false
- Нет блока Relationship в runtime;
affinity_deltaне применяется.
quests: false
- Opening не сидирует квесты; post не вызывает
upsert_quest.
choices: false
- UI не рисует кнопки; post/beat choices не добавляются в SSE.
stats: true
- В runtime: блок lust/stamina/tension (0–10).
- post:
stats_deltaкаждый ключ −2..+2, clamp 0–10. - Дефолт stamina = 10.
is_narrator_choice: true
- User content =
[Player chose: …].
Контекст > 85%
- В system добавляется одна строка-предупреждение (не в БД).
Вспомогательные запросы (не в примере 1–3, но в RPG)
| Запрос | Когда |
|---|---|
GET /chat/system/{id} |
Панель system blob: static, runtime-превью, scene, stats, context_usage |
GET /chat/history/{id} |
Перезагрузка чата |
POST /chat/rpg/bootstrap |
RPG на старом чате: arc + narrator_post opening + apply (как opening, без нового first_mes) |
POST /chat/ (не stream) |
Упрощённый ход: static + runtime, один ответ, без narrator pre/post |
Пример хронологии (3 сообщения, все опции ON)
| # | HTTP | LLM-вызовы | Заметки |
|---|---|---|---|
| 0 | PATCH session | — | rpg_enabled |
| 0 | POST init | — | system + first_mes |
| 0 | POST opening/process | Plot, Narrator post, SD | arc, scene, affinity, quests, choices |
| 1 | POST stream | Pre (no check), Chat, Facts, Post, SD | Диалог; +facts; choices в done |
| 2 | POST stream | Pre (check), Pre2+d20, Chat, Facts, Post, SD | Bubble рассказчика; action_resolutions |
| 3 | POST stream | Pre, Chat, Facts, Post, SD; arc travel? | [Player chose:…] или текст; beat injection |
Порядок SSE на ходу 2:
narrator(d20)chunk× Nimage_generating(если SD)done(choices, affinity, quests, debug)
Файлы для углубления
| Тема | Файл |
|---|---|
| Stream + RPG | routers/chat.py → chat_stream |
| Opening | services/opening.py |
| Narrator JSON | services/rpg_narrator.py |
| Runtime blocks | routers/chat.py → build_rpg_runtime_suffix, services/rpg_state.py |
| Persist post | services/rpg_state.py → apply_narrator_post |
| Arc / beats | services/rpg_plot.py |
| Facts | services/rpg_facts.py |
| UI stream | static/js/chat.js → sendMessage, consumeStream |
| New chat | static/js/newChatWizard.js |
| Context % | services/context_budget.py |
Обновлено под текущую реализацию RP UX (anti-OOC, scene_json, context blob, rpFormat, bootstrap narrator).