# Product Requirements Document: Dynamic Encounters and Goal Registration ## 1. Overview The Mardonar Encounter Engine is a Discord-native, LLM-driven D&D encounter system. Currently, encounters rely on static specs with a predefined list of goals. If player actions diverge significantly from the spec's options, the LLM has to force an awkward resolution or ignore the player's creative agency. This document defines the requirements for a new **Dynamic Goal Registration** mechanic. This feature enables the LLM to register custom goals on the fly during play. It also outlines three updated encounter specs showcasing increased versatility and randomizable elements. --- ## 2. Dynamic Goal Registration Mechanic ### 2.1 User Experience / Gameplay Flow 1. The encounter starts with 2–3 static base goals. 2. Players take an unexpected or highly creative action (e.g., instead of fighting or running, they bribe a hostile NPC and invite them to a heist). 3. The LLM detects that the current predefined goals are unrealistic or do not match the player's direction. 4. The LLM calls the `goal_register` tool to define a new goal with a custom ID, description, and status (primary vs. secondary). 5. The bot acknowledges the new goal in the session logs. 6. For all subsequent turns, the system prompt includes this newly registered goal under ``. 7. Once the conditions are met, the LLM resolves the encounter using the custom `outcomeId` via the `encounter_resolve` tool. 8. The ending embed in Discord displays the custom goal's description. ### 2.2 System Architecture & Integration ```mermaid sequenceDiagram participant LLM as LLM (Gemma) participant Dispatcher as Tool Dispatcher participant Registry as Redis Session Manager participant PromptBuilder as Prompt Builder LLM->>Dispatcher: goal_register(id, label, isPrimary, reason) Dispatcher->>Registry: Update SessionState.spec.goals Registry-->>Dispatcher: Success Dispatcher-->>LLM: Confirm tool result (system message) Note over LLM, PromptBuilder: Next message turn starts... PromptBuilder->>Registry: Get SessionState Registry-->>PromptBuilder: SessionState (with new goal) PromptBuilder->>LLM: Injected System Prompt (contains new goal in ) ``` ### 2.3 Technical Specifications #### A. Tool: `goal_register` * **Name**: `goal_register` * **File Location**: `src/harness/tools/goalRegister.ts` * **Arguments**: * `id` (`string`): Unique kebab-case ID (e.g., `bribe_and_recruit`). Must match regex `^[a-z0-9-_]+$`. * `label` (`string`): Precise description of the trigger conditions. * `isPrimary` (`boolean`): Whether it is a primary driver or a secondary fallback. * `reason` (`string`): Justification for the on-the-fly goal registration (used for logging and debugging). * **Behavior**: * Auto-prefixes the goal ID with `dynamic_` (if not already present) to separate custom runtime goals from spec goals. * Validates that the goal ID matches the regex `^[a-z0-9-_]+$` and doesn't conflict with existing goals. * Enforces a cap of at most **2 dynamic goals** per session to prevent infinite loop goal creation. * Enforces a cap of at most **20 messages** in the session history before blocking any new goal registrations, ensuring the encounter winds down. * Updates the active `SessionState` in Redis, appending the new goal to `spec.goals.primary` (if `isPrimary` is true) or `spec.goals.secondary`. * Returns a system message confirmation: `[TOOL] New hidden goal registered on the fly: -