Files
2026-06-05 14:57:15 +03:00

17 KiB
Raw Permalink Blame History

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_MODELmistralai/mistral-nemo POST /chat/streamstream_message()
Нарратор pre/post RPG_NARRATOR_MODELdeepseek/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"? }

Действия:

  1. get_system_prompt(persona, [], "") → static (персона, lorebook, guardrails).
  2. upsert_static_system_message — одна строка role=system в messages.
  3. Если истории нет — 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)

  1. static_prompt = get_system_prompt(persona, history, message) — lorebook по последним 5 репликам + guardrails.
  2. 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_updateUPDATE sessions.status_quo.
      • scene_update (partial) → merge в scene_json.
      • Нет d20, нет bubble «Рассказчик».
  3. Linear story (pre-stream): reconcile_story_arc → один active-квест = текущий steps[i]:
    • format_step_guidance_for_character — цель шага в system CHAT.
    • При первом входе в шаг: injection как мягкая подсказка (format_step_hint_for_character).
    • Если арка завершена и игрок выбрал «новую арку» → roll_next_arc.
  4. runtime_suffix = build_rpg_runtime_suffix(session) + narrator_extra (directives + step guidance/hint).
  5. upsert_static_system_message(static) — в БД system без RPG-блоков.
  6. add_message(user, message).
  7. context_usage — если > 85%, в конец system добавляется [Context: ~N% …].
  8. llm_messages = system(static+runtime) + история; язык сессии (infer_rp_language) в narrator/plot/injection.

Этап 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 narrator_post NARRATOR Контекст: шаг N/M, completion_criteria, последние 8 реплик
C3 apply_narrator_post_with_story facts, affinity, scene; step_complete → advance step, sync quest
C4 step choices / new arc choices из нового шага; при arc_completed — «Начать новую арку»
C5 extract_facts FACTS Последние 10 реплик
C6 SD + SSE done всегда quests + story_arc в payload (live UI)
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: нет C6C7; 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 (010).
  • post: stats_delta каждый ключ 2..+2, clamp 010.
  • Дефолт 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:

  1. narrator (d20)
  2. chunk × N
  3. image_generating (если SD)
  4. done (choices, affinity, quests, debug)

Файлы для углубления

Тема Файл
Stream + RPG routers/chat.pychat_stream
Opening services/opening.py
Narrator JSON services/rpg_narrator.py
Runtime blocks routers/chat.pybuild_rpg_runtime_suffix, services/rpg_state.py
Persist post services/rpg_state.pyapply_narrator_post
Arc / beats services/rpg_plot.py
Facts services/rpg_facts.py
UI stream static/js/chat.jssendMessage, consumeStream
New chat static/js/newChatWizard.js
Context % services/context_budget.py

Обновлено под текущую реализацию RP UX (anti-OOC, scene_json, context blob, rpFormat, bootstrap narrator).