new RPG system

This commit is contained in:
2026-06-05 14:57:15 +03:00
parent 6189a5fb74
commit 01b16dbeaa
29 changed files with 2395 additions and 311 deletions
+58 -20
View File
@@ -186,31 +186,45 @@ def stats_prompt_block(stats: dict) -> str:
)
def format_narrator_outcome_for_llm(data: dict) -> str:
def format_narrator_outcome_for_llm(data: dict, *, lang: str = "ru") -> str:
"""Turn stored narrator JSON into a binding user-turn for the character model."""
roll = data.get("roll")
outcome = (data.get("outcome") or "").strip().lower()
text = (data.get("text") or "").strip()
lines = [
"--- Narrator ruling (MANDATORY — your next in-character reply MUST follow this) ---",
f"Roll d20={roll}. Outcome: {outcome}.",
f"What ACTUALLY happened (canonical truth): {text}",
]
if outcome in ("failure", "critical failure"):
lines.append(
"The player's action FAILED as they imagined it. "
"Do NOT write a success version: no crowd fleeing, no intimidation working, "
"no effortless victory. Show the failure, embarrassment, or partial result above."
)
elif outcome == "critical success":
lines.append(
"The attempt succeeded dramatically. You may show amplified success aligned with the outcome above."
)
if lang == "ru":
lines = [
"--- Правило рассказчика (ОБЯЗАТЕЛЬНО — ответ персонажа должен ему следовать) ---",
f"Бросок d20={roll}. Исход: {outcome}.",
f"Что РЕАЛЬНО произошло (канон): {text}",
]
if outcome in ("failure", "critical failure"):
lines.append(
"Действие игрока НЕ удалось. Не пиши успешную версию — покажи провал или частичный результат выше."
)
elif outcome == "critical success":
lines.append("Попытка удалась блестяще. Усиль успех в духе исхода выше.")
else:
lines.append("Попытка удалась. Ответ должен совпадать с исходом выше, не противоречить ему.")
lines.append("Отвечай только как персонаж на ЭТОТ исход. Не упоминай кубики, броски, статы.")
else:
lines.append(
"The attempt succeeded. Your reply must align with the narrator outcome above, not contradict it."
)
lines.append("Respond as the character to THIS outcome only. Never cite dice, rolls, or stats.")
lines = [
"--- Narrator ruling (MANDATORY — your next in-character reply MUST follow this) ---",
f"Roll d20={roll}. Outcome: {outcome}.",
f"What ACTUALLY happened (canonical truth): {text}",
]
if outcome in ("failure", "critical failure"):
lines.append(
"The player's action FAILED. Do NOT write a success version; show failure per above."
)
elif outcome == "critical success":
lines.append(
"The attempt succeeded dramatically. Align with the outcome above."
)
else:
lines.append(
"The attempt succeeded. Your reply must align with the narrator outcome above."
)
lines.append("Respond as the character to THIS outcome only. Never cite dice, rolls, or stats.")
lines.append("---")
return "\n".join(lines)
@@ -319,3 +333,27 @@ async def apply_narrator_post(session_id: str, post: dict, rpg_settings: dict, s
applied["quests_updated"] += 1
return applied
async def apply_narrator_post_with_story(
session_id: str,
post: dict,
rpg_settings: dict,
session: dict | None = None,
arc: dict | None = None,
) -> dict:
"""apply_narrator_post + linear story step advance."""
from services.rpg_story import apply_story_post, normalize_story_arc
applied = await apply_narrator_post(session_id, post, rpg_settings, session)
arc = normalize_story_arc(arc or {})
story = await apply_story_post(session_id, post, arc, rpg_settings)
applied.update({
"step_advanced": story.get("step_advanced", False),
"arc_completed": story.get("arc_completed", False),
"new_step_title": story.get("new_step_title", ""),
"step_injection": story.get("step_injection", ""),
})
if story.get("arc"):
applied["arc"] = story["arc"]
return applied