54 lines
1.8 KiB
Python
54 lines
1.8 KiB
Python
"""Shared context blocks for RPG narrator / plot LLM calls."""
|
|
|
|
from services.rpg_plot import count_active_quests
|
|
|
|
|
|
def format_narrator_context(
|
|
arc: dict | None,
|
|
quests: list | None,
|
|
status_quo: str = "",
|
|
) -> str:
|
|
parts: list[str] = []
|
|
arc = arc or {}
|
|
beats = arc.get("beats") or []
|
|
if not isinstance(beats, list):
|
|
beats = []
|
|
|
|
parts.append(f"Plot phase: {arc.get('phase', 'opening')}. Scripted beats left: {len(beats)}.")
|
|
if not beats:
|
|
parts.append(
|
|
"IMPORTANT: Scripted beats are EXHAUSTED (quests may already be done). "
|
|
"The story must CONTINUE — do not stall. "
|
|
"Always return 2-4 meaningful choices for the player's next actions. "
|
|
"You may add quest_updates with status 'active' for NEW optional threads. "
|
|
"Do NOT re-activate quests the player already completed unless they explicitly revisit that thread."
|
|
)
|
|
elif count_active_quests(quests) == 0:
|
|
pending = [
|
|
(b.get("title") or b.get("id") or "beat")
|
|
for b in beats[:3]
|
|
if isinstance(b, dict)
|
|
]
|
|
parts.append(
|
|
"IMPORTANT: No active quests but scripted beats remain — arc was likely desynced. "
|
|
"The engine will inject the next beat; prefer choices that fit pending beats: "
|
|
+ ", ".join(pending)
|
|
+ ". Do NOT treat the arc as finished."
|
|
)
|
|
hint = (arc.get("next_beat_hint") or "").strip()
|
|
if hint:
|
|
parts.append(f"Arc hint: {hint}")
|
|
|
|
if quests:
|
|
parts.append("Quest log:")
|
|
for q in quests:
|
|
parts.append(f" [{q.get('status', 'active')}] {q.get('title', '')}")
|
|
else:
|
|
parts.append("Quest log: (empty)")
|
|
|
|
sq = (status_quo or "").strip()
|
|
if sq:
|
|
parts.append(f"Status quo: {sq[:400]}")
|
|
|
|
return "\n".join(parts)
|