112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
"""Load a role's system prompt (§16 — prompts are source code) and render the
|
|
canon log into a curated digest for the user message (charter §11).
|
|
|
|
The digest is a projection, not a dump: it surfaces narrative context and
|
|
deliberately omits raw integers — companion dispositions render as names only,
|
|
luck as its descriptor, and ids/schema_version are dropped (charter §7/§2).
|
|
"""
|
|
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
|
|
_PROMPT_DIR = Path(__file__).resolve().parent.parent / "prompts"
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
def system_prompt(role: str) -> str:
|
|
text = (_PROMPT_DIR / f"{role}.md").read_text(encoding="utf-8")
|
|
# Everything below the first '---' line is the model-facing prompt; the
|
|
# header above it is human/dev front-matter.
|
|
parts = text.split("\n---\n", 1)
|
|
body = parts[1] if len(parts) == 2 else text
|
|
return body.strip()
|
|
|
|
|
|
def render_digest(canon_log: dict) -> str:
|
|
lines: list[str] = []
|
|
|
|
location = canon_log.get("location", {})
|
|
lines.append(f"Location: {location.get('name', 'an unknown place')}")
|
|
|
|
events = canon_log.get("recent_events", [])
|
|
if events:
|
|
lines.append("Recent events:")
|
|
lines += [f"- {e}" for e in events]
|
|
|
|
facts = canon_log.get("established_facts", [])
|
|
if facts:
|
|
lines.append("Established facts:")
|
|
lines += [f"- {f}" for f in facts]
|
|
|
|
party = canon_log.get("party", [])
|
|
if party:
|
|
lines.append("Companions present: " + ", ".join(m.get("name", "") for m in party))
|
|
|
|
for quest in canon_log.get("active_quests", []):
|
|
if quest.get("status") == "active":
|
|
lines.append(f"Active quest: {quest.get('name', '')} — {quest.get('objective', '')}")
|
|
|
|
player = canon_log.get("player", {})
|
|
descriptor = player.get("luck_descriptor")
|
|
if descriptor:
|
|
lines.append(f"Fortune: {descriptor}")
|
|
lines.append(f"Player: {player.get('name', '')}, a {player.get('class_id', '')}")
|
|
|
|
lines.append("")
|
|
lines.append("Describe the scene as it is now.")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def render_npc_digest(
|
|
canon_log: dict,
|
|
*,
|
|
persona: str,
|
|
knowledge: list[str],
|
|
disposition: int,
|
|
available_moves: list[str],
|
|
utterance: str,
|
|
) -> str:
|
|
"""User-message digest for the NPC role (§6). Unlike the narrator digest,
|
|
this deliberately carries the disposition integer (§6 permits it; §7 hides
|
|
Luck, not disposition) and the closed move vocabulary the NPC may use."""
|
|
lines: list[str] = []
|
|
lines.append(f"You are: {persona}")
|
|
lines.append(
|
|
f"Your disposition toward the player: {disposition} "
|
|
f"(-100 hostile, 0 neutral, 100 devoted)."
|
|
)
|
|
|
|
if knowledge:
|
|
lines.append("")
|
|
lines.append("What you know (this is ALL you know — do not invent beyond it):")
|
|
lines += [f"- {k}" for k in knowledge]
|
|
|
|
lines.append("")
|
|
lines.append("Moves you may perform this turn (use ONLY these, as [MOVE: name(args)]):")
|
|
lines += [f"- {m}" for m in available_moves]
|
|
|
|
location = canon_log.get("location", {})
|
|
lines.append("")
|
|
lines.append(f"Location: {location.get('name', 'an unknown place')}")
|
|
|
|
events = canon_log.get("recent_events", [])
|
|
if events:
|
|
lines.append("Recent events:")
|
|
lines += [f"- {e}" for e in events]
|
|
|
|
facts = canon_log.get("established_facts", [])
|
|
if facts:
|
|
lines.append("Established facts:")
|
|
lines += [f"- {f}" for f in facts]
|
|
|
|
for quest in canon_log.get("active_quests", []):
|
|
if quest.get("status") == "active":
|
|
lines.append(f"Active quest: {quest.get('name', '')} — {quest.get('objective', '')}")
|
|
|
|
player = canon_log.get("player", {})
|
|
lines.append(f"The player is {player.get('name', '')}, a {player.get('class_id', '')}.")
|
|
|
|
lines.append("")
|
|
lines.append(f'The player says to you: "{utterance}"')
|
|
return "\n".join(lines)
|