Quests Reference
Complete documentation for tabs/quests.json.
QuestDefinition Schema
type QuestDefinition = BasicQuestDefinition | DetailedQuestDefinition
interface QuestBaseDefinition {
name: string // ✅ Quest display name, must match object key
questSource: string // ✅ Where quest originates (NPC, location, etc.)
questStatement: string // ✅ One-sentence quest description
mainObjective: string // ✅ Primary goal text shown in quest log
completionCondition?: QuestCompletionCondition // ✅ How quest completion is detected (see below); omit for quests completed only by explicit effects
status?: 'hidden' | 'planned' | 'available' | 'accepted' // ⚠️ Starting status (default: hidden); see Starting Status below
questGiverNPC?: string // ❌ Stripped on save; the engine assigns givers to quests it generates or details. Name the giver in questSource and questDesignBrief
questDesignBrief?: string // ✅ Free-form design guidance for AI quest generation
conclusive?: boolean // ⚠️ Whether completing this quest concludes its parent arc (default: false)
objectives?: Record<string, QuestObjective> // ✅ Authored objective ladder, keyed by objective id
activeObjectiveId?: string // ✅ Currently highlighted objective (normally managed by trigger effects)
nextStep?: QuestNextStep // ✅ Short guidance shown on the quest UI (normally managed by trigger effects)
onCompleteEffects?: Effect[] // ✅ Applied once when the quest completes; same effect objects as triggers (see below)
}
type QuestCompletionCondition =
| { type: 'story'; query: string } // ✅ Semantic query matched against the story
| { type: 'narrative-event-completed'; eventId: string } // ✅ Completes when the referenced narrative event completes
interface QuestObjective {
id: string // ✅ Must match the objectives record key
text: string // ✅ Imperative objective text shown to the player
status: 'hidden' | 'active' | 'completed' // ✅ Typically authored 'hidden' and revealed via triggers
}
interface QuestNextStep {
text: string // ✅ Short imperative guidance
source: 'objective' | 'narrative-event' // ✅ What is driving this next step
}
interface BasicQuestDefinition extends QuestBaseDefinition {
detailType: 'basic' // ✅ AI generates location details
spatialRelationship: SpatialRelationship // ✅ Where to generate location
}
interface DetailedQuestDefinition extends QuestBaseDefinition {
detailType: 'detailed' // ✅ Uses predefined location
questLocation?: string // ⚠️ Key from locations.json; engine fills '' when omitted (locationless quest, see Quest Step Phases)
}
Legend
- ✅ Predefine-able: Can be set in config, preserved via spread
- ⚠️ Calculated default: Has fallback logic if not predefined
- ❌ Always overwritten: Set by initialization regardless of what exists in config
Completion Conditions
completionCondition is an optional object with one of two forms:
| Form | Meaning |
|---|---|
{ "type": "story", "query": "..." } | The query is a natural-language description of what “done” looks like, matched semantically against the story. Drives the auto-generated completion trigger (see below). |
{ "type": "narrative-event-completed", "eventId": "..." } | The quest completes (if accepted) when the referenced narrative event reaches completed status. eventId references the narrativeEvents world section (see the narrative-events skill). |
Legacy plain-string completion conditions are auto-converted to the story form ({ "type": "story", "query": "<the string>" }).
completionCondition is optional, and omitting it is a first-class choice for trigger-driven quests. A quest without one completes only through explicit effects: quest-progress reaching completion, or quest-complete, fired by triggers, narrative events, or another quest’s onCompleteEffects. There is no AI-judged fallback.
Starting Status
status is the quest’s starting status, authored like any other starting value (default hidden). Play moves it on from there. An authored world can begin a quest only at one of these four values; the later lifecycle statuses are reached through play:
| Value | At world start |
|---|---|
hidden | Invisible; the default |
planned | Invisible, but reserves its storyline (see Planned Quests below) |
available | The quest is on offer at turn 0 |
accepted | The quest is fully accepted at turn 0: target location revealed, quest phase set, party guidance active |
When several quests start accepted, all stay accepted and the last one becomes the active quest.
Worlds written before this field was renamed may still carry initialStatus. That key is no longer part of the schema and is not read, so move its value to status.
Planned Quests and Duplicate Avoidance
planned sits between hidden and available: the quest is invisible to players, but it reserves its storyline. The AI quest generator treats a planned quest as “do not duplicate”, and if the planned quest has an arc, no new quest is generated for that arc while it stands. Standing available offers discourage duplicate generation the same way.
To make a future quest shape and deduplicate AI generation, author it planned (via status or a quest-plan trigger effect); hidden keeps a quest purely invisible, without feeding duplicate avoidance.
A planned or hidden quest can still accumulate objective progress silently, with no player-facing quest toasts.
Quest Runtime Schema
interface Quest {
// === FROM DEFINITION (preserved via spread) ===
name: string // ✅ From definition
questSource: string // ✅ From definition
questStatement?: string // ✅ From definition
mainObjective?: string // ✅ From definition
completionCondition?: QuestCompletionCondition // ✅ From definition (legacy strings auto-converted to story form); stays absent when omitted
questGiverNPC?: string // ❌ Engine-assigned for generated quests; an authored value is stripped on save
spatialRelationship?: SpatialRelationship // ✅ From definition (basic only)
questDesignBrief?: string // ✅ From definition
conclusive?: boolean // ⚠️ From definition (default: false)
objectives?: Record<string, QuestObjective> // ✅ From definition; advanced only via trigger effects
activeObjectiveId?: string // ✅ From definition; dropped at load if it matches no objective
nextStep?: QuestNextStep // ✅ From definition; normally managed by objective/next-step trigger effects
onCompleteEffects?: Effect[] // ✅ From definition; applied once when the quest completes
// === CONDITIONALLY SET ===
questLocation: string // ⚠️ From definition if provided, else '' (locationless quest)
// === ALWAYS OVERWRITTEN ===
id: string // ❌ Always generated UUID
definitionKey?: string // ❌ Engine-assigned link back to the authored quest file
creationTick: number // ❌ Always current tick
detailType: 'basic' | 'detailed' // ❌ Always set from definition
status: QuestStatus // ⚠️ Starts from the authored status (default 'hidden'); play moves it on from there
contentOrigin: 'authored' | 'generated' // ❌ 'authored' for world-defined quests, 'generated' for engine-created quests
// === ARC FIELDS (engine-set on generated quests; stripped from authored files) ===
arcId?: string // ❌ Engine-set link to the quest's arc (generated quests only); stripped on save
arcEscalationAtCreation?: number // ❌ Engine snapshot of arc escalation at quest creation; stripped on save
// === RUNTIME STATE (set during gameplay) ===
questType?: 'acquire' | 'rescue' | 'combat' | 'assassination' | 'social' | 'investigate' // Quest category (never read by engine)
detectionTick?: number // Set when quest becomes available
acceptedTick?: number // Set when player accepts
expiryTick?: number // Set if quest can expire
completedTick?: number // Set when quest completed
abandonedTick?: number // Set when quest abandoned
rejectedTick?: number // Set when player rejects
startingArea?: string // Generated for basic quests
connectingAreaName?: string // Area connecting to quest location
questAreas?: string[] // Areas created for the quest
questStepPhase?: 'goToLocation' | 'goToArea' | 'completeObjectives'
hasVisitedLocation?: boolean // Tracks if party reached quest location
hasVisitedStartingArea?: boolean // Tracks if party reached starting area
objectiveCompleted?: boolean
offeredAtLocation?: string // Location where the quest was offered (engine-set)
questGiverNPCKey?: string // Runtime NPC key reference (engine-set)
arcQuestOrdinal?: number // Quest's position within its arc (engine-set)
}
type QuestStatus = 'hidden' | 'planned' | 'available' | 'expired' | 'accepted' | 'completed' | 'abandoned' | 'rejected'
type SpatialRelationship =
| 'existingLocalArea'
| 'newLocalArea'
| 'nearbyNewLocation'
| 'distantNewLocation'
| 'existingLocationNewAreas'
contentOrigin is a pure label shown on the quest card (“Authored Quest” vs “Generated Quest”). It gates no engine behavior and is always stamped by the engine: quests defined in the world config get 'authored', quests the engine generates during play get 'generated'.
definitionKey is stamped by the engine on runtime quests to link them back to the authored quest file entry. The quest FILE ID (the record key in tabs/quests.json) is the stable reference to author: quest-init / quest-plan / quest-accept effect values, questId fields on quest-* effects and the quest-status condition, and storyStarts.startingQuests all resolve by runtime id first, then file id, then unique display name.
Quest Lifecycle
Definition -> hidden -> planned -> available -> accepted -> completed
| \ |
| rejected |
| abandoned
|
expired
| Status | Description | How to Reach |
|---|---|---|
hidden | Quest exists but invisible to player; can accumulate objective progress silently | Default at creation |
planned | Invisible to players, but reserves its storyline against duplicate AI generation | Authored status: 'planned' or a quest-plan effect |
available | Quest can be discovered/offered | Authored status: 'available', story start, or a quest-init effect |
accepted | Player actively pursuing | Player accepts the offer, authored status: 'accepted', or a quest-accept effect |
rejected | Player declined the offer | Player rejects the offer |
completed | Objectives achieved | Completion trigger fires, quest progress reaches completion, or the referenced narrative event completes |
abandoned | Player gave up | Player abandons quest |
expired | Time limit exceeded, party left, or giver gone | See expiry conditions below |
Status Transitions via Effects
quest-planmoveshiddentoplanned, and nothing else: quests in any other status keep their status.quest-initmoveshiddenorplannedtoavailable. Quests in any other status keep their status. Worlds that relied onquest-initre-offering or resurrecting expired or completed quests must switch toquest-acceptor restructure.quest-accept(predefined, authored quests only) moveshidden,planned, oravailabletoaccepted.
Re-accepting a concluded quest (completed, abandoned, rejected, expired) is a fresh run: objectives reset to hidden and the terminal history is cleared. Progress made before first acceptance, while the quest was hidden, planned, or available, is kept.
A quest a trigger or effect chain just planned, offered, or accepted this turn suppresses the AI’s generated quest for that turn: authored quest chains win over ambient generation.
Quest Acceptance
When an available quest is offered, the player either accepts or rejects it as a single step. Acceptance and rejection are immediate (no separate “pending” state).
Expiry Conditions
An available quest with an expiry lapses to expired when any of these become true:
- The expiry tick is reached
- The party leaves the location where the quest was offered
- The quest giver dies, becomes incapacitated, or is no longer near the party
A standing offer without an expiry stays valid indefinitely and keeps discouraging duplicate AI generation.
Quest Step Phases
Once accepted, quests track progress through phases:
| Phase | Description |
|---|---|
goToLocation | Player must reach the quest’s location (any area within it) |
goToArea | Player must reach a specific area within the location |
completeObjectives | Player is at the correct location/area, working on objectives |
Phase auto-advances when the player reaches the required location or area.
Tracking fields:
hasVisitedLocation: Boolean tracking if party ever reached the quest locationhasVisitedStartingArea: Boolean tracking if party ever reached the starting area within that location
Phase initialization logic:
- Local quests (
existingLocalArea,newLocalArea): Start atgoToAreaphase (player already at location) - Remote quests with area navigation (
existingLocationNewAreas): Start atgoToLocation, then transition togoToAreaonce at location - Other remote quests (
nearbyNewLocation,distantNewLocation): Start atgoToLocation, then skip directly tocompleteObjectivesonce at location
Note: Quest phases and tracking fields are runtime-only. Do not manually set questStepPhase, hasVisitedLocation, or hasVisitedStartingArea in quest definitions - the game engine initializes these automatically based on the quest’s spatialRelationship.
An authored basic quest with a blank questLocation counts as already at the location: it skips the travel phases and advances to completeObjectives immediately.
A detailed quest without a questLocation behaves the same way: it skips travel, starts directly in objective mode (completeObjectives), and shows its mainObjective as the current step; no location is revealed on accept. spatialRelationship semantics are unchanged and apply to basic quests only.
Travel Difficulty and Movement Locks
Travel to a quest location is simple by default. The narrator treats a move as impossible only when the destination’s text explicitly establishes a lock: a boss guarding it, sealed access, a required access item, or authority clearance. Otherwise a move costs at most a skill check (when the party is restrained, in combat, or the destination is not yet established). A quest does not gate travel by itself; to make a quest location gated, state the lock in the location or area text and echo it in questDesignBrief.
Objectives and Next Steps
objectives is an authored objective ladder. Objectives advance exclusively through trigger effects — no AI decides when an objective is revealed or completed:
quest-objective-revealsets an objective toactive, makes it the quest’s active objective, and sets the quest’s next step to the objective’s text.quest-objective-completemarks the objectivecompletedand advances the next step to the nextactiveobjective (or clears it if none remains).
Revealing or completing an objective produces a player-visible status update when the quest is visible.
Typical authoring pattern: author objectives with status: 'hidden' and reveal them via triggers as the story progresses. An objective authored with status: 'active' is live from the start.
activeObjectiveId points at the currently highlighted objective. It is normally managed by the reveal/complete effects; set it yourself only to point at an objective you authored as initially active. If it matches no objective, it is dropped at load.
nextStep is short imperative guidance shown on the quest UI and injected into story and NPC prompts (“Next Step: …”). It is normally managed by the objective effects and the quest-next-step-set / quest-next-step-clear trigger effects. Next steps with source: 'narrative-event' are auto-cleared when the driving narrative event completes or stops. An authored nextStep with source: 'objective' whose text matches no objective is dropped at load.
Player-facing next-step display resolution order: quest nextStep text → current objective text → travel-phase fallback → mainObjective.
onCompleteEffects
onCompleteEffects on a quest definition is an array of effects using the exact same effect vocabulary as triggers, including the npc-* and player-* effects. Full effect semantics live in the triggers reference (see the triggers skill).
- They fire once, last in the completion sequence for that quest.
- They are party-scoped: per-player targeting via
satisfyingPlayersresolves to nobody here, so use party-wide or named targets. - Effects beyond the per-trigger cap are dropped at apply time.
- The story phase sees them, so their consequences can be narrated.
Common uses: reveal the next quest in a chain (quest-plan, quest-init, quest-accept), grant rewards (player-resource, player-experience), or start a narrative event (narrative-event-start).
Detail Types
| Type | Location Handling | Auto-Trigger |
|---|---|---|
basic | AI generates via spatialRelationship | No - needs manual triggers |
detailed | Uses exact questLocation; locationless (objective mode from the start) when omitted | Yes - from a story-type completionCondition |
Spatial Relationships
For basic quests, defines where the AI generates the quest location:
| Relationship | Description |
|---|---|
existingLocalArea | Current location, existing area |
newLocalArea | Current location, new area created |
nearbyNewLocation | New location generated nearby |
distantNewLocation | New location generated far away |
existingLocationNewAreas | Existing location, new areas added |
Auto-Generated Triggers
For any quest with a story-type completionCondition, the system auto-generates a hidden completion trigger:
{
name: `__generated_quest_progress__/${questId}`,
recurring: false,
conditions: [{
type: 'story',
query: completionCondition.query, // Used directly as the semantic trigger query
}],
effects: [{
type: 'quest-progress',
questId,
}],
}
If the query is empty or whitespace, no trigger is generated — the world creator must create triggers manually in tabs/triggers.json.
Trigger names beginning __generated_quest_progress__/ are reserved for these engine-generated quest progress triggers; author your own triggers under other names.
narrative-event-completed conditions build no trigger. Instead, the quest completes (if accepted) once the referenced narrative event completes.
Basic quests never get auto-generated triggers regardless of completionCondition.
Making Quests Available
Quests start as hidden unless their authored status says otherwise (see Starting Status above). Ways to move them along:
Via Story Start:
{
"storyStarts": {
"adventure": {
"startingQuests": ["rescue-princess", "investigate-ruins"]
}
}
}
startingQuests entries resolve by quest file id or unique name. At story start, hidden quests listed here are promoted to available; quests already accepted (for example via an authored status: 'accepted') are skipped.
Note: firstQuest is a separate freeform text field for AI quest generation - it doesn’t reference predefined quests.
Via Trigger Effect:
{
"triggers": {
"unlock-quest": {
"conditions": [{ "type": "story", "query": "Player speaks to the king" }],
"effects": [{ "type": "quest-init", "operator": "set", "value": "rescue-princess" }]
}
}
}
Trigger Effects and Conditions for Quests
Triggers can manipulate quests through these effects (full semantics in the triggers reference — see the triggers skill):
| Effect | Parameters | What it does |
|---|---|---|
quest-plan | operator: 'set', value (quest file id) | Moves a hidden quest to planned, reserving its storyline against duplicate AI generation |
quest-init | operator: 'set', value (quest file id) | Moves a hidden or planned quest to available (puts it on offer) |
quest-accept | operator: 'set', value (quest file id) | Accepts a hidden, planned, or available predefined quest outright |
quest-objective-reveal | questId, objectiveId | Sets the objective active, makes it the quest’s active objective, sets the quest’s next step to its text |
quest-objective-complete | questId, objectiveId | Marks the objective completed, advances the next step to the next active objective (or clears it) |
quest-next-step-set | questId, text, source | Sets the quest’s next step directly |
quest-next-step-clear | questId | Clears the quest’s next step |
quest-complete | questId | Silently marks the quest’s main objective done — same as quest-progress but with no player-visible status line; the quest then completes on the same turn if accepted |
party-next-step-set | text, source | Party-level guidance shown before any quest is visible |
party-next-step-clear | — | Clears party-level guidance |
The quest-status condition (questId, operator, value) compares against the quest’s status (hidden, planned, available, expired, accepted, completed, abandoned, rejected).
Quest references (quest-plan / quest-init / quest-accept values, questId fields on quest-* effects and the quest-status condition, and startingQuests entries) resolve by runtime quest id first, then quest file id, then unique display name. The quest file id is the stable reference to author.
Interaction with Narrative Events
While a narrative event is active, generated-quest creation is fully suppressed — the engine will not offer new generated quests mid-event. Authored quest triggers still fire normally.
AI Task Usage
| Task | Reads | Writes |
|---|---|---|
| Game initialization | quests (definitions) | quests (instances) |
generateProblemDetails | spatialRelationship, questDesignBrief | questLocation, questAreas, startingArea |
generateNewQuests | planned and available quests (duplicate avoidance) | Creates new Quest instances |
| Trigger effects | status | status, objectives, next-step changes |
| UI | All fields for quest log | - |
Cross-References
| Field | References |
|---|---|
questLocation | tabs/locations.json |
completionCondition.eventId | Keys in the narrativeEvents world section (see the narrative-events skill) |
startingQuests | Quest file ids or unique names in tabs/quests.json (from story-starts.json) |
firstQuest | Freeform text instruction (NOT a quest key) |
| Quest keys | Referenced by trigger effects quest-plan, quest-init, quest-accept, quest-objective-reveal, quest-objective-complete, quest-next-step-set, quest-next-step-clear, quest-complete and condition quest-status (see the triggers skill) |
onCompleteEffects[] fields | Same references as trigger effects — see the triggers reference Cross-References table |