986 lines
35 KiB
Markdown
986 lines
35 KiB
Markdown
# Story Progression System
|
|
|
|
**Status:** Active
|
|
**Phase:** 4 (AI Integration + Story Progression)
|
|
**Timeline:** Week 8 of Phase 4 (Days 8-14)
|
|
**Last Updated:** November 23, 2025
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The Story Progression System is the core single-player gameplay loop where players interact with the AI Dungeon Master through turn-based actions. Unlike combat (which uses structured mechanics), story progression allows players to explore the world, gather information, travel, and engage with narrative content.
|
|
|
|
**Key Principles:**
|
|
- **Solo sessions only** - Story progression is single-player focused
|
|
- **Button-based actions** - Structured prompts reduce AI costs and improve response quality
|
|
- **Tier-based features** - Free tier has basic actions, paid tiers unlock more options
|
|
- **Turn-based gameplay** - Player action → AI response → state update → repeat
|
|
- **Quest integration** - Quests are offered during story turns based on context and location
|
|
- **Location-based world** - Structured world with regions, locations, and travel
|
|
- **Persistent NPCs** - Characters with personalities, knowledge, and relationship tracking
|
|
|
|
---
|
|
|
|
## World Structure
|
|
|
|
### Regions and Locations
|
|
|
|
The game world is organized hierarchically:
|
|
|
|
```
|
|
World
|
|
└── Regions (e.g., Crossville Province)
|
|
└── Locations (e.g., Crossville Village, The Rusty Anchor Tavern)
|
|
└── NPCs (e.g., Grom Ironbeard, Elara the Herbalist)
|
|
```
|
|
|
|
**Regions** group related locations together for organizational purposes. Each region has:
|
|
- Regional lore and atmosphere
|
|
- List of contained locations
|
|
- Region-wide events (future feature)
|
|
|
|
**Locations** are the atomic units of the world map:
|
|
- Each location has a type (town, tavern, wilderness, dungeon, etc.)
|
|
- Locations contain NPCs who reside there
|
|
- Travel connections define which locations can be reached from where
|
|
- Some locations are "starting locations" where new characters spawn
|
|
|
|
### Location Discovery
|
|
|
|
Players don't know about all locations initially. Locations are discovered through:
|
|
|
|
1. **Starting location**: All new characters begin at a designated starting location (Crossville Village)
|
|
2. **Travel exploration**: Adjacent locations listed in `discoverable_locations`
|
|
3. **NPC conversations**: NPCs can reveal hidden locations via `reveals_locations`
|
|
4. **Story events**: AI narrative can unlock new destinations
|
|
|
|
**Example Location Discovery Flow:**
|
|
```
|
|
1. Player starts in Crossville Village
|
|
└── Discovered: [crossville_village]
|
|
|
|
2. Player explores village, finds paths to tavern and market
|
|
└── Discovered: [crossville_village, crossville_tavern, crossville_market]
|
|
|
|
3. Player talks to tavern keeper, learns about old mines
|
|
└── Discovered: [..., crossville_old_mines]
|
|
```
|
|
|
|
### Travel System
|
|
|
|
Travel moves players between discovered locations:
|
|
|
|
1. **Check available destinations**: `GET /api/v1/travel/available?session_id={id}`
|
|
2. **Travel to location**: `POST /api/v1/travel` with `session_id` and `location_id`
|
|
3. **AI narration**: Travel triggers narrative description of journey and arrival
|
|
4. **State update**: Player's `current_location_id` updated, NPCs at new location available
|
|
|
|
**Travel Restrictions:**
|
|
- Can only travel to discovered locations
|
|
- Some locations may require prerequisites (quest completion, level, etc.)
|
|
- Travel may trigger random encounters (future feature)
|
|
|
|
---
|
|
|
|
## NPC Interaction System
|
|
|
|
### NPC Overview
|
|
|
|
NPCs are persistent characters with rich data for AI dialogue generation:
|
|
|
|
- **Personality**: Traits, speech patterns, and quirks
|
|
- **Knowledge**: What they know (public) and secrets they may reveal
|
|
- **Relationships**: How they feel about other NPCs
|
|
- **Inventory**: Items for sale (if merchant)
|
|
- **Quest connections**: Quests they give and locations they reveal
|
|
|
|
### Talking to NPCs
|
|
|
|
When players interact with NPCs:
|
|
|
|
1. **NPC selection**: Player chooses NPC from location's NPC list
|
|
2. **Initial greeting or response**: Player either greets NPC or responds to previous dialogue
|
|
3. **AI prompt built**: Includes NPC personality, knowledge, relationship state, and conversation history
|
|
4. **Dialogue generated**: AI creates in-character response
|
|
5. **State updated**: Interaction tracked, dialogue exchange saved, secrets potentially revealed
|
|
|
|
**Initial Greeting Flow:**
|
|
```
|
|
Player: Clicks "Greet" on Grom Ironbeard
|
|
└── Topic: "greeting"
|
|
|
|
System builds prompt with:
|
|
- NPC personality (gruff, honest, protective)
|
|
- NPC speech style (short sentences, dwarven expressions)
|
|
- Public knowledge (tavern history, knows travelers)
|
|
- Conditional knowledge (if relationship >= 70, mention goblins)
|
|
- Current relationship level (65 - friendly)
|
|
|
|
AI generates response as Grom:
|
|
"Welcome to the Rusty Anchor! What'll it be?"
|
|
```
|
|
|
|
**Bidirectional Conversation Flow:**
|
|
|
|
Players can respond to NPC dialogue to continue conversations:
|
|
|
|
```
|
|
Player: Types "Have you heard any rumors lately?"
|
|
└── player_response: "Have you heard any rumors lately?"
|
|
|
|
System builds prompt with:
|
|
- All personality/knowledge context (same as above)
|
|
- Previous dialogue history (last 3 exchanges for context)
|
|
- Player's current response
|
|
|
|
AI generates continuation as Grom:
|
|
"Aye, I've heard things. *leans in* Strange folk been coming
|
|
through lately. Watch yerself on the roads, friend."
|
|
|
|
System saves exchange:
|
|
- player_line: "Have you heard any rumors lately?"
|
|
- npc_response: "Aye, I've heard things..."
|
|
- timestamp: "2025-11-24T10:30:00Z"
|
|
```
|
|
|
|
**Conversation History Display:**
|
|
|
|
The UI shows the full conversation history with each NPC:
|
|
- Previous exchanges are displayed with reduced opacity
|
|
- Current exchange is highlighted
|
|
- Player can type new responses to continue the conversation
|
|
- Last 10 exchanges per NPC are stored for context
|
|
|
|
### Relationship Tracking
|
|
|
|
Each character-NPC pair has an `NPCInteractionState`:
|
|
|
|
| Relationship Level | Status | Effects |
|
|
|--------------------|--------|---------|
|
|
| 0-20 | Hostile | NPC refuses to help, may report player |
|
|
| 21-40 | Unfriendly | Minimal assistance, no secrets |
|
|
| 41-60 | Neutral | Normal interaction |
|
|
| 61-80 | Friendly | Better prices, more information |
|
|
| 81-100 | Trusted | Full access to secrets, special quests |
|
|
|
|
**Relationship modifiers:**
|
|
- Successful interactions: +1 to +5 relationship
|
|
- Failed attempts: -1 to -5 relationship
|
|
- Quest completion for NPC: +10 to +20 relationship
|
|
- Helping NPC's friends: +5 relationship
|
|
- Harming NPC's allies: -10 to -20 relationship
|
|
|
|
### Secret Knowledge Reveals
|
|
|
|
NPCs can hold secrets that unlock under conditions:
|
|
|
|
```yaml
|
|
knowledge:
|
|
secret:
|
|
- "Saw strange lights in the forest last week"
|
|
will_share_if:
|
|
- condition: "relationship_level >= 70"
|
|
reveals: "Has heard rumors of goblins gathering in the old mines"
|
|
- condition: "interaction_count >= 5"
|
|
reveals: "Knows about a hidden cave behind the waterfall"
|
|
```
|
|
|
|
When a condition is met:
|
|
1. Secret index added to `revealed_secrets` list
|
|
2. Information included in AI prompt context
|
|
3. NPC "remembers" sharing this in future conversations
|
|
|
|
### Location Reveals from NPCs
|
|
|
|
NPCs can unlock new locations for players:
|
|
|
|
```yaml
|
|
reveals_locations:
|
|
- "crossville_old_mines"
|
|
- "hidden_waterfall_cave"
|
|
```
|
|
|
|
When an NPC reveals a location:
|
|
1. Location ID added to character's `discovered_locations`
|
|
2. Location becomes available for travel
|
|
3. AI narration describes how player learned about it
|
|
|
|
---
|
|
|
|
## Gameplay Loop
|
|
|
|
### Turn Structure
|
|
|
|
```
|
|
1. Display current state (location, quests, conversation history)
|
|
2. Present available actions (buttons based on tier + context)
|
|
3. Player selects action (or enters custom text if Premium/Elite)
|
|
4. Action sent to backend API
|
|
5. AI processes action and generates narrative response
|
|
6. Game state updated (location changes, quest progress, etc.)
|
|
7. Quest offering check (context-aware + location-based)
|
|
8. Response displayed to player
|
|
9. Next turn begins
|
|
```
|
|
|
|
### Turn Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ Player Views Current State │
|
|
│ - Location │
|
|
│ - Active Quests │
|
|
│ - Conversation History │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ System Presents Action Options │
|
|
│ - Tier-based button selection │
|
|
│ - Context-aware prompts │
|
|
│ - Custom input (Premium/Elite) │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Player Selects Action │
|
|
│ POST /api/v1/sessions/{id}/action │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Backend Processing │
|
|
│ - Validate action │
|
|
│ - Build AI prompt (context) │
|
|
│ - Call AI API (tier-based model) │
|
|
│ - Parse response │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Update Game State │
|
|
│ - Location changes │
|
|
│ - Quest progress │
|
|
│ - Conversation history │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Quest Offering Check │
|
|
│ - Context-aware analysis │
|
|
│ - Location-based roll │
|
|
│ - Max 2 active quests limit │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ Return Response to Player │
|
|
│ - AI narrative text │
|
|
│ - State changes │
|
|
│ - Quest offered (if any) │
|
|
└────────────┬────────────────────────┘
|
|
│
|
|
▼
|
|
Next Turn
|
|
```
|
|
|
|
---
|
|
|
|
## Action System
|
|
|
|
### Action Categories
|
|
|
|
Actions are organized into three primary categories:
|
|
|
|
#### 1. Ask Questions
|
|
Information-gathering actions that don't change location or trigger major events.
|
|
|
|
#### 2. Travel
|
|
Movement actions that change the player's current location.
|
|
|
|
#### 3. Gather Information
|
|
Social interaction actions that gather rumors, quests, and world knowledge.
|
|
|
|
### Tier-Based Action Availability
|
|
|
|
| Tier | Action Count | Free-Form Input | AI Model | Cost/Turn |
|
|
|------|--------------|-----------------|----------|-----------|
|
|
| **Free** | 4 basic actions | ❌ No | Replicate (free tier) | $0 |
|
|
| **Basic** | 4 basic actions | ❌ No | Claude Haiku | ~$0.01 |
|
|
| **Premium** | 7 actions | ✅ Yes (250 chars) | Claude Sonnet | ~$0.03 |
|
|
| **Elite** | 10 actions | ✅ Yes (500 chars) | Claude Opus | ~$0.10 |
|
|
|
|
### Action Prompt Definitions
|
|
|
|
#### Free Tier Actions (4 total)
|
|
|
|
**Ask Questions:**
|
|
1. **"What do I see around me?"** - Describe current location and visible surroundings
|
|
2. **"Are there any dangers nearby?"** - Check for enemies, hazards, or threats
|
|
|
|
**Travel:**
|
|
3. **"Travel to [dropdown: known towns]"** - Fast travel to discovered towns
|
|
4. **"Explore the area"** - Random exploration in current region
|
|
|
|
#### Premium Tier Additional Actions (+3 more, 7 total)
|
|
|
|
**Ask Questions:**
|
|
5. **"What do I remember about this place?"** - Recall location history (ties to Memory Thief origin)
|
|
|
|
**Gather Information:**
|
|
6. **"Ask around the streets"** - Gather rumors from common folk (town/city only)
|
|
7. **"Visit the local tavern"** - Gather information and hear tales (town/city only)
|
|
|
|
#### Elite Tier Additional Actions (+3 more, 10 total)
|
|
|
|
**Ask Questions:**
|
|
8. **"Search for hidden secrets"** - Thorough investigation of area (may find loot or lore)
|
|
|
|
**Gather Information:**
|
|
9. **"Seek out the town elder"** - Get official information and potential quests (town/city only)
|
|
|
|
**Travel:**
|
|
10. **"Chart a course to unexplored lands"** - Discover new locations (wilderness only)
|
|
|
|
#### Premium/Elite: Custom Free-Form Input
|
|
|
|
**"Ask the DM a custom question"** (Premium/Elite only)
|
|
- Premium: 250 character limit
|
|
- Elite: 500 character limit
|
|
- Examples: "I want to climb the tower and look for a vantage point", "Can I search the abandoned house for clues?"
|
|
|
|
---
|
|
|
|
## Action Prompt Data Model
|
|
|
|
### ActionPrompt Dataclass
|
|
|
|
```python
|
|
@dataclass
|
|
class ActionPrompt:
|
|
"""Represents a button-based action prompt available to players."""
|
|
|
|
prompt_id: str # Unique identifier (e.g., "ask_surroundings")
|
|
category: str # "ask", "travel", "gather"
|
|
display_text: str # Button text shown to player
|
|
description: str # Tooltip/help text
|
|
tier_required: str # "free", "basic", "premium", "elite"
|
|
context_filter: Optional[str] # "town", "wilderness", "any" (where action is available)
|
|
dm_prompt_template: str # Jinja2 template for AI prompt
|
|
|
|
def is_available(self, user_tier: str, location_type: str) -> bool:
|
|
"""Check if this action is available to the user."""
|
|
# Check tier requirement
|
|
tier_hierarchy = ["free", "basic", "premium", "elite"]
|
|
if tier_hierarchy.index(user_tier) < tier_hierarchy.index(self.tier_required):
|
|
return False
|
|
|
|
# Check context filter
|
|
if self.context_filter and self.context_filter != "any":
|
|
return location_type == self.context_filter
|
|
|
|
return True
|
|
```
|
|
|
|
### YAML Action Definitions
|
|
|
|
Actions will be defined in `/app/data/action_prompts.yaml`:
|
|
|
|
```yaml
|
|
actions:
|
|
- prompt_id: "ask_surroundings"
|
|
category: "ask"
|
|
display_text: "What do I see around me?"
|
|
description: "Get a description of your current surroundings"
|
|
tier_required: "free"
|
|
context_filter: "any"
|
|
dm_prompt_template: |
|
|
The player is currently in {{ location_name }}.
|
|
Describe what they see, hear, and sense around them.
|
|
Include atmosphere, notable features, and any immediate points of interest.
|
|
|
|
- prompt_id: "check_dangers"
|
|
category: "ask"
|
|
display_text: "Are there any dangers nearby?"
|
|
description: "Check for threats, enemies, or hazards in the area"
|
|
tier_required: "free"
|
|
context_filter: "any"
|
|
dm_prompt_template: |
|
|
The player is in {{ location_name }} and wants to know about nearby dangers.
|
|
Assess the area for threats: enemies, traps, environmental hazards.
|
|
Be honest about danger level but maintain narrative tension.
|
|
|
|
- prompt_id: "travel_town"
|
|
category: "travel"
|
|
display_text: "Travel to..."
|
|
description: "Fast travel to a known town or city"
|
|
tier_required: "free"
|
|
context_filter: "any"
|
|
dm_prompt_template: |
|
|
The player is traveling from {{ current_location }} to {{ destination }}.
|
|
Describe a brief travel montage. Include:
|
|
- Journey description (terrain, weather)
|
|
- Any minor encounters or observations
|
|
- Arrival at destination
|
|
|
|
- prompt_id: "explore_area"
|
|
category: "travel"
|
|
display_text: "Explore the area"
|
|
description: "Wander and explore your current region"
|
|
tier_required: "free"
|
|
context_filter: "any"
|
|
dm_prompt_template: |
|
|
The player explores {{ location_name }}.
|
|
Generate a random encounter or discovery:
|
|
- 30% chance: Minor combat encounter
|
|
- 30% chance: Interesting location/landmark
|
|
- 20% chance: NPC interaction
|
|
- 20% chance: Nothing significant
|
|
|
|
- prompt_id: "recall_memory"
|
|
category: "ask"
|
|
display_text: "What do I remember about this place?"
|
|
description: "Recall memories or knowledge about current location"
|
|
tier_required: "premium"
|
|
context_filter: "any"
|
|
dm_prompt_template: |
|
|
The player tries to recall memories about {{ location_name }}.
|
|
{% if character.origin == "memory_thief" %}
|
|
The player has fragmented memories due to their amnesia.
|
|
Provide vague, incomplete recollections that hint at a past here.
|
|
{% else %}
|
|
Provide relevant historical knowledge or personal memories.
|
|
{% endif %}
|
|
|
|
- prompt_id: "gather_street_rumors"
|
|
category: "gather"
|
|
display_text: "Ask around the streets"
|
|
description: "Talk to common folk and gather local rumors"
|
|
tier_required: "premium"
|
|
context_filter: "town"
|
|
dm_prompt_template: |
|
|
The player mingles with townsfolk in {{ location_name }}.
|
|
Generate 2-3 local rumors or pieces of information:
|
|
- Local events or concerns
|
|
- Potential quest hooks
|
|
- World lore or history
|
|
|
|
- prompt_id: "visit_tavern"
|
|
category: "gather"
|
|
display_text: "Visit the local tavern"
|
|
description: "Gather information and hear tales at the tavern"
|
|
tier_required: "premium"
|
|
context_filter: "town"
|
|
dm_prompt_template: |
|
|
The player enters the tavern in {{ location_name }}.
|
|
Describe the atmosphere and provide:
|
|
- Overheard conversations
|
|
- Rumors and tales
|
|
- Potential quest opportunities
|
|
- NPC interactions
|
|
|
|
- prompt_id: "search_secrets"
|
|
category: "ask"
|
|
display_text: "Search for hidden secrets"
|
|
description: "Thoroughly investigate the area for hidden items or lore"
|
|
tier_required: "elite"
|
|
context_filter: "any"
|
|
dm_prompt_template: |
|
|
The player conducts a thorough search of {{ location_name }}.
|
|
Roll for discovery (60% chance of finding something):
|
|
- Hidden items or treasures
|
|
- Secret passages or areas
|
|
- Lore fragments or journals
|
|
- Environmental storytelling clues
|
|
|
|
- prompt_id: "seek_elder"
|
|
category: "gather"
|
|
display_text: "Seek out the town elder"
|
|
description: "Get official information and guidance from town leadership"
|
|
tier_required: "elite"
|
|
context_filter: "town"
|
|
dm_prompt_template: |
|
|
The player seeks audience with the town elder of {{ location_name }}.
|
|
The elder provides:
|
|
- Official town history and current situation
|
|
- Important quests or tasks
|
|
- Warnings about regional threats
|
|
- Access to restricted information
|
|
|
|
- prompt_id: "chart_course"
|
|
category: "travel"
|
|
display_text: "Chart a course to unexplored lands"
|
|
description: "Venture into unknown territory to discover new locations"
|
|
tier_required: "elite"
|
|
context_filter: "wilderness"
|
|
dm_prompt_template: |
|
|
The player ventures into unexplored territory from {{ location_name }}.
|
|
Generate a new location discovery:
|
|
- Name and type of location
|
|
- Initial description
|
|
- Potential dangers or opportunities
|
|
- Add to player's discovered locations
|
|
```
|
|
|
|
---
|
|
|
|
## Session Management
|
|
|
|
### Solo Session Creation
|
|
|
|
**Endpoint:** `POST /api/v1/sessions`
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"character_id": "char_abc123",
|
|
"session_type": "solo"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"session_id": "session_xyz789",
|
|
"character_id": "char_abc123",
|
|
"session_type": "solo",
|
|
"current_location": "thornfield_plains",
|
|
"turn_number": 0,
|
|
"status": "active",
|
|
"created_at": "2025-11-16T10:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Taking a Story Action
|
|
|
|
**Endpoint:** `POST /api/v1/sessions/{session_id}/action`
|
|
|
|
**Request Body (Button Action):**
|
|
```json
|
|
{
|
|
"action_type": "prompt",
|
|
"prompt_id": "ask_surroundings"
|
|
}
|
|
```
|
|
|
|
**Request Body (Custom Free-Form - Premium/Elite Only):**
|
|
```json
|
|
{
|
|
"action_type": "custom",
|
|
"custom_text": "I want to climb the old tower and scout the horizon"
|
|
}
|
|
```
|
|
|
|
**Request Body (Ask DM - Out-of-Character Question):**
|
|
```json
|
|
{
|
|
"action_type": "ask_dm",
|
|
"question": "Is there anyone around who might see me steal this item?"
|
|
}
|
|
```
|
|
|
|
> **Note:** Ask DM questions are informational only. They don't advance the story, increment the turn number, or cause game state changes. They use a separate rate limit from regular actions.
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"session_id": "session_xyz789",
|
|
"turn_number": 1,
|
|
"action_taken": "What do I see around me?",
|
|
"dm_response": "You stand in the windswept Thornfield Plains, tall grasses swaying in the breeze. To the north, you can make out the silhouette of a crumbling watchtower against the grey sky. The air smells of rain and distant smoke. A worn path leads east toward what might be a settlement.",
|
|
"state_changes": {
|
|
"discovered_locations": ["old_watchtower"]
|
|
},
|
|
"quest_offered": null,
|
|
"ai_cost": 0.025,
|
|
"timestamp": "2025-11-16T10:01:23Z"
|
|
}
|
|
```
|
|
|
|
### Getting Session State
|
|
|
|
**Endpoint:** `GET /api/v1/sessions/{session_id}`
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"session_id": "session_xyz789",
|
|
"character_id": "char_abc123",
|
|
"session_type": "solo",
|
|
"current_location": "thornfield_plains",
|
|
"location_type": "wilderness",
|
|
"turn_number": 5,
|
|
"status": "active",
|
|
"active_quests": ["quest_001"],
|
|
"discovered_locations": ["thornfield_plains", "old_watchtower", "riverwatch"],
|
|
"conversation_history": [
|
|
{
|
|
"turn": 1,
|
|
"action": "What do I see around me?",
|
|
"dm_response": "You stand in the windswept Thornfield Plains..."
|
|
}
|
|
],
|
|
"available_actions": [
|
|
{
|
|
"prompt_id": "ask_surroundings",
|
|
"display_text": "What do I see around me?",
|
|
"category": "ask"
|
|
},
|
|
{
|
|
"prompt_id": "check_dangers",
|
|
"display_text": "Are there any dangers nearby?",
|
|
"category": "ask"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## AI Prompt Engineering
|
|
|
|
### Context Building
|
|
|
|
Each player action requires building a comprehensive prompt for the AI Dungeon Master:
|
|
|
|
```python
|
|
def build_dm_prompt(session: GameSession, character: Character, action: ActionPrompt, custom_text: Optional[str] = None) -> str:
|
|
"""Build the AI prompt with full context."""
|
|
|
|
context = {
|
|
# Character info
|
|
"character_name": character.name,
|
|
"character_class": character.player_class.name,
|
|
"character_level": character.level,
|
|
"character_origin": character.origin,
|
|
|
|
# Location info
|
|
"location_name": session.game_state.current_location,
|
|
"location_type": get_location_type(session.game_state.current_location),
|
|
"discovered_locations": session.game_state.discovered_locations,
|
|
|
|
# Quest info
|
|
"active_quests": get_quest_details(session.game_state.active_quests),
|
|
|
|
# Recent history (last 3 turns)
|
|
"recent_history": session.conversation_history[-3:],
|
|
|
|
# Player action
|
|
"player_action": custom_text or action.display_text,
|
|
}
|
|
|
|
# Render base prompt template
|
|
if custom_text:
|
|
prompt = render_custom_action_prompt(context, custom_text)
|
|
else:
|
|
prompt = render_template(action.dm_prompt_template, context)
|
|
|
|
return prompt
|
|
```
|
|
|
|
### AI Model Selection by Tier
|
|
|
|
```python
|
|
def get_ai_model_for_tier(user_tier: str) -> str:
|
|
"""Select AI model based on user subscription tier."""
|
|
model_map = {
|
|
"free": "replicate/llama-3-8b", # Free tier model
|
|
"basic": "claude-haiku", # Fast, cheap
|
|
"premium": "claude-sonnet", # Balanced
|
|
"elite": "claude-opus" # Best quality
|
|
}
|
|
return model_map.get(user_tier, "replicate/llama-3-8b")
|
|
```
|
|
|
|
### Response Parsing and Item Extraction
|
|
|
|
AI responses include both narrative text and structured game actions. The system automatically parses responses to extract items, gold, and experience to apply to the player's character.
|
|
|
|
**Response Format:**
|
|
|
|
The AI returns narrative followed by a structured `---GAME_ACTIONS---` block:
|
|
|
|
```
|
|
The kind vendor smiles and presses a few items into your hands...
|
|
|
|
---GAME_ACTIONS---
|
|
{
|
|
"items_given": [
|
|
{"name": "Stale Bread", "type": "consumable", "description": "A half-loaf of bread", "value": 1},
|
|
{"item_id": "health_potion"}
|
|
],
|
|
"items_taken": [],
|
|
"gold_given": 5,
|
|
"gold_taken": 0,
|
|
"experience_given": 10,
|
|
"quest_offered": null,
|
|
"quest_completed": null,
|
|
"location_change": null
|
|
}
|
|
```
|
|
|
|
**Item Grant Types:**
|
|
|
|
1. **Existing Items** (by `item_id`): References items from the game data registry
|
|
2. **Generic Items** (by name/type/description): Creates simple mundane items
|
|
|
|
**Item Validation:**
|
|
|
|
All AI-granted items are validated before being added to inventory:
|
|
|
|
- **Level requirements**: Items requiring higher level than character are rejected
|
|
- **Class restrictions**: Class-specific items validated against character class
|
|
- **Validation logging**: Failed items logged for review but don't fail the request
|
|
|
|
**Processing Flow:**
|
|
|
|
```python
|
|
# 1. Parse AI response
|
|
parsed_response = parse_ai_response(ai_response.narrative)
|
|
|
|
# 2. Validate and resolve items
|
|
for item_grant in parsed_response.game_changes.items_given:
|
|
item, error = validator.validate_and_resolve_item(item_grant, character)
|
|
if item:
|
|
character.add_item(item)
|
|
|
|
# 3. Apply gold/experience
|
|
character.add_gold(parsed_response.game_changes.gold_given)
|
|
character.add_experience(parsed_response.game_changes.experience_given)
|
|
|
|
# 4. Save to database
|
|
```
|
|
|
|
**Generic Item Templates:**
|
|
|
|
Common mundane items have predefined templates in `/app/data/generic_items.yaml`:
|
|
- Light sources: torch, lantern, candle
|
|
- Food: bread, cheese, rations, ale
|
|
- Tools: rope, flint, bedroll, crowbar
|
|
- Supplies: ink, parchment, bandages
|
|
|
|
---
|
|
|
|
## UI/UX Design
|
|
|
|
### Story Gameplay Screen Layout
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Code of Conquest [Char] [Logout] │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ 📍 Current Location: Thornfield Plains │
|
|
│ 🎯 Active Quests: 1 │
|
|
│ │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Conversation History │
|
|
│ ┌───────────────────────────────────────────────┐ │
|
|
│ │ │ │
|
|
│ │ Turn 1 │ │
|
|
│ │ You: What do I see around me? │ │
|
|
│ │ │ │
|
|
│ │ DM: You stand in the windswept Thornfield │ │
|
|
│ │ Plains, tall grasses swaying in the breeze. │ │
|
|
│ │ To the north, you can make out the │ │
|
|
│ │ silhouette of a crumbling watchtower... │ │
|
|
│ │ │ │
|
|
│ │ Turn 2 │ │
|
|
│ │ You: Explore the area │ │
|
|
│ │ │ │
|
|
│ │ DM: As you wander through the plains, you │ │
|
|
│ │ stumble upon fresh tracks leading north... │ │
|
|
│ │ │ │
|
|
│ └───────────────────────────────────────────────┘ │
|
|
│ │
|
|
├─────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Take Action │
|
|
│ │
|
|
│ Ask Questions: │
|
|
│ [What do I see?] [Any dangers?] │
|
|
│ [What do I remember?] 🔒Premium │
|
|
│ │
|
|
│ Travel: │
|
|
│ [Travel to ▼] [Explore area] │
|
|
│ │
|
|
│ Gather Information: │
|
|
│ [Ask around streets] 🔒Premium │
|
|
│ [Visit tavern] 🔒Premium │
|
|
│ │
|
|
│ Custom (Premium/Elite): │
|
|
│ ┌───────────────────────────────────────────────┐ │
|
|
│ │ I want to... │ │
|
|
│ └───────────────────────────────────────────────┘ │
|
|
│ [Ask the DM] (250 chars) │
|
|
│ │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Action Button States
|
|
|
|
- **Available**: Full color, clickable
|
|
- **Tier-locked**: Greyed out with 🔒 icon and "Premium" or "Elite" badge
|
|
- **Context-locked**: Hidden (e.g., "Visit tavern" not shown in wilderness)
|
|
- **Loading**: Disabled with spinner during AI processing
|
|
|
|
### Conversation History Display
|
|
|
|
- Scrollable container with newest at bottom
|
|
- Turn numbers clearly visible
|
|
- Player actions in different style from DM responses
|
|
- Quest notifications highlighted
|
|
- Location changes noted
|
|
- Timestamps optional (show on hover)
|
|
|
|
---
|
|
|
|
## Cost Management
|
|
|
|
### Per-Turn Cost Estimates
|
|
|
|
| Tier | AI Model | Est. Tokens | Cost/Turn | Daily Limit (Turns) |
|
|
|------|----------|-------------|-----------|---------------------|
|
|
| Free | Replicate Llama-3 8B | ~500 | $0 | Unlimited |
|
|
| Basic | Claude Haiku | ~800 | ~$0.01 | 200 turns/day |
|
|
| Premium | Claude Sonnet | ~1200 | ~$0.03 | 100 turns/day |
|
|
| Elite | Claude Opus | ~1500 | ~$0.10 | 50 turns/day |
|
|
|
|
### Cost Control Measures
|
|
|
|
1. **Token limits**: Cap AI responses at 500 tokens (narrative should be concise)
|
|
2. **Daily turn limits**: Prevent abuse and runaway costs
|
|
3. **Prompt caching**: Cache location descriptions, character context (Phase 4B)
|
|
4. **Model selection**: Free tier uses free model, paid tiers get better quality
|
|
5. **Monitoring**: Track per-user AI spending daily
|
|
|
|
---
|
|
|
|
## Implementation Timeline
|
|
|
|
### Week 8 of Phase 4 (Days 8-14)
|
|
|
|
**Day 8: Action Prompt System**
|
|
- Create ActionPrompt dataclass
|
|
- Create ActionPromptLoader (YAML-based)
|
|
- Define all 10 action prompts in `action_prompts.yaml`
|
|
- Write unit tests for action availability logic
|
|
|
|
**Day 9: Session Service**
|
|
- Create SessionService (create solo sessions, manage state)
|
|
- Session creation logic
|
|
- Session state retrieval
|
|
- Turn advancement logic
|
|
- Database operations
|
|
|
|
**Day 10: Story Progression API**
|
|
- Implement story action endpoints:
|
|
- `POST /api/v1/sessions` - Create solo session
|
|
- `GET /api/v1/sessions/{id}` - Get session state
|
|
- `POST /api/v1/sessions/{id}/action` - Take action
|
|
- `GET /api/v1/sessions/{id}/history` - Get conversation history
|
|
- Validate actions based on tier and context
|
|
- Integrate with AI service (from Week 7)
|
|
|
|
**Day 11-12: Story Progression UI**
|
|
- Create `templates/game/story.html` - Main story gameplay screen
|
|
- Action button rendering (tier-based, context-aware)
|
|
- Conversation history display (scrollable, formatted)
|
|
- HTMX integration for real-time turn updates
|
|
- Loading states during AI processing
|
|
|
|
**Day 13-14: Integration Testing**
|
|
- Test full story turn flow
|
|
- Test tier restrictions
|
|
- Test action context filtering
|
|
- Test AI prompt building and response parsing
|
|
- Test session state persistence
|
|
- Performance testing (response times, cost tracking)
|
|
|
|
---
|
|
|
|
## Testing Criteria
|
|
|
|
### Unit Tests
|
|
- ✅ ActionPrompt.is_available() logic
|
|
- ✅ ActionPromptLoader loads all prompts correctly
|
|
- ✅ Tier hierarchy validation
|
|
- ✅ Context filtering (town vs wilderness)
|
|
|
|
### Integration Tests
|
|
- ✅ Create solo session
|
|
- ✅ Take action (button-based)
|
|
- ✅ Take action (custom text, Premium/Elite)
|
|
- ✅ Verify tier restrictions enforced
|
|
- ✅ Verify context filtering works
|
|
- ✅ Verify conversation history persists
|
|
- ✅ Verify AI cost tracking
|
|
- ✅ Verify daily turn limits
|
|
|
|
### Manual Testing
|
|
- ✅ Full story turn flow (10+ turns)
|
|
- ✅ All action types tested
|
|
- ✅ Custom text input (Premium/Elite)
|
|
- ✅ UI responsiveness
|
|
- ✅ Conversation history display
|
|
- ✅ Quest offering during turns (tested in Quest System phase)
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
- ✅ All 10 action prompts defined and loadable from YAML
|
|
- ✅ Action availability logic working (tier + context)
|
|
- ✅ Solo session creation and management functional
|
|
- ✅ Story action API endpoints operational
|
|
- ✅ AI prompt building with full context
|
|
- ✅ AI responses parsed and state updated correctly
|
|
- ✅ Story gameplay UI functional with HTMX
|
|
- ✅ Tier restrictions enforced (Free vs Premium vs Elite)
|
|
- ✅ Custom text input working for Premium/Elite tiers
|
|
- ✅ Conversation history persisted and displayed
|
|
- ✅ Cost tracking and daily limits enforced
|
|
|
|
---
|
|
|
|
## Future Enhancements (Post-MVP)
|
|
|
|
### Phase 13+
|
|
- **Dynamic action generation**: AI suggests context-specific actions
|
|
- **Voice narration**: Text-to-speech for DM responses (Elite tier)
|
|
- **Branching narratives**: Major story choices with consequences
|
|
- **Location memory**: AI remembers previous visits and descriptions
|
|
- ~~**NPC persistence**: Recurring NPCs with memory of past interactions~~ ✅ Implemented
|
|
- **Session sharing**: Export/share story sessions as Markdown
|
|
- **Illustrations**: AI-generated scene images (Elite tier)
|
|
- **Random encounters**: Travel between locations may trigger events
|
|
- **NPC schedules**: NPCs move between locations based on time of day
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- **[QUEST_SYSTEM.md](QUEST_SYSTEM.md)** - Quest offering and tracking during story turns
|
|
- **[GAME_SYSTEMS.md](GAME_SYSTEMS.md)** - Combat system (separate turn-based gameplay)
|
|
- **[DATA_MODELS.md](DATA_MODELS.md)** - GameSession, ConversationEntry, ActionPrompt, Location, NPC models
|
|
- **[API_REFERENCE.md](API_REFERENCE.md)** - Full API endpoint documentation (includes Travel and NPC APIs)
|
|
- **[ROADMAP.md](ROADMAP.md)** - Phase 4 timeline and milestones
|
|
|
|
### Data Files
|
|
- `/app/data/regions/crossville.yaml` - Crossville region locations
|
|
- `/app/data/npcs/crossville_npcs.yaml` - NPCs in Crossville region
|
|
- `/app/data/action_prompts.yaml` - Player action definitions
|
|
|
|
### Services
|
|
- `LocationLoader` - Loads and caches location/region data
|
|
- `NPCLoader` - Loads and caches NPC definitions
|
|
- `SessionService` - Manages game sessions and state
|
|
|
|
---
|
|
|
|
**Document Version:** 1.2
|
|
**Created:** November 16, 2025
|
|
**Last Updated:** November 24, 2025
|