Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Feature 07: Quests & Dungeon Runs

Overview

The core gameplay loop: browse the quest board, start a run (which consumes real time), and review the results when it completes. This feature connects the combat engine (Feature 06) to the API and job queue, making the game actually playable.

Dependencies

  • Feature 06 (Combat Engine)
  • Feature 05 (Skill Queue) — active queue used for the run
  • Feature 03 (Items & Inventory) — equipped gear snapshot, loot rewards

Technical Tasks

1. Static Game Data — Quests

  • Create data/quests/ directory:
    • bounties/ — short quests (1-3 encounters, 15-45 min)
    • questlines/ — medium quests (5-8 encounters, 1-3 hours)
    • dungeon_crawls/ — long quests (10-20 encounters, 3-8 hours)
    • Per quest: id, name, type, level_range, base_duration_secs, encounters (ordered list), loot_table, difficulty_options, description, zone
    • Each encounter: type (combat/trap/decision/hazard/rest/puzzle), enemy_ids (for combat), difficulty_class (for skill checks), description
    • Start with 10-15 quests covering levels 1-10

2. Quest Board Logic (crates/game)

  • Create src/quest_board.rs:
    • generate_quest_board(character_level: u16, zone: Option<&str>, rng: &mut impl Rng) -> Vec<QuestBoardEntry>:
      • Return 6-10 level-appropriate quests
      • Mix of bounties, questlines, and dungeon crawls
      • Refreshes every 6 hours (timestamp-seeded for consistency)
    • QuestBoardEntry: quest_id, name, type, difficulty, estimated_duration, encounter_count, level_range, recommended_attributes
    • calculate_run_duration(quest: &QuestDefinition, difficulty: Difficulty, is_patron: bool) -> Duration:
      • Base duration from quest data
      • Multiply by difficulty modifier (Trivial 0.5x, Normal 1x, Hard 1.5x, Deadly 2x)
      • Patron: multiply by 0.667 (50% faster = 2/3 time)

3. Run Management — Database

  • Create migration 0006_create_runs.sql:
    • runs table: id, character_id, quest_id, difficulty, status (in_progress/completed/failed), skill_queue (JSONB snapshot), loadout (JSONB snapshot), supplies (JSONB snapshot), party_id, started_at, completes_at, completed_at, run_log (JSONB), rewards (JSONB)
    • Indexes: character_id, status WHERE in_progress, completes_at WHERE in_progress

4. Run Queries (crates/db)

  • Create src/queries/runs.rs:
    • create_run(pool, params) -> Run
    • find_active_run(pool, character_id) -> Option<Run>
    • find_run_by_id(pool, id) -> Option<Run>
    • find_runs_by_character(pool, character_id, limit, offset) -> Vec<Run>
    • complete_run(pool, run_id, status, run_log, rewards)

5. Run Start Flow (crates/api)

  • Create src/routes/runs.rs:
    • GET /api/quest-board?character_id=:
      • Generate quest board for character’s level
      • Return list of available quests with details
    • POST /api/runs:
      • Body: { character_id, quest_id, difficulty }
      • Validations:
        • Character not already in a run
        • Character meets quest level requirements
        • Character has an active skill queue
        • Character has gear equipped (at minimum a weapon)
      • Snapshot the character’s current state:
        • Skill queue (copied, not referenced — changes after start don’t affect the run)
        • Equipped gear (stats frozen at start time)
        • Supplies (deducted from inventory at start)
      • Calculate completes_at timestamp
      • Create run record (status: in_progress)
      • Enqueue delayed job: resolve-run with delay = duration
      • Return { run_id, completes_at }

6. Run Resolution Worker (crates/workers)

  • Create src/simulation_worker.rs:
    • Register job handler for resolve-run queue
    • On job received:
      1. Load run record from DB
      2. Load character snapshot + quest definition
      3. Build SimulationContext with seeded RNG
      4. Call resolve_run() from the game crate
      5. Write results to DB:
        • Update run: status, completed_at, run_log (JSONB), rewards
        • If success: grant XP, gold, loot items to character
        • Update weapon proficiency (based on weapon used)
        • Check for level up, apply if needed
      6. Create notification record (Feature 14)
      7. If character has push enabled, send push notification

7. Run Results & History (crates/api)

  • Extend src/routes/runs.rs:
    • GET /api/runs/:id:
      • Return run details: status, quest info, timestamps
      • If completed: include run_log, rewards, failure analysis (if failed)
    • GET /api/characters/:id/runs:
      • Paginated run history
      • Summary view: quest name, difficulty, status, rewards, date
    • POST /api/runs/:id/collect:
      • Collect loot rewards from a completed run
      • Move items from run rewards into character’s backpack
      • Grant XP and gold to character
      • Idempotent (can’t collect twice)

8. Supplies System

  • Define supply items in data/items/consumables/supplies.toml:
    • Rations (restore HP during rest encounters)
    • Torches (improve Perception in dark encounters)
    • Healing Potions (emergency heal — auto-used when HP < 20%)
    • Antidotes (cure poison)
    • Repair Kits (prevent gear durability loss)
  • POST /api/runs deducts selected supplies from inventory at run start
  • Supplies affect encounter resolution (checked during simulation)

9. Client — Quest Board & Runs

  • Create routes/(game)/quest-board/+page.svelte:
    • Quest board: list of available quests as cards
    • Each card: quest name, type badge, difficulty selector, encounter count, estimated duration, level range, reward preview
    • “Start Run” button per quest → confirmation modal showing:
      • Active skill queue summary
      • Equipped gear summary
      • Supply selection (optional)
      • Estimated completion time
  • Create routes/(game)/runs/+page.svelte:
    • Active run panel (if any): quest name, progress bar to completesAt, countdown timer
    • Run history: paginated list of past runs
  • Create routes/(game)/runs/[id]/+page.svelte:
    • Run result detail page
    • Encounter-by-encounter timeline (collapsible)
    • Each encounter: rounds, actions, rolls, outcomes (the Run Replay Viewer from the tech architecture)
    • Loot display with “Collect All” button
    • If failed: failure analysis (which encounter, what went wrong)
  • Wire polling: active run status polls every 60s; on timer expiry, immediate refetch

Tests

Unit Tests

  • generate_quest_board: returns level-appropriate quests
  • generate_quest_board: includes mix of quest types
  • generate_quest_board: deterministic for same time window + seed
  • calculate_run_duration: Patron gets 2/3 time
  • calculate_run_duration: difficulty modifier applied correctly

Integration Tests

  • GET /api/quest-board returns quests appropriate to character level
  • POST /api/runs creates run with correct completes_at timestamp
  • POST /api/runs snapshots skill queue and loadout
  • POST /api/runs deducts supplies from inventory
  • POST /api/runs rejects if character already in a run
  • POST /api/runs rejects if no active skill queue
  • POST /api/runs rejects if quest level too high
  • GET /api/runs/:id returns in_progress run with completes_at
  • Worker resolves run and updates DB with results and log
  • Worker grants XP and gold to character on successful run
  • Worker creates loot items in character’s rewards
  • Worker updates weapon proficiency after run
  • Worker triggers level-up if XP threshold reached
  • POST /api/runs/:id/collect moves loot to backpack and grants XP/gold
  • POST /api/runs/:id/collect second call → 409 (already collected)
  • GET /api/characters/:id/runs returns paginated history
  • Full end-to-end: create character → equip gear → set queue → start run → worker resolves → collect loot → verify character state updated