# Quest System Design (MVP)

Status: Draft
Date: 2026-03-28

---

## 1. Town Definitions

Towns are fixed locations along the hero's walking road, one per biome. They replace the procedural `isCityMarket` plaza clusters with authored, named settlements. The hero visits each town automatically while auto-walking through the biome.

| # | Town Name | Biome | Level Range | World X (approx) | Description |
|---|-----------|-------|-------------|-------------------|-------------|
| 1 | Willowdale | Meadow | 1-5 | 50 | Starting village, tutorial NPCs |
| 2 | Thornwatch | Forest | 5-10 | 200 | Logging camp turned outpost |
| 3 | Ashengard | Ruins | 10-16 | 400 | Crumbling fortress with survivors |
| 4 | Redcliff | Canyon | 16-22 | 650 | Mining settlement on canyon edge |
| 5 | Boghollow | Swamp | 22-28 | 900 | Stilt village above murky waters |
| 6 | Cinderkeep | Volcanic | 28-34 | 1200 | Forge town in cooled lava flows |
| 7 | Starfall | Astral | 34-40 | 1550 | Floating platform outpost |

Each town occupies a rectangular region roughly 15x15 tiles centered on the road, rendered as `plaza` terrain. The hero enters when `position_x` falls within the town's bounding box.

### Town radius and entry

- Town center is at `(world_x, world_y)` stored in the `towns` table.
- Entry radius: 8 tiles (configurable). Hero is "in town" when `distance(hero, town_center) <= radius`.
- While in town the hero's state remains `walking` -- no combat spawns inside the radius.

---

## 2. NPC Types

Three NPC archetypes for MVP. Each NPC belongs to exactly one town.

| Type | Role | Interaction |
|------|------|-------------|
| `quest_giver` | Offers and completes quests | Tap to see available/active quests |
| `merchant` | Sells potions for gold | Tap to open shop (buy potions) |
| `healer` | Restores HP for gold | Tap to heal (costs gold, instant) |

### NPC placement

- Each town has 2-3 NPCs (1 quest giver always, +1-2 of merchant/healer).
- NPCs have a fixed offset from the town center (`offset_x`, `offset_y` in tiles).
- NPCs are non-interactive during combat (hero auto-walks past if fighting).

### NPC data model

NPCs are seeded, not player-created. Each has a `name`, `type`, `town_id`, and position offset.

---

## 3. Quest Types (MVP)

Three quest types, all trackable with a single integer counter.

### 3.1 `kill_count` -- Kill N enemies

- **Objective**: Kill `target_count` enemies of a specified type (or any type if `target_enemy_type` is NULL).
- **Tracking**: Increment `hero_quests.progress` each time the hero kills a matching enemy.
- **Example**: "Slay 10 Forest Wolves" (target_enemy_type = 'wolf', target_count = 10).

### 3.2 `visit_town` -- Visit a specific town

- **Objective**: Walk to the target town.
- **Tracking**: Set `progress = 1` when the hero enters the target town's radius.
- **Example**: "Deliver a message to Ashengard" (target_town_id = 3, target_count = 1).

### 3.3 `collect_item` -- Collect N item drops

- **Objective**: Collect `target_count` of a quest-specific drop from enemies.
- **Tracking**: When the hero kills an enemy in the quest's level range, roll a drop chance. On success, increment `progress`.
- **Drop chance**: 30% per eligible kill (configurable per quest in `drop_chance`).
- **Example**: "Collect 5 Spider Fangs" (target_count = 5, target_enemy_type = 'spider', drop_chance = 0.3).

### Quest lifecycle

```
available -> accepted -> (progress tracked) -> completed -> rewards claimed
```

- `available`: Quest is offered by an NPC; hero has not accepted it.
- `accepted`: Hero tapped "Accept". Progress begins tracking.
- `completed`: `progress >= target_count`. Rewards are claimable.
- `claimed`: Rewards distributed. Quest removed from active log.

A hero can have at most **3 active (accepted) quests** at a time. This keeps the mobile UI simple.

### Level gating

Each quest has `min_level` / `max_level`. NPCs only show quests appropriate for the hero's current level.

---

## 4. Reward Structure

Rewards are defined per quest template. MVP rewards are additive (all granted on claim).

| Reward Field | Type | Description |
|--------------|------|-------------|
| `reward_xp` | BIGINT | XP granted |
| `reward_gold` | BIGINT | Gold granted |
| `reward_potions` | INT | Healing potions granted (0-3) |

### Reward scaling guidelines

| Quest Difficulty | XP | Gold | Potions |
|------------------|-----|------|---------|
| Trivial (kill 5) | 20-50 | 10-30 | 0 |
| Normal (kill 15) | 80-150 | 50-100 | 1 |
| Hard (collect 10) | 200-400 | 100-250 | 2 |
| Journey (visit distant town) | 100-300 | 50-150 | 1 |

Reward values scale with quest `min_level`. Rough formula: `base_reward * (1 + min_level * 0.1)`.

---

## 5. Database Schema

All tables use the same conventions as the existing schema: `BIGSERIAL` PKs, `TIMESTAMPTZ` timestamps, `IF NOT EXISTS` guards.

### 5.1 `towns`

Stores the 7 fixed town definitions.

```sql
CREATE TABLE IF NOT EXISTS towns (
    id          BIGSERIAL PRIMARY KEY,
    name        TEXT NOT NULL UNIQUE,
    biome       TEXT NOT NULL,
    world_x     DOUBLE PRECISION NOT NULL,
    world_y     DOUBLE PRECISION NOT NULL,
    radius      DOUBLE PRECISION NOT NULL DEFAULT 8.0,
    level_min   INT NOT NULL DEFAULT 1,
    level_max   INT NOT NULL DEFAULT 100,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 5.2 `npcs`

Non-hostile NPCs, each tied to a town.

```sql
CREATE TABLE IF NOT EXISTS npcs (
    id          BIGSERIAL PRIMARY KEY,
    town_id     BIGINT NOT NULL REFERENCES towns(id) ON DELETE CASCADE,
    name        TEXT NOT NULL,
    type        TEXT NOT NULL CHECK (type IN ('quest_giver', 'merchant', 'healer')),
    offset_x    DOUBLE PRECISION NOT NULL DEFAULT 0,
    offset_y    DOUBLE PRECISION NOT NULL DEFAULT 0,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 5.3 `quests`

Quest template definitions. These are authored content, not player data.

```sql
CREATE TABLE IF NOT EXISTS quests (
    id                  BIGSERIAL PRIMARY KEY,
    npc_id              BIGINT NOT NULL REFERENCES npcs(id) ON DELETE CASCADE,
    title               TEXT NOT NULL,
    description         TEXT NOT NULL DEFAULT '',
    type                TEXT NOT NULL CHECK (type IN ('kill_count', 'visit_town', 'collect_item')),
    target_count        INT NOT NULL DEFAULT 1,
    target_enemy_type   TEXT,
    target_town_id      BIGINT REFERENCES towns(id),
    drop_chance         DOUBLE PRECISION NOT NULL DEFAULT 0.3,
    min_level           INT NOT NULL DEFAULT 1,
    max_level           INT NOT NULL DEFAULT 100,
    reward_xp           BIGINT NOT NULL DEFAULT 0,
    reward_gold         BIGINT NOT NULL DEFAULT 0,
    reward_potions      INT NOT NULL DEFAULT 0,
    created_at          TIMESTAMPTZ NOT NULL DEFAULT now()
);
```

### 5.4 `hero_quests`

Per-hero quest progress tracking.

```sql
CREATE TABLE IF NOT EXISTS hero_quests (
    id          BIGSERIAL PRIMARY KEY,
    hero_id     BIGINT NOT NULL REFERENCES heroes(id) ON DELETE CASCADE,
    quest_id    BIGINT NOT NULL REFERENCES quests(id) ON DELETE CASCADE,
    status      TEXT NOT NULL DEFAULT 'accepted'
                CHECK (status IN ('accepted', 'completed', 'claimed')),
    progress    INT NOT NULL DEFAULT 0,
    accepted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    completed_at TIMESTAMPTZ,
    claimed_at  TIMESTAMPTZ,
    UNIQUE (hero_id, quest_id)
);
```

### Indexes

```sql
CREATE INDEX IF NOT EXISTS idx_npcs_town ON npcs(town_id);
CREATE INDEX IF NOT EXISTS idx_quests_npc ON quests(npc_id);
CREATE INDEX IF NOT EXISTS idx_hero_quests_hero ON hero_quests(hero_id);
CREATE INDEX IF NOT EXISTS idx_hero_quests_status ON hero_quests(hero_id, status);
```

---

## 6. API Endpoints

All under `/api/v1/`. Auth via `X-Telegram-Init-Data` header (existing pattern).

### 6.1 Towns

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/towns` | List all towns (id, name, biome, world_x, world_y, radius, level range) |
| `GET` | `/towns/:id/npcs` | List NPCs in a town |

### 6.2 Quests

| Method | Path | Description |
|--------|------|-------------|
| `GET` | `/npcs/:id/quests` | List available quests from an NPC (filtered by hero level, excluding already accepted/claimed) |
| `POST` | `/quests/:id/accept` | Accept a quest (hero must be in the NPC's town, max 3 active) |
| `POST` | `/quests/:id/claim` | Claim rewards for a completed quest |
| `GET` | `/hero/quests` | List hero's active/completed quests with progress |
| `POST` | `/hero/quests/:id/abandon` | Abandon an accepted quest |

### Response shapes

**GET /towns**
```json
{
  "towns": [
    {
      "id": 1,
      "name": "Willowdale",
      "biome": "meadow",
      "worldX": 50,
      "worldY": 15,
      "radius": 8,
      "levelMin": 1,
      "levelMax": 5
    }
  ]
}
```

**GET /hero/quests**
```json
{
  "quests": [
    {
      "id": 1,
      "questId": 3,
      "title": "Slay 10 Forest Wolves",
      "description": "The wolves are terrorizing Thornwatch.",
      "type": "kill_count",
      "targetCount": 10,
      "progress": 7,
      "status": "accepted",
      "rewardXp": 100,
      "rewardGold": 50,
      "rewardPotions": 1,
      "npcName": "Guard Halric",
      "townName": "Thornwatch"
    }
  ]
}
```

**POST /quests/:id/accept**
- 200: Quest accepted, returns hero_quest record.
- 400: Already at max active quests / not in town / level mismatch.
- 409: Quest already accepted.

**POST /quests/:id/claim**
- 200: Rewards granted, returns updated hero stats.
- 400: Quest not completed yet.

---

## 7. Frontend UI Needs

### 7.1 Town markers on the map

- Render a flag/banner sprite at each town's world position, visible at all zoom levels.
- Town name label appears when the hero is within 20 tiles.
- Reuse existing `plaza` terrain for the town area; add a distinct border or glow.

### 7.2 NPC sprites

- Small colored circles or simple character sprites at the NPC's position (town center + offset).
- Quest giver: yellow `!` icon above head when they have an available quest.
- Quest giver: yellow `?` icon when the hero has a completed quest to turn in.
- Merchant: bag/potion icon. Healer: cross icon.
- Tap an NPC sprite to open the interaction popup.

### 7.3 NPC interaction popup (React overlay)

- Appears when hero is in town and player taps an NPC.
- **Quest giver popup**: Lists available quests with title, short description, rewards, and "Accept" button. Shows in-progress quests with progress bar and "Claim" button if complete.
- **Merchant popup**: List of purchasable items (potions) with gold cost and "Buy" button.
- **Healer popup**: "Heal to full" button with gold cost shown.

### 7.4 Quest log panel

- Accessible via a small scroll icon in the HUD (always visible, bottom-right area).
- Shows up to 3 active quests in a compact list.
- Each entry: quest title, progress bar (`7/10`), quest type icon.
- Tap a quest entry to expand: full description, rewards, abandon button.
- Toast notification when a quest completes ("Quest Complete: Slay 10 Forest Wolves").

### 7.5 Quest progress toast

- Lightweight toast at top of screen: "Wolf slain (7/10)" on kill_count progress.
- Only show every 1-2 increments to avoid spam (show on first kill, then every 3rd, then on completion).

---

## 8. Hero Travel Flow -- Town Integration with Auto-Walk

### Current flow
```
Hero auto-walks along road -> encounters enemies -> fights -> continues walking
```

### Updated flow with towns
```
Hero auto-walks -> enters town radius -> combat paused, NPCs visible ->
  (player can interact or do nothing) -> hero continues walking ->
  exits town radius -> combat resumes -> next enemy encounter
```

### Key behaviors

1. **Town entry**: When the game tick detects `distance(hero, town_center) <= town.radius`, the backend sets a transient `in_town` flag (not persisted, computed from position). The frontend receives the town ID via the state push.

2. **In-town state**: While in town:
   - No enemy spawns within the town radius.
   - Hero continues walking at normal speed (idle game -- no forced stops).
   - NPC sprites become tappable.
   - The quest log auto-checks `visit_town` quest completion.

3. **Town exit**: When the hero walks past the town radius, NPCs disappear from the tappable area, combat spawning resumes.

4. **Auto-interaction**: If the hero has a completable quest for a quest giver in this town, show a brief highlight/pulse on the NPC. The player must still tap to claim (keeps engagement without blocking idle flow).

5. **Quest progress tracking** (backend, per game tick):
   - On enemy kill: check active `kill_count` and `collect_item` quests. Update `hero_quests.progress`.
   - On town entry: check active `visit_town` quests. Update progress if target matches.
   - On `progress >= target_count`: set `status = 'completed'`, `completed_at = now()`. Push WebSocket event.

6. **Offline simulation**: When processing offline ticks, quest progress increments normally. Kill-based quests advance with simulated kills. Visit-town quests advance if the hero's simulated path crosses a town. Quest completions are batched and shown on reconnect.

### WebSocket events (additions to existing protocol)

| Event | Direction | Payload |
|-------|-----------|---------|
| `quest_progress` | server -> client | `{ questId, progress, targetCount }` |
| `quest_completed` | server -> client | `{ questId, title }` |
| `town_entered` | server -> client | `{ townId, townName }` |
| `town_exited` | server -> client | `{ townId }` |

---

## 9. Seed Data

The migration includes seed data for all 7 towns, ~15 NPCs, and ~20 starter quests. See `000006_quest_system.sql` for the full seed.

---

## 10. Future Considerations (Post-MVP)

These are explicitly out of scope for MVP but noted for schema forward-compatibility:

- **Repeatable quests**: Add a `is_repeatable` flag and `cooldown_hours` to `quests`. Not in MVP.
- **Quest chains**: Add `prerequisite_quest_id` to `quests`. Not in MVP.
- **Dialogue**: Add `dialogue_text` JSON array to `quests` for NPC speech bubbles. Not in MVP.
- **Merchant inventory**: Separate `merchant_inventory` table. MVP uses hardcoded potion prices.
- **Healer scaling**: MVP uses flat gold cost. Later: cost scales with level.
