Add meta language
This commit is contained in:
parent
83a83dd421
commit
e002bafbc8
@ -49,10 +49,20 @@ class GameEngine:
|
||||
on_action("DM is preparing a response")
|
||||
|
||||
system = build_system_prompt(recent_narrative=recent_narrative, recent_log=session_log)
|
||||
|
||||
is_meta = bool(player_action and player_action.strip().startswith(">"))
|
||||
|
||||
base_parts = []
|
||||
if player_action:
|
||||
base_parts.append(f"## Player's Request\n{player_action}")
|
||||
if not player_action and not recent_narrative:
|
||||
if is_meta:
|
||||
base_parts.append(
|
||||
"## Instructions\n"
|
||||
"The player's message starts with `>` — this is a meta out-of-character question to the DM. "
|
||||
"Do NOT advance the story. Respond as the DM in meta language, starting the response with `>`. "
|
||||
"Use the `narrative` tool to output your meta response. Do NOT call any other tools (no journal_update, no finalize_turn, no rolls, no state changes)."
|
||||
)
|
||||
elif not player_action and not recent_narrative:
|
||||
base_parts.append(
|
||||
"## Instructions\n"
|
||||
"This is a new story. Welcome the player and guide them through the game setup."
|
||||
@ -63,6 +73,7 @@ class GameEngine:
|
||||
"Advance the story based on the player's request. "
|
||||
"All state is shown above — write the outcome directly."
|
||||
)
|
||||
if not is_meta:
|
||||
base_parts.append(f"\n*A die is cast: **{die_roll}** (1d6).*")
|
||||
base_user = "\n\n".join(base_parts)
|
||||
|
||||
@ -132,8 +143,19 @@ class GameEngine:
|
||||
else:
|
||||
state_changes.append(tc)
|
||||
|
||||
# Meta check — reject if state changes produced for a meta action
|
||||
if is_meta and state_changes:
|
||||
state.append_llm_log(f"\n[TURN META REJECTED] state changes not allowed for meta action")
|
||||
if attempt < MAX_RETRIES:
|
||||
feedback = "This is a meta action. Do NOT call any state-changing tools. Respond only with meta text (starting with `>`) and no tool calls beyond a finalize_turn."
|
||||
state.append_llm_log(f"\n[TURN REGENERATE] (meta) attempt {attempt + 2}")
|
||||
if on_action:
|
||||
on_action("DM is consulting the fates...")
|
||||
continue
|
||||
state.append_llm_log(f"\n[TURN META EXCEEDED] accepting despite state changes")
|
||||
|
||||
# Duplicate check — reject if narrative is 80%+ similar to last book entry
|
||||
if book_log:
|
||||
if not is_meta and book_log:
|
||||
prev = state.read_recent_book(1)
|
||||
if prev and prev not in ("*No prior story.*",):
|
||||
prev_text = re.sub(r"^## Turn \d+\n\n", "", prev, flags=re.MULTILINE).strip()
|
||||
@ -164,6 +186,7 @@ class GameEngine:
|
||||
changes=state_changes,
|
||||
story=recent_narrative,
|
||||
log=session_log,
|
||||
meta=is_meta,
|
||||
)
|
||||
|
||||
if valid:
|
||||
@ -203,6 +226,9 @@ class GameEngine:
|
||||
# Accept this turn — execute all tool calls
|
||||
break
|
||||
|
||||
if is_meta:
|
||||
tool_calls = [tc for tc in tool_calls if tc.get("tool") == "narrative"]
|
||||
|
||||
# Second pass — execute all tool calls
|
||||
extr_start = datetime.now()
|
||||
|
||||
@ -256,6 +282,7 @@ class GameEngine:
|
||||
ambience=ambience,
|
||||
debug_info="; ".join(errors) if errors else "",
|
||||
changes=changes,
|
||||
is_meta=is_meta,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -14,3 +14,4 @@ class TurnResult:
|
||||
error: Optional[str] = None
|
||||
debug_info: str = ""
|
||||
changes: list[str] = field(default_factory=list)
|
||||
is_meta: bool = False
|
||||
|
||||
@ -171,6 +171,33 @@ Regenerate (turn had fixable issues like wrong state changes or minor inconsiste
|
||||
```
|
||||
"""
|
||||
|
||||
META_VALIDATION_PROMPT = """You are validating a meta (out-of-character) DM response. The player's action starts with `>` — they are talking to the DM, not to a character.
|
||||
|
||||
## Player Action (Meta)
|
||||
{action}
|
||||
|
||||
## Generated Meta Response
|
||||
{narrative}
|
||||
|
||||
## Instructions
|
||||
1. **Meta Format**: The entire response must start with `>` and use meta language (DM addressing the player directly).
|
||||
2. **State Changes**: There MUST be no state changes. This is a meta conversation, not story progression.
|
||||
3. **Answer Quality**: The response should address the player's meta question and be helpful.
|
||||
4. **No Story Advancement**: The response must not advance the game narrative.
|
||||
|
||||
Reply with ONLY a ```tool block. Examples:
|
||||
|
||||
Valid:
|
||||
```tool
|
||||
{{"tool": "validate", "args": {{"valid": true, "reason": "ok", "action": "ok"}}}}
|
||||
```
|
||||
|
||||
Regenerate (response didn't start with `>` or tried to change state):
|
||||
```tool
|
||||
{{"tool": "validate", "args": {{"valid": false, "reason": "describe the issue", "action": "regenerate"}}}}
|
||||
```
|
||||
"""
|
||||
|
||||
|
||||
def _format_changes(changes: list[dict]) -> str:
|
||||
"""Format tool calls into a readable change list for the validation prompt."""
|
||||
@ -193,6 +220,7 @@ def validate_turn(
|
||||
changes: list[dict] | None = None,
|
||||
story: str = "",
|
||||
log: str = "",
|
||||
meta: bool = False,
|
||||
) -> tuple[bool, str, str]:
|
||||
"""Validate a complete generated turn.
|
||||
|
||||
@ -208,6 +236,12 @@ def validate_turn(
|
||||
journal = state.read_file(JOURNAL_PATH) or "*No journal entries.*"
|
||||
change_summary = _format_changes(changes or [])
|
||||
|
||||
if meta:
|
||||
prompt = META_VALIDATION_PROMPT.format(
|
||||
action=player_action,
|
||||
narrative=narrative,
|
||||
)
|
||||
else:
|
||||
prompt = TURN_VALIDATION_PROMPT.format(
|
||||
character=char, world=world, story=recent,
|
||||
log=log_entries, journal=journal, action=player_action,
|
||||
|
||||
13
tools/run.py
13
tools/run.py
@ -67,6 +67,7 @@ class ChaosTUI(App):
|
||||
#char-content { background: #1e1e2a; color: #c0c0c0; padding: 0 1; }
|
||||
#transcript { background: #1a2a1a; color: #c8c8c8; padding: 0 1; }
|
||||
#play-narrative { background: #161616; color: #d8d8d8; padding: 1 2; height: auto; }
|
||||
#play-narrative.meta { background: #1a1a2e; color: #b0a0e0; border-top: solid #6b4fa0; border-bottom: solid #6b4fa0; }
|
||||
#play-status { background: #1a2a1a; color: #e0b060; padding: 0 2; height: 1; text-style: bold italic; text-align: center; }
|
||||
#play-status.processing { background: #2a1a0a; color: #ffd93d; }
|
||||
#play-input { height: 3; background: #222; color: #e0d0c0; border: solid #555; padding: 0 1; }
|
||||
@ -304,7 +305,7 @@ class ChaosTUI(App):
|
||||
if result.error:
|
||||
self._show_error(result.error, result.debug_info)
|
||||
return
|
||||
if result.book_log:
|
||||
if result.book_log and not result.is_meta:
|
||||
turn_num = state.archive_turn(result.book_log)
|
||||
if result.log_entry:
|
||||
state.append_log(f"- **Turn {turn_num}** — {result.log_entry}")
|
||||
@ -312,7 +313,7 @@ class ChaosTUI(App):
|
||||
summary = result.book_log.strip().split(chr(10))[0][:80]
|
||||
state.append_log(f"- **Turn {turn_num}** — {summary}")
|
||||
result.book_log = load_book_pages()[-1]
|
||||
elif result.log_entry:
|
||||
elif result.log_entry and not result.is_meta:
|
||||
state.append_log(f"- {result.log_entry}")
|
||||
state.apply_state(result)
|
||||
if result.book_log or not result.user_prompt:
|
||||
@ -344,7 +345,7 @@ class ChaosTUI(App):
|
||||
parts.append(self._render_changes(result.changes))
|
||||
if result.user_prompt:
|
||||
parts.append(f"---\n\n{result.user_prompt}")
|
||||
self._set_narrative("\n\n".join(parts) if parts else "")
|
||||
self._set_narrative("\n\n".join(parts) if parts else "", meta=result.is_meta)
|
||||
self._enable_input()
|
||||
|
||||
def _enable_input(self, value: str = "") -> None:
|
||||
@ -354,9 +355,11 @@ class ChaosTUI(App):
|
||||
inp.value = value
|
||||
inp.focus()
|
||||
|
||||
def _set_narrative(self, text: str) -> None:
|
||||
def _set_narrative(self, text: str, meta: bool = False) -> None:
|
||||
self._last_narrative = text
|
||||
self.query_one("#play-narrative", Static).update(RichMarkdown(text))
|
||||
widget = self.query_one("#play-narrative", Static)
|
||||
widget.set_class(meta, "meta")
|
||||
widget.update(RichMarkdown(text))
|
||||
self.query_one("#play-scroll", VerticalScroll).scroll_home(animate=False)
|
||||
|
||||
def _show_error(self, error: str, debug_info: str = "") -> None:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user