Files
Code_of_Conquest/docs/plans/noble-singing-origami.md
Phillip Tarrant df26abd207 feat: Implement Phase 5 Quest System (100% complete)
Add YAML-driven quest system with context-aware offering:

Core Implementation:
- Quest data models (Quest, QuestObjective, QuestReward, QuestTriggers)
- QuestService for YAML loading and caching
- QuestEligibilityService with level, location, and probability filtering
- LoreService stub (MockLoreService) ready for Phase 6 Weaviate integration

Quest Content:
- 5 example quests across difficulty tiers (2 easy, 2 medium, 1 hard)
- Quest-centric design: quests define their NPC givers
- Location-based probability weights for natural quest offering

AI Integration:
- Quest offering section in npc_dialogue.j2 template
- Response parser extracts [QUEST_OFFER:quest_id] markers
- AI naturally weaves quest offers into NPC conversations

API Endpoints:
- POST /api/v1/quests/accept - Accept quest offer
- POST /api/v1/quests/decline - Decline quest offer
- POST /api/v1/quests/progress - Update objective progress
- POST /api/v1/quests/complete - Complete quest, claim rewards
- POST /api/v1/quests/abandon - Abandon active quest
- GET /api/v1/characters/{id}/quests - List character quests
- GET /api/v1/quests/{quest_id} - Get quest details

Frontend:
- Quest tracker sidebar with HTMX integration
- Quest offer modal for accept/decline flow
- Quest detail modal for viewing progress
- Combat service integration for kill objective tracking

Testing:
- Unit tests for Quest models and serialization
- Integration tests for full quest lifecycle
- Comprehensive test coverage for eligibility service

Documentation:
- Reorganized docs into /docs/phases/ structure
- Added Phase 5-12 planning documents
- Updated ROADMAP.md with new structure

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 15:42:55 -06:00

13 KiB

Phase 5 Quest System - NPC Integration Plan

Summary

Implement a quest-centric system where quests define their NPC givers, and NPCs automatically offer eligible quests during conversations using a probability roll + AI selection approach.

Key Design Decisions

Decision Choice Rationale
Data ownership Quest-centric Quests define quest_giver_npc_ids. Adding new quests doesn't touch NPC files.
Offer trigger Probability + AI select Location-based roll first, then AI naturally weaves selected quest into dialogue.
Lore integration Stub service Create LoreService interface with mock data now; swap to Weaviate in Phase 6.

Architecture Overview

NPC Conversation Flow with Quest Integration:

POST /api/v1/npcs/{npc_id}/talk
         │
         ▼
┌─────────────────────────────┐
│ 1. Load Context             │
│    - NPC, Character, Location│
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│ 2. Quest Eligibility Check  │
│    QuestEligibilityService  │
│    - Find quests where NPC  │
│      is quest_giver         │
│    - Filter by char level,  │
│      completed, active, etc │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│ 3. Probability Roll         │
│    - Town: 30%, Tavern: 35% │
│    - Wilderness: 5%         │
│    - If fail → no offer     │
└────────────┬────────────────┘
             │ (if success)
             ▼
┌─────────────────────────────┐
│ 4. Get Lore Context (stub)  │
│    LoreService.get_context()│
│    - Quest embedded lore    │
│    - Mock regional lore     │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│ 5. Build AI Prompt          │
│    - NPC persona + knowledge│
│    - Quest offering context │
│    - Lore context           │
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│ 6. AI Generates Dialogue    │
│    Naturally weaves quest   │
│    offer into conversation  │
│    Includes [QUEST_OFFER:id]│
└────────────┬────────────────┘
             │
             ▼
┌─────────────────────────────┐
│ 7. Parse Response           │
│    Extract quest_offered    │
│    Return to frontend       │
└─────────────────────────────┘

YAML Schema Designs

Quest YAML (Quest-Centric Approach)

# /api/app/data/quests/easy/cellar_rats.yaml
quest_id: quest_cellar_rats
name: "Rat Problem in the Cellar"
description: |
  Giant rats have infested the Rusty Anchor's cellar.
difficulty: easy

# NPC Quest Givers (quest defines this, not NPC)
quest_giver_npc_ids:
  - npc_grom_ironbeard
quest_giver_name: "Grom Ironbeard"  # Display fallback

# Location context
location_id: crossville_tavern
region_id: crossville

# Offering conditions
offering_triggers:
  location_types: ["tavern"]
  min_character_level: 1
  max_character_level: 5
  required_quests_completed: []
  probability_weights:
    tavern: 0.35
    town: 0.20

# NPC-specific offer dialogue (keyed by NPC ID)
npc_offer_dialogues:
  npc_grom_ironbeard:
    dialogue: |
      *leans in conspiratorially* Got a problem, friend. Giant rats
      in me cellar. Been scaring off customers. 50 gold for whoever
      clears 'em out.
    conditions:
      min_relationship: 30
      required_flags: []
      forbidden_flags: ["refused_rat_quest"]

# What NPCs know about this quest (for AI context)
npc_quest_knowledge:
  npc_grom_ironbeard:
    - "The rats started appearing about a week ago"
    - "They seem bigger than normal rats"
    - "Old smuggling tunnels under the cellar"

# Embedded lore (used before Weaviate exists)
lore_context:
  backstory: |
    The cellar connects to old smuggling tunnels from Captain
    Morgath's days. Recent earthquakes may have reopened them.
  world_connections:
    - "The earthquakes also disturbed the Old Mines"
  regional_hints:
    - "Smuggling was common 50 years ago in Crossville"

# Narrative hooks for natural dialogue
dialogue_templates:
  narrative_hooks:
    - "mentions unusual scratching sounds from below"
    - "complains about spoiled food supplies"
    - "nervously glances toward the cellar door"

# Objectives
objectives:
  - objective_id: kill_rats
    description: "Clear out the giant rats (0/10)"
    objective_type: kill
    required_progress: 10
    target_enemy_type: giant_rat

# Rewards
rewards:
  gold: 50
  experience: 100
  items: []
  relationship_bonuses:
    npc_grom_ironbeard: 10
  unlocks_quests: ["quest_tunnel_mystery"]

# Completion
completion_dialogue:
  npc_grom_ironbeard: |
    *actually smiles* Well done! Rats are gone, cellar's safe.
    Here's your coin. Drink's on the house tonight.

NPC YAML (Simplified - No Quest Lists)

NPCs no longer need quest_giver_for since quests define their givers.

# /api/app/data/npcs/crossville/npc_grom_ironbeard.yaml
npc_id: npc_grom_ironbeard
name: Grom Ironbeard
role: bartender
location_id: crossville_tavern
image_url: /static/images/npcs/crossville/grom_ironbeard.png

# All other existing fields remain unchanged
personality: { ... }
appearance: { ... }
knowledge: { ... }
dialogue_hooks: { ... }
# NO quest_giver_for field needed

Implementation Tasks

Group 1: Quest Data Models (3 tasks)

ID Task File Notes
5.1 Create Quest dataclass /api/app/models/quest.py Include all fields from YAML schema
5.2 Create QuestObjective, QuestReward, QuestTriggers /api/app/models/quest.py Objective types: kill, collect, travel, interact, discover
5.3 Add to_dict()/from_dict() serialization /api/app/models/quest.py Test round-trip JSON serialization

Group 2: Quest Loading Service (3 tasks)

ID Task File Notes
5.4 Create /api/app/data/quests/ directory structure directories easy/, medium/, hard/, epic/
5.5 Implement QuestService (loader) /api/app/services/quest_service.py Follow npc_loader.py pattern with caching
5.6 Write 5 example quests /api/app/data/quests/*.yaml 2 easy, 2 medium, 1 hard

Group 3: Quest Eligibility Service (3 tasks)

ID Task File Notes
5.7 Create QuestEligibilityService /api/app/services/quest_eligibility_service.py Core eligibility logic
5.8 Implement get_quests_for_npc() same file Find quests where NPC is quest_giver
5.9 Implement probability roll + filtering same file Location-based weights, level check, status check

Group 4: Lore Service Stub (2 tasks)

ID Task File Notes
5.10 Create LoreService interface /api/app/services/lore_service.py Abstract interface for Phase 6
5.11 Implement MockLoreService same file Returns quest's embedded lore_context

Group 5: AI Prompt Integration (3 tasks)

ID Task File Notes
5.12 Add quest offering section to template /api/app/ai/templates/npc_dialogue.j2 Quest context + natural weaving instructions
5.13 Add lore context section to template same file Filtered lore for NPC knowledge
5.14 Implement quest offer parsing /api/app/ai/response_parser.py Extract [QUEST_OFFER:quest_id] markers

Group 6: NPC API Integration (4 tasks)

ID Task File Notes
5.15 Integrate eligibility check into talk_to_npc /api/app/api/npcs.py Check before building AI context
5.16 Add quest context to AI task /api/app/tasks/ai_tasks.py Modify _process_npc_dialogue_task
5.17 Handle quest_offered in response /api/app/api/npcs.py Parse and include in API response
5.18 Remove quest_giver_for from NPC model /api/app/models/npc.py Clean up old field if exists

Group 7: Quest Accept/Manage Endpoints (4 tasks)

ID Task File Notes
5.19 Create quests blueprint /api/app/api/quests.py Register in __init__.py
5.20 Implement POST /api/v1/quests/accept same file Add to active_quests, update relationship
5.21 Implement POST /api/v1/quests/decline same file Set refused_{quest_id} flag
5.22 Implement GET /api/v1/characters/{id}/quests same file List active and completed quests

Group 8: Testing & Validation (3 tasks)

ID Task File Notes
5.23 Unit tests for Quest models /api/tests/test_quest_models.py Serialization, validation
5.24 Unit tests for QuestEligibilityService /api/tests/test_quest_eligibility.py Filtering logic
5.25 Integration test: full quest offer flow /api/tests/test_quest_integration.py NPC talk → offer → accept

Critical Files to Read Before Implementation

File Reason
/api/app/services/npc_loader.py Pattern for YAML loading with caching
/api/app/models/npc.py Current NPC dataclass structure
/api/app/api/npcs.py Current talk_to_npc endpoint implementation
/api/app/tasks/ai_tasks.py _process_npc_dialogue_task function (lines 667-795)
/api/app/ai/templates/npc_dialogue.j2 Current prompt template structure
/api/app/models/character.py active_quests, completed_quests fields

Data Flow Summary

1. Player talks to NPC → POST /api/v1/npcs/{npc_id}/talk

2. Backend:
   a. QuestService.get_quests_for_npc(npc_id)
      → Find quests where npc_id in quest_giver_npc_ids

   b. QuestEligibilityService.filter_eligible(quests, character)
      → Remove: already active, completed, wrong level, flags block

   c. Probability roll based on location
      → 35% chance in tavern, 5% in wilderness, etc.

   d. If roll succeeds + eligible quests exist:
      → Pick quest (first eligible or AI-selected if multiple)
      → Build QuestOfferContext with dialogue + lore

   e. Add quest context to AI prompt

   f. AI generates dialogue, naturally mentions quest
      → Includes [QUEST_OFFER:quest_cellar_rats] if offering

3. Parse response, return to frontend:
   {
     "dialogue": "NPC's natural dialogue...",
     "quest_offered": {
       "quest_id": "quest_cellar_rats",
       "name": "Rat Problem",
       "description": "...",
       "rewards": {...}
     }
   }

4. Frontend shows quest offer UI
   → Player clicks Accept

5. POST /api/v1/quests/accept
   → Add to character.active_quests
   → Update NPC relationship (+5)
   → Return acceptance dialogue

Phase 6 Integration Points

When implementing Phase 6 (Weaviate lore), these touchpoints enable integration:

  1. LoreService interface - Replace MockLoreService with WeaviateLoreService
  2. Quest.lore_context - Supplement embedded lore with Weaviate queries
  3. NPC dialogue template - Lore section already prepared
  4. Knowledge filtering - LoreService.filter_for_npc() method exists

NPC YAML Migration

Existing NPC files need these changes:

Remove (if exists):

  • quest_giver_for: [...] - No longer needed

Keep unchanged:

  • location_id - Required
  • image_url - Required
  • All other fields - Unchanged

The quest_giver_for field in npc_mayor_aldric.yaml will be removed since quests now define their givers.


Success Criteria

  • Quest YAML schema implemented and validated
  • QuestService loads quests from YAML with caching
  • QuestEligibilityService filters correctly by all conditions
  • Probability roll works per location type
  • AI prompt includes quest context when offering
  • AI naturally weaves quest offers into dialogue
  • Quest offer parsing extracts [QUEST_OFFER:id] correctly
  • Accept endpoint adds quest to active_quests
  • Max 2 active quests enforced
  • Relationship bonus applied on quest completion
  • LoreService stub returns embedded lore_context