diff --git a/session/changes.md b/session/changes.md new file mode 100644 index 0000000..e69de29 diff --git a/tools/engine.py b/tools/engine.py index 677d8f3..3c5e0c1 100644 --- a/tools/engine.py +++ b/tools/engine.py @@ -31,6 +31,7 @@ AMBIENCE_PATH = SESSION_DIR / 'ambience.md' LOG_DIR = SESSION_DIR / 'log' LLM_LOG_PATH = SESSION_DIR / 'llm.log' AMBIENCE_OPTIONS_PATH = SESSION_DIR / "ambience_options.md" +CHANGES_PATH = SESSION_DIR / "changes.md" AUDIO_DIR = SESSION_DIR / "audio" TODAY = date.today().isoformat() @@ -59,6 +60,7 @@ class TurnResult: log_entry: Optional[str] = None error: Optional[str] = None debug_info: str = "" + changes: list[str] = field(default_factory=list) # ── DM System Prompt Template ────────────────────────────────────────────── @@ -653,6 +655,42 @@ class GameEngine: desc = f"using {tool_name}" return f"DM is {desc}..." + @staticmethod + def _describe_change(tool_name: str, args: dict) -> str: + """Build a compact human-readable change description from a tool call.""" + if tool_name == "modify_vitals": + parts = [] + for k, v in args.items(): + label = k.replace("_", " ").title() + parts.append(f"{label}: {v}") + return f"⚡ {', '.join(parts)}" if parts else "" + elif tool_name == "modify_traits": + parts = [] + for k, v in args.items(): + parts.append(f"{k.upper()}: {v}") + return f"⚡ {', '.join(parts)}" + elif tool_name == "add_to_inventory": + return f"+ {args.get('item', '?')}" + elif tool_name == "remove_from_inventory": + return f"− {args.get('item', '?')}" + elif tool_name == "replace_gear": + return f"↻ {args.get('before', '?')} → {args.get('after', '?')}" + elif tool_name == "add_note": + note = args.get("note", "?") + return f"📝 {note[:60]}{'…' if len(note) > 60 else ''}" + elif tool_name == "replace_note": + return f"📝 {args.get('before', '?')[:40]} → {args.get('after', '?')[:40]}" + elif tool_name == "world_update": + return "🌍 World updated" + elif tool_name == "journal_update": + parts = [] + for a in args.get("add", []): + parts.append(f"📋 {a}") + for d in args.get("done", []): + parts.append(f"✅ {d}") + return "; ".join(parts) if parts else "" + return "" + def _execute_tool(self, tool_name: str, args: dict) -> str: fn_map = { "roll": self._tool_roll, @@ -815,6 +853,7 @@ class GameEngine: user_prompt = self._auto_prompt("") ambience = None debug_info = "" + changes = [] for outer_attempt in range(3): # ── Phase 1: Prose ──────────────────────────────────────────── @@ -969,6 +1008,7 @@ class GameEngine: on_debug("phase", {"phase": 3, "status": "tools_found", "tools": names, "has_finalize": fin}) errors = [] + attempt_changes = [] for tc in tool_calls: name = tc.get("tool", "?") args = tc.get("args", {}) @@ -993,12 +1033,17 @@ class GameEngine: if result.startswith("**Error:") or result.startswith("Tool error") or result.startswith("Unknown"): errors.append(f"{name}: {result}") + else: + desc = self._describe_change(name, args) + if desc: + attempt_changes.append(desc) if on_debug: on_debug("tool_result", {"round": p3_attempt + 1, "tool": name, "result": result}) if not errors: phase3_ok = True debug_info = "" + changes = attempt_changes if on_debug: on_debug("phase", {"phase": 3, "status": "done", "applied": len([tc for tc in tool_calls if tc.get("tool") != "finalize_turn"])}) break @@ -1053,6 +1098,7 @@ class GameEngine: user_prompt=user_prompt, ambience=ambience, debug_info=debug_info, + changes=changes, ) @staticmethod @@ -1206,6 +1252,10 @@ class GameEngine: """Write state changes from a TurnResult to disk.""" if result.ambience: AMBIENCE_PATH.write_text(result.ambience.strip().lower() + "\n") + if result.changes: + CHANGES_PATH.write_text("\n".join(result.changes) + "\n") + else: + CHANGES_PATH.write_text("") def archive_turn(self, narrative: str) -> None: """Append the narrative as a new turn in book.md.""" diff --git a/tools/run.py b/tools/run.py index ff290b6..7f49596 100755 --- a/tools/run.py +++ b/tools/run.py @@ -46,6 +46,7 @@ AMBIENCE_PATH = SESSION / 'ambience.md' AMBIENCE_OPTIONS_PATH = SESSION / 'ambience_options.md' BOOK_PATH = SESSION / 'book.md' LAST_PROMPT_PATH = SESSION / 'last_prompt.md' +CHANGES_PATH = SESSION / 'changes.md' AUDIO_DIR = SESSION / 'audio' TODAY = date.today().isoformat() LOG_PATH = LOG_DIR / f'{TODAY}.md' @@ -697,6 +698,11 @@ class ChaosTUI(App): parts = [] if pages: parts.append(pages[-1]) + if CHANGES_PATH.exists(): + changes = [l for l in CHANGES_PATH.read_text().splitlines() if l.strip()] + if changes: + changes_text = "\n".join(f"> {c}" for c in changes) + parts.append(f"> **Last turn changes:**\n{changes_text}") parts.append(f"---\n\n{saved}") self._set_narrative("\n\n".join(parts)) self._enable_input() @@ -987,6 +993,9 @@ class ChaosTUI(App): parts = [] if result.book_log: parts.append(result.book_log) + if result.changes: + changes_text = "\n".join(f"> {c}" for c in result.changes) + parts.append(f"> **Changes:**\n{changes_text}") if result.user_prompt: parts.append(f"---\n\n{result.user_prompt}") self._set_narrative("\n\n".join(parts) if parts else "")