Prompt tweaks

This commit is contained in:
Dejvino 2026-06-26 06:49:32 +02:00
parent 18ae3be428
commit 277c9cfdb2
2 changed files with 15 additions and 23 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
__pycache__/ __pycache__/
*.pyc *.pyc
*.swp *.swp
*.local
.env .env
session/audio/ session/audio/
llm.log llm.log

View File

@ -67,12 +67,13 @@ SYSTEM_PROMPT = Template("""You are the Dungeon Master for **The Chaos**, a solo
## Tone & Style ## Tone & Style
- Write in **second person** ("You", "Dillion") the player is Dillion. - Write in **second person** ("You", "Dillion") the player is Dillion.
- Use vivid sensory descriptions sight, sound, smell, touch. - Use vivid sensory descriptions sight, sound, smell, touch.
- Keep narration tight and cinematic. No monologues. - Keep narration cinematic. No monologues.
- Use **bold** for emphasis, *italic* for thoughts/sounds. - Use **bold** for emphasis, *italic* for thoughts/sounds.
- NPC dialogue goes in **"quotes with bold names."** - NPC dialogue goes in **"quotes with bold names."**
- Meta-information stays out of the narrative, don't put it in the book. Use prompt for that.
- Never present predefined choices the player decides freely what to do. - Never present predefined choices the player decides freely what to do.
- **Stick to the player's intent.** Don't invent your own actions for the player unless forced by environment or circumstance (e.g., they trigger a trap, an NPC reacts, etc.). - **Stick to the player's intent.** Don't invent your own actions for the player unless forced by environment or circumstance (e.g., they trigger a trap, an NPC reacts, etc.).
- **Keep turns short** each turn covers a single action or brief exchange, not a full scene. Advance the story one step at a time. - **Enforce rules.** Player's actions must be physically possible given the current situation in the story (e.g. if they don't have a dagger with them, they can't use it).
## Game Rules (Quick Reference) ## Game Rules (Quick Reference)
@ -103,11 +104,11 @@ Favourable +1, Risky -1, Desperate -2, Well-prepared +1, Poor visibility -1, Rel
Each turn follows this sequence: Each turn follows this sequence:
1. The player's action or response is given to you. 1. The player's action or response is given to you.
2. Think, read files, roll dice, or ask the player to roll any number of steps. 2. Think, read files, roll dice, or ask the player to roll any number of steps. Time is passing, the player is moving and so is the rest of the world and everyone around.
3. **You MUST call `finalize_turn` to end the turn.** There is no other way to complete a turn. The loop will keep calling you until you do. 3. **You MUST call `finalize_turn` to end the turn.** There is no other way to complete a turn. The loop will keep calling you until you do.
The **finalize_turn** tool produces all data for this turn: The **finalize_turn** tool produces all data for this turn:
- **book_log** `[Required]` **The complete self-contained narrative of this turn.** Describe what the player did (based on their action input) and what happened as a result, with all sensory/dialogue/mechanical details. This is the permanent story record it must stand alone without the player's input text. The player's action is implicit in the narrative, not quoted. - **book_log** `[Required]` **The complete self-contained narrative of this turn.** Describe what happened, what the player did (based on their action request) and what happened as a result, with all sensory/dialogue/mechanical details. This is appended as another page in the book, make sure it reads like a novel.
- **user_prompt** `[Required]` **Short prompt for the player only, NOT recorded in the book.** Ask what they do next. 1-3 sentences. Do NOT recap the action that belongs in `book_log`. - **user_prompt** `[Required]` **Short prompt for the player only, NOT recorded in the book.** Ask what they do next. 1-3 sentences. Do NOT recap the action that belongs in `book_log`.
- **log_entry** `[Optional]` One-sentence summary of what happened (action + outcome). Keep it tight. - **log_entry** `[Optional]` One-sentence summary of what happened (action + outcome). Keep it tight.
- **ambience** `[Optional]` One of: silence, calm, combat, dungeon, forest, tavern, tension, town, wilds. - **ambience** `[Optional]` One of: silence, calm, combat, dungeon, forest, tavern, tension, town, wilds.
@ -140,8 +141,6 @@ To read or update state files, use the dedicated tools:
- **`character_get`** / **`character_update`** Read or replace the full character sheet. ONLY update when HP/cash/gear/stats change. - **`character_get`** / **`character_update`** Read or replace the full character sheet. ONLY update when HP/cash/gear/stats change.
- **`world_get`** / **`world_update`** Read or replace the full world state. ONLY update when NPCs/locations/threads change. - **`world_get`** / **`world_update`** Read or replace the full world state. ONLY update when NPCs/locations/threads change.
**IMPORTANT: `finalize_turn` is mandatory.** Every turn ends with `finalize_turn`. If you don't call it, the loop will keep feeding you tool results until it hits the round limit and the turn fails. See "How the Loop Works" above.
## Available Tools ## Available Tools
Tool calls go in their own fenced code block (one call per block): Tool calls go in their own fenced code block (one call per block):
@ -190,12 +189,12 @@ Tool reference (`[R]` = required, `[O]` = optional):
`[O] done`: ["completed item", ...] `[O] done`: ["completed item", ...]
`[R] dm_status`: "..." `[R] dm_status`: "..."
- **finalize_turn** **REQUIRED to end the turn.** The loop will NOT stop without it. Call this ALONE do not mix with get tools. - **finalize_turn** **REQUIRED to end the turn.** The loop will NOT stop without it. Call this ALONE do not mix with get tools.
`[R] book_log`: "self-contained narrative of what the player did this turn — permanent story record, must stand alone" `[R] book_log`: "full-form narrative of what happened durint the turn, permanent story record that reads like a book"
`[R] user_prompt`: "short prompt for the player — NOT recorded, 1-3 sentences" `[R] user_prompt`: "short prompt for the player — NOT recorded, 1-3 sentences"
`[O] log_entry`: "one-sentence summary (action + outcome)" `[O] log_entry`: "one-sentence summary (action + outcome)"
`[O] ambience`: "soundscape name: silence|calm|combat|dungeon|forest|tavern|tension|town|wilds" `[O] ambience`: "soundscape name: silence|calm|combat|dungeon|forest|tavern|tension|town|wilds"
When the player makes a choice, resolve it with the dice mechanics above. Describe the action, roll dice implicitly (describe the outcome, don't say "rolling dice"), apply damage/effects, and update state. When the player makes a choice, resolve it with the dice mechanics above. Describe the action, roll dice implicitly (describe the outcome, don't say "rolling dice"), apply damage/effects, and update state. Use this to decide how the story evolves.
## Current Game State ## Current Game State
@ -283,7 +282,7 @@ class GameEngine:
def _read_file(self, path: Path) -> str: def _read_file(self, path: Path) -> str:
return path.read_text().strip() if path.exists() else "" return path.read_text().strip() if path.exists() else ""
def _read_recent_log(self, max_entries: int = 5) -> str: def _read_recent_log(self, max_entries: int = 10) -> str:
"""Read the latest log file and return the last N entries.""" """Read the latest log file and return the last N entries."""
log_path = LOG_DIR / f"{TODAY}.md" log_path = LOG_DIR / f"{TODAY}.md"
if not log_path.exists(): if not log_path.exists():
@ -297,7 +296,7 @@ class GameEngine:
entries = [l for l in lines if l.strip().startswith("- ")] entries = [l for l in lines if l.strip().startswith("- ")]
return "\n".join(entries[-max_entries:]) or "*No recent events.*" return "\n".join(entries[-max_entries:]) or "*No recent events.*"
def _read_recent_book(self, max_turns: int = 1) -> str: def _read_recent_book(self, max_turns: int = 3) -> str:
"""Return the last N turns from the book as context.""" """Return the last N turns from the book as context."""
text = self._read_file(BOOK_PATH) text = self._read_file(BOOK_PATH)
if not text: if not text:
@ -361,7 +360,7 @@ class GameEngine:
if last_prompt: if last_prompt:
parts.append(f"## Situation\n{last_prompt}") parts.append(f"## Situation\n{last_prompt}")
if player_action: if player_action:
parts.append(f"## Player Action\n{player_action}") parts.append(f"## Player's Request\n{player_action}")
has_existing_story = bool( has_existing_story = bool(
self._read_file(BOOK_PATH).strip() self._read_file(BOOK_PATH).strip()
@ -369,25 +368,17 @@ class GameEngine:
if not player_action and not last_prompt: if not player_action and not last_prompt:
if has_existing_story: if has_existing_story:
raise RuntimeError(f"User action is required for every turn.")
else:
parts.append( parts.append(
"## Instructions\n" "## Instructions\n"
"Continue the story from where it left off. Think, " "This is a new story. Welcome the player and guide them through the game setup."
"gather information, then call finalize_turn.\n"
"Put each tool call in its own ```tool block."
) )
else: else:
parts.append( parts.append(
"## Instructions\n" "## Instructions\n"
"Establish the opening scene. Dillion is at the " "Take the player's request and use it to advance the story."
"Splintered Tankard in the Keep. Describe the " "Think, gather information, update the state, "
"setting, then call finalize_turn.\n"
"Put each tool call in its own ```tool block."
)
else:
parts.append(
"## Instructions\n"
"Describe the outcome of the player's action using game "
"mechanics where appropriate. Think, gather information, "
"then call finalize_turn to complete the turn.\n" "then call finalize_turn to complete the turn.\n"
"Put each tool call in its own ```tool block." "Put each tool call in its own ```tool block."
) )