diff --git a/api/app/prompts.py b/api/app/prompts.py index f334ea5..094f3ab 100644 --- a/api/app/prompts.py +++ b/api/app/prompts.py @@ -55,3 +55,57 @@ def render_digest(canon_log: dict) -> str: 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) diff --git a/api/tests/test_prompts.py b/api/tests/test_prompts.py index 940fbeb..d1fceb1 100644 --- a/api/tests/test_prompts.py +++ b/api/tests/test_prompts.py @@ -40,3 +40,26 @@ def test_narrator_body_is_authored(): assert "TBD" not in body assert "[FACT:" in body # the tag instruction is present assert "second person" in body # the core task is present + + +def test_render_npc_digest_includes_all_sections(): + from app import prompts + log = { + "player": {"name": "Aldric", "class_id": "sellsword", "luck_descriptor": "the dice are kind"}, + "location": {"id": "greywater_docks", "name": "Greywater Docks"}, + "party": [], "recent_events": ["a gull screamed"], + "established_facts": ["the eastern bridge is out"], + "active_quests": [], "humiliations": [], + } + out = prompts.render_npc_digest( + log, persona="A harried clerk.", knowledge=["The ledger is gone."], + disposition=-10, available_moves=["reveal(varrell_twins)", "refuse"], + utterance="What happened here?", + ) + assert "A harried clerk." in out + assert "-10" in out + assert "The ledger is gone." in out + assert "reveal(varrell_twins)" in out + assert "Greywater Docks" in out + assert "the eastern bridge is out" in out + assert out.rstrip().endswith('What happened here?"') # utterance last