boilerplate/quest-system.ts
// ============================================================
// Quest System — Types and State Machine
// ============================================================
// ============================================================
// Enums
// ============================================================
export const QuestType = {
FETCH: "fetch",
KILL: "kill",
ESCORT: "escort",
EXPLORE: "explore",
CRAFT: "craft",
SOCIAL: "social",
PUZZLE: "puzzle",
CHAIN: "chain",
} as const;
export type QuestType = (typeof QuestType)[keyof typeof QuestType];
export const QuestState = {
AVAILABLE: "available",
ACTIVE: "active",
COMPLETED: "completed",
FAILED: "failed",
} as const;
export type QuestState = (typeof QuestState)[keyof typeof QuestState];
// ============================================================
// Objective Tree
// ============================================================
export interface QuestObjective {
id: string;
description: string;
type: QuestType;
/** Current progress toward the target */
progress: number;
/** Target value to complete this objective */
target: number;
/** Whether this objective is optional (bonus) */
optional: boolean;
/** Child objectives that must be completed first */
children: QuestObjective[];
/** Completion logic for children: all must complete, or just one */
childRequirement: "all" | "any";
}
export interface ObjectiveTree {
root: QuestObjective;
}
// ============================================================
// Quest Definition
// ============================================================
export interface QuestReward {
type: string;
id: string;
quantity: number;
}
export interface QuestPrerequisite {
type:
| "quest_completed"
| "level_min"
| "item_held"
| "flag_set"
| "reputation_min";
value: string | number;
}
export interface QuestDefinition {
id: string;
name: string;
description: string;
type: QuestType;
objectives: ObjectiveTree;
prerequisites: QuestPrerequisite[];
rewards: QuestReward[];
/** Time limit in seconds, 0 = no limit */
timeLimit: number;
/** Whether the quest can be retried after failure */
retriable: boolean;
/** Chains to another quest on completion */
nextQuestId: string | null;
}
// ============================================================
// Quest Instance (runtime state)
// ============================================================
export interface QuestInstance {
questId: string;
playerId: string;
state: QuestState;
objectives: ObjectiveTree;
startedAt: number | null;
completedAt: number | null;
failedAt: number | null;
/** Remaining time in seconds, null if no time limit */
timeRemaining: number | null;
}
// ============================================================
// State Machine — Valid Transitions
// ============================================================
type TransitionMap = Record<QuestState, QuestState[]>;
const VALID_TRANSITIONS: TransitionMap = {
[QuestState.AVAILABLE]: [QuestState.ACTIVE],
[QuestState.ACTIVE]: [QuestState.COMPLETED, QuestState.FAILED],
[QuestState.COMPLETED]: [],
[QuestState.FAILED]: [QuestState.AVAILABLE],
};
export class QuestStateMachine {
private instance: QuestInstance;
constructor(instance: QuestInstance) {
this.instance = instance;
}
getState(): QuestState {
return this.instance.state;
}
getInstance(): Readonly<QuestInstance> {
return this.instance;
}
canTransition(to: QuestState): boolean {
return VALID_TRANSITIONS[this.instance.state].includes(to);
}
transition(to: QuestState): boolean {
if (!this.canTransition(to)) {
return false;
}
this.instance.state = to;
switch (to) {
case QuestState.ACTIVE:
this.instance.startedAt = Date.now();
break;
case QuestState.COMPLETED:
this.instance.completedAt = Date.now();
break;
case QuestState.FAILED:
this.instance.failedAt = Date.now();
break;
case QuestState.AVAILABLE:
this.instance.startedAt = null;
this.instance.completedAt = null;
this.instance.failedAt = null;
break;
}
return true;
}
accept(): boolean {
return this.transition(QuestState.ACTIVE);
}
complete(): boolean {
return this.transition(QuestState.COMPLETED);
}
fail(): boolean {
return this.transition(QuestState.FAILED);
}
retry(): boolean {
return this.transition(QuestState.AVAILABLE);
}
}
// ============================================================
// Objective Evaluation
// ============================================================
export const isObjectiveComplete = (objective: QuestObjective): boolean => {
if (objective.children.length === 0) {
return objective.progress >= objective.target;
}
const childCheck =
objective.childRequirement === "all"
? objective.children.every(isObjectiveComplete)
: objective.children.some(isObjectiveComplete);
return childCheck && objective.progress >= objective.target;
};
export const isQuestComplete = (tree: ObjectiveTree): boolean => {
return isObjectiveComplete(tree.root);
};
export const getObjectiveProgress = (objective: QuestObjective): number => {
if (objective.children.length === 0) {
return Math.min(1, objective.progress / objective.target);
}
const childProgresses = objective.children.map(getObjectiveProgress);
const childAvg =
childProgresses.reduce((a, b) => a + b, 0) / childProgresses.length;
const selfProgress = Math.min(1, objective.progress / objective.target);
return (selfProgress + childAvg) / 2;
};
// ============================================================
// Factory
// ============================================================
export const createQuestInstance = (
definition: QuestDefinition,
playerId: string,
): QuestInstance => ({
questId: definition.id,
playerId,
state: QuestState.AVAILABLE,
objectives: structuredClone(definition.objectives),
startedAt: null,
completedAt: null,
failedAt: null,
timeRemaining: definition.timeLimit > 0 ? definition.timeLimit : null,
});
SKILL.md
---
name: design-quest-mission-design
description: >-
Use when designing quests, missions, objectives, quest trees, or reward structures. Requires quest-narrative-coherence check before any quest creation. Triggers: quest, mission, objectives, quest tree, rewards.
---
# Quest & Mission Design
## Purpose
Quest types, objective trees, reward structures, and mission flow design for any game genre. Enforces narrative coherence through mandatory lore checks.
## When to Use
Trigger: quest design, mission design, objectives, quest types, quest rewards, quest tree, mission flow, side quests, main quest, quest chain, objective design
## Prerequisites
- `quest-narrative-coherence` — **MANDATORY**. Read and follow the 5-step coherence check before creating ANY quest.
- `game-design-fundamentals` — core loop and reward system knowledge
- `worldbuilding` — world context for quest setting
## Core Principles
> Jonathan Blow: "Every puzzle should feel like a genuine insight, not busywork."
> Sid Meier: "A game is a series of interesting decisions."
> Hidetaka Miyazaki: "Quests should reveal the world, not just provide objectives."
1. **Every quest must pass the coherence check** — no orphan quests, no lore contradictions (see `quest-narrative-coherence`)
2. **Objectives must present meaningful choices** — not just "go here, collect that" (Sid Meier)
3. **Quest rewards must match the game economy** — cross-reference `game-economy-design` for balance
4. **Multi-path solutions** — at least 2 ways to complete any significant quest (Jonathan Blow)
5. **Quests reveal world** — every quest should teach the player something about the world (Miyazaki)
6. **Pacing matches the game** — quest length and density fit the session cadence from `game-design-fundamentals`
7. **No fetch quests without narrative purpose** — if a quest is mechanically simple, it must be narratively rich
## Step-by-Step Instructions
### 1. Run Coherence Check
Follow the 5-step check from `quest-narrative-coherence`. Load lore, check registry, validate, reference existing content, register.
### 2. Define Quest Type
Choose from the quest type taxonomy (see below).
### 3. Design Objective Tree
Map out the objectives, branching paths, and completion conditions.
### 4. Balance Rewards
Cross-reference `game-economy-design` for appropriate reward scale.
### 5. Write Quest Brief
Use the quest template in `templates/`.
### 6. Register Quest
Add to quest-registry.md per the coherence clause.
## Quest Type Taxonomy
| Type | Description | Example (abstract) |
|------|-------------|-------------------|
| Main Story | Advances primary narrative arc | "Uncover the truth about [event]" |
| Side Quest | Optional, enriches world | "Help [NPC] with [problem]" |
| Chain Quest | Multi-step, each unlocks next | "Phase 1 → Phase 2 → Phase 3" |
| Branching | Player choice determines outcome | "Side with [A] or [B]" |
| Repeatable | Can be done multiple times | "Daily challenge: [task]" |
| Discovery | Triggered by exploration | "Find [hidden thing] to unlock" |
| Social | Requires other players | "Complete [task] with a group" |
| Timed | Must complete within deadline | "Finish before [timer] expires" |
| Collection | Gather a set of items/objects | "Find all [N] pieces of [set]" |
| Escort/Protect | Keep something safe | "Protect [target] during [event]" |
## Objective Tree Structure
```
Quest: [Name]
├── Objective 1: [Required]
│ ├── Sub-objective 1a: [Required]
│ └── Sub-objective 1b: [Optional bonus]
├── Objective 2: [Choice A OR Choice B]
│ ├── Choice A: [Path with consequence X]
│ └── Choice B: [Path with consequence Y]
└── Objective 3: [Final — depends on choice above]
├── Outcome A: [If chose A above]
└── Outcome B: [If chose B above]
```
## Cross-References
- `quest-narrative-coherence` — MANDATORY prerequisite
- `worldbuilding` — world context for quest setting
- `game-economy-design` — reward balancing
- `game-design-fundamentals` — core loop integration
- `character-design-narrative` — NPC involvement
- `postgres-game-schema` — quest data persistence
## Pitfalls & Anti-Patterns
- **"Go collect 10 things"** — collection quests without narrative justification
- **"Orphan quest"** — quest that connects to nothing in the world (caught by coherence check)
- **"Reward inflation"** — quest gives too much, breaking economy
- **"One true path"** — quest with only one solution removes player agency
- **"Lore dump quest"** — NPC talks for 5 minutes, player does nothing
- **"Invisible prerequisite"** — quest requires something the player can't know about
## Designer Philosophy
**Jonathan Blow** (Braid, The Witness): Every quest objective should feel like a genuine insight. If the player is just following a marker, the quest has failed. The best quests make players think, experiment, and discover.
**Sid Meier** (Civilization): Quests are decisions. "Go kill X" is not a decision. "Choose between helping faction A (gaining their trust but angering B) or helping faction B" IS a decision.
**Hidetaka Miyazaki** (Dark Souls, Elden Ring): The best quests don't announce themselves. They emerge from exploration, environmental clues, and NPC conversations. The player should feel like they discovered the quest, not that it was assigned.
## Sources
- "Designing Quests That Don't Suck" — GDC 2018
- "The Art of Game Design" — Jesse Schell, Chapter on Stories
- "Narrative Design for Indie Games" — GDC 2020
- "Quest Design in Open World Games" — GDC 2019
templates/quest-template.md
# Quest Design: [Quest Name]
## Coherence Check
- [ ] Step 1: World state loaded (read world-lore.md)
- [ ] Step 2: Quest registry checked (read quest-registry.md)
- [ ] Step 3: Validated against existing content
- [ ] Step 4: References existing lore element: ___________
- [ ] Step 5: Registered in quest-registry.md
## Overview
- **Quest ID**: [Unique identifier]
- **Name**: [Display name]
- **Type**: [Main Story / Side Quest / Chain / Branching / Repeatable / Discovery / Social / Timed]
- **Arc**: [Which story arc this belongs to]
- **Difficulty**: [1-10]
- **Estimated Duration**: [Time to complete]
- **Repeatable**: [Yes / No]
- **Prerequisites**: [Other quests, level requirements, faction standing]
## Story Context
### Why This Quest Exists
[What narrative purpose does this quest serve? What does it reveal about the world?]
### Lore Connection
[Which existing lore element does this quest reference or expand?]
### Narrative Thread
[Which ongoing story arc does this advance?]
## NPCs Involved
| NPC | Role in Quest | Faction | Current Status |
|-----|-------------|---------|---------------|
| [Name] | [Quest giver / Target / Ally / Antagonist] | [Faction] | [Alive, available] |
## Objectives
### Objective Tree
```
[Quest Name]
├── Obj 1: [Description] — [Required/Optional]
├── Obj 2: [Description] — [Choice A / Choice B]
│ ├── A: [Path and consequence]
│ └── B: [Path and consequence]
└── Obj 3: [Final objective]
```
### Detailed Objectives
| # | Objective | Type | Required | Success Condition | Fail Condition |
|---|-----------|------|----------|------------------|---------------|
| 1 | [Description] | [Go to / Interact / Defeat / Collect / Choose / Protect] | [Yes/No] | [What counts as success] | [What counts as failure, if any] |
## Branching Paths (if applicable)
| Choice | Consequence | Faction Impact | Reward Difference |
|--------|------------|---------------|-------------------|
| [Choice A] | [What happens] | [Who likes/dislikes this] | [Reward variation] |
| [Choice B] | [What happens] | [Who likes/dislikes this] | [Reward variation] |
## Rewards
| Reward | Type | Amount | Condition |
|--------|------|--------|-----------|
| [Reward] | [Currency / Item / XP / Unlock / Reputation] | [Amount] | [Completion / Bonus / Choice-dependent] |
### Economy Check
- [ ] Reward amount reviewed against `game-economy-design` guidelines
- [ ] No reward inflation (compared to similar-level quests in registry)
- [ ] Sink included if reward is large (cost to start quest, material requirements)
## Locations
| Location | Purpose in Quest | Exists in World? |
|----------|-----------------|-----------------|
| [Location] | [Where objective happens] | [Yes — existing / No — needs creation] |
## Dialogue Summary
| Beat | Speaker | Content Summary | Player Choices |
|------|---------|----------------|---------------|
| [Intro] | [NPC] | [What they say] | [Accept / Decline / Ask more] |
| [Mid] | [NPC] | [Update/twist] | [Choice A / Choice B] |
| [End] | [NPC] | [Resolution] | [None — cutscene] |
## Quest Registry Entry
```
ID: [quest_id]
Name: [Quest Name]
Arc: [Arc name]
NPCs: [NPC1, NPC2]
Locations: [Loc1, Loc2]
Factions: [Affected factions]
Rewards: [Summary]
Prerequisites: [quest_ids or conditions]
Status: [planned / active / completed]
```