## Example ```json { "The Missing Documents": { "name": "The Missing Documents", "questType": "side", "questSource": "Archivist Sera Vane", "questStatement": "Side quest. Vane is a guild archivist who has identified a set of classified records being quietly prepared for destruction — documents that contradict the official history of a major land dispute. She cannot retrieve them herself without triggering a mandatory audit of her clearance. She needs an outside agent to enter the restricted archive during the next scheduled maintenance window and remove the records before they are incinerated.", "mainObjective": "Recover the classified documents from the guild archive and deliver them to Vane's contact at the docks.", "completionCondition": { "type": "story", "query": "The documents have been physically delivered to the dock contact and confirmed received." }, "detailType": "detailed", "questLocation": "The Capital", "questDesignBrief": "Vane is trusting the player with materials the guild would destroy. Frame this as a genuine trust exchange - she cannot do this herself. The documents matter; do not reduce the retrieval to a single skill check. Let the risk of exposure feel real." }, "The Final Reckoning": { "name": "The Final Reckoning", "questType": "main", "questSource": "The Resistance", "questStatement": "Main quest — final arc. The evidence gathered across the investigation is complete: financial records, witness depositions, and the incriminating vault documents. The Resistance has secured access to the tribunal hall and a sympathetic magistrate willing to hear a formal case. The player must present the full evidence cache at a scheduled tribunal session and hold the case together under cross-examination by council-appointed advocates.", "mainObjective": "Bring the full evidence cache to the tribunal hall and formally present the case against the council.", "completionCondition": { "type": "story", "query": "The evidence has been presented at the tribunal and the verdict delivered." }, "detailType": "detailed", "questLocation": "The Tribunal", "questDesignBrief": "The primary ending. The tribunal is the culmination of everything gathered across the scenario. Give it weight - let the verdict land with gravity. Do not compress the resolution." } } ``` ## Fields ### questType Free-form string. A category label for the quest. There is no closed set — any string is accepted, so a world can coin labels that fit its own quest taxonomy (`"bounty"`, `"diplomacy"`, `"exploration"`, `"crafting"`, and so on). The categories seen most often are `"acquire"`, `"rescue"`, `"combat"`, `"assassination"`, `"social"`, and `"investigate"`; reach for one of those when it fits, and only invent a label when none does. This field is **not in the codec schema** and is **not read by the engine**. It is a categorization and sorting label only and has no effect on narration, the journal, or any generation task. The editor and the validator accept it; author it for organization, not behavior — adding it changes nothing about how the quest is run. ### objectives Optional map of sub-objectives for the quest, keyed by an id. Each entry is `{ "id", "text", "status" }`: - `id` -- the objective's identifier; **must be byte-identical to its outer map key** (same pattern as quests and triggers). - `text` -- the objective description shown to the player. - `status` -- one of `hidden`, `active`, or `completed`. Author the starting state (usually `hidden` for later steps, `active` for the first), then let triggers move objectives forward. Objectives are driven by triggers: the [`quest-objective-reveal`](/mechanics/triggers#reference) effect reveals a `hidden` objective, and [`quest-objective-complete`](/mechanics/triggers#reference) marks one `completed`. A quest with no `objectives` map behaves exactly as before -- objectives are an optional layer for quests that benefit from visible sub-steps. ```json "objectives": { "reach_archive": { "id": "reach_archive", "text": "Reach the guild archive during the maintenance window.", "status": "active" }, "recover_records": { "id": "recover_records", "text": "Recover the classified records.", "status": "hidden" } } ``` ### activeObjectiveId Optional string -- the `id` of the objective the player is currently working on. It points the engine (and the journal) at which objective is the current focus. Keep it pointing at an objective whose `status` is `active`; triggers that advance objectives typically update this alongside `quest-objective-complete`. ### nextStep Optional object `{ "text", "source" }` giving the player a short "what to do next" hint for the quest. - `text` -- the hint shown to the player. - `source` -- where the hint comes from: `objective` (it reflects the active objective) or `narrative-event` (it reflects an active narrative event). You can author an initial `nextStep`, but it is normally managed at runtime by the [`quest-next-step-set`](/mechanics/triggers#reference) and `quest-next-step-clear` trigger effects as the quest progresses. A world can also set a party-wide hint (independent of any quest) with the `party-next-step-set` / `party-next-step-clear` effects. ### name Display name of the quest, and **must be byte-identical to the outer map key** — the engine matches entries by key, so the key and `name` have to be exactly equal (same case, same spaces). A mismatch is a validation error. Build the map as `{ quest["name"]: quest }`. ### completionCondition **Required.** Missing it causes a deeply nested Zod error like `quests.Name.1.0.0.completionCondition`. It is an object in one of two shapes: - `{ "type": "story", "query": "..." }` -- a story-completion check; `query` is the situation that marks the quest done, in plain language (`"The documents have been delivered to the dock contact."`). This is the common form. - `{ "type": "narrative-event-completed", "eventId": "..." }` -- the quest completes when the named [narrative event](/world/narrativeEvents) completes. Use this to end a quest on the resolution of a multi-turn arc rather than a single detected moment. > **📋 Note:** Earlier versions used a plain **string** here. That form is legacy: on load it is wrapped into `{ "type": "story", "query": }`. Author the object form directly. Detailed quests auto-generate a completion trigger from `completionCondition`. Basic quests need manual triggers. If `completionCondition` is empty, no auto-trigger is created for either type. ### detailType Determines which location field is required and how the AI handles quest location: | `detailType` | Location source | What the AI does | |---|---|---| | `"detailed"` | `questLocation` (required) -- exact pre-built location name | Quest is pinned to that location; AI generates quest content there | | `"basic"` | `spatialRelationship` (required) -- spatial hint enum | AI generates a location on the fly based on the spatial hint | **Valid `detailType` values:** Only `"basic"` and `"detailed"`. `"brief"` is **not** valid — using it causes a Zod error listing every expected field. The error message is misleading (it looks like the whole quest schema is wrong) but the root cause is always the invalid `detailType` string. ### questLocation Required when `detailType` is `"detailed"`. **Must be a location name, not a region name.** Must exactly match a key in [`locations`](/world/locations) — a specific location display name like `"Capital City Docks"`, not its parent region name like `"The Capital Region"`. Region names cause "Invalid questLocation" warnings. Always use the specific location, not its parent region. ### spatialRelationship Required when `detailType` is `"basic"`. Codec enum with five accepted values: `existingLocalArea | newLocalArea | nearbyNewLocation | distantNewLocation | existingLocationNewAreas` What each tells the narrator to construct: `existingLocalArea` - the quest stays in the current area (no travel needed); `newLocalArea` - moving to a new part of the current location that didn't previously exist (e.g. a hidden basement); `nearbyNewLocation` - travel within the same region, framed as a short trip; `distantNewLocation` - a journey to a different region or far-off part of the world; `existingLocationNewAreas` - returning to a known location and discovering entirely new sections of it. > **For `basic` quests, use `existingLocalArea`** (or make the quest `detailType: "detailed"` with a `questLocation`). The location-generating values `nearbyNewLocation` and `distantNewLocation` resolve a new location *relative to the player's current position*, which can fail when the quest is accepted — the engine aborts with "Failed to accept quest". This bites starting quests (accepted at spawn, before any movement) and arc/chained quests offered before the player has travelled. Anchor the quest at the player's area and let outward travel emerge through play; reserve named destinations for `detailType: "detailed"` + `questLocation`. ### questStatement In practice carries most of the AI guidance load for individual quests — not just "the situation that creates the quest" but the full scene context: who is involved, what the player must do, where the encounter happens, and how success is judged. A well-written `questStatement` runs 100–500 characters. For authored quest chains, many authors open `questStatement` with a category label on its own line (`Premade Questline: Arc Name`, `AI Generated Quest`) before the narrative setup paragraph — this helps the AI understand the quest's origin and treat it accordingly. > **📋 Note:** `questStatement` and the global [`storySettings.questGenerationGuidance`](/world/storySettings) work as a general-to-specific pair. The global guidance carries world-wide quest tone so any quest feels like it belongs in the scenario; `questStatement` carries the mission-specific context so the objective lands with the right weight. `questDesignBrief` adds tone and feel guidance on top of that when the quest's emotional register is non-obvious from the other fields. ### questDesignBrief Optional string — authoring notes about how the quest should feel and be run. Not player-visible. Include it for quests where the tone or pacing is non-obvious from the other fields alone. ```json "questDesignBrief": "Direct confrontation with the mastermind at their stronghold. They are willing to negotiate - this should feel like a revelation, not an automatic fight. No violence unless the player chooses it. Their account should answer questions and raise new ones." ``` ### Validation gotchas > **⚠️ Warning:** > - `completionCondition` is required - omitting it causes a deeply nested Zod error. > - The correct trigger effect format for `quest-init` is `{ "type": "quest-init", "operator": "set", "value": "Quest Name" }` - `"operator": "set"` must be present. > - `questLocation` must be a **location** name (a key in `locations`), not a region name. Region names produce "Invalid questLocation" warnings. > - `detailType` accepts only `"basic"` and `"detailed"` - `"brief"` is invalid. ## Objectives and the next-step hint Beyond the single `mainObjective`, a quest can carry a map of **objectives** -- ordered sub-steps the player works through -- and a **next-step hint** that tells them what to do right now. Both are optional; a quest with neither behaves exactly as a plain quest. ### Objectives Each objective has an `id`, player-facing `text`, and a `status` of `hidden`, `active`, or `completed`. Author the opening state (typically the first objective `active`, later ones `hidden`) and then drive them with triggers: | Effect | What it does | |---|---| | `quest-objective-reveal` | reveals a `hidden` objective to the player | | `quest-objective-complete` | marks an objective `completed` | | `quest-complete` | marks the whole quest complete | Point `activeObjectiveId` at whichever objective is the current focus. A typical flow: complete objective A with `quest-objective-complete`, reveal objective B with `quest-objective-reveal`, and update `activeObjectiveId` to B -- all in the same trigger. ### Next-step hint `nextStep` is a short `{ text, source }` prompt telling the player what to do next. Set it with `quest-next-step-set` and clear it with `quest-next-step-clear`; `source` records whether the hint reflects the active `objective` or an active `narrative-event`. There is also a party-wide hint, independent of any quest, managed with `party-next-step-set` / `party-next-step-clear`. All of these are ordinary [trigger effects](/mechanics/triggers#reference) -- objectives and next steps are authored as data on the quest, then advanced by the same trigger system that surfaces the quest in the first place. ## Quest lifecycle ### Status flow Hidden → (trigger fires `quest-init`) → Available → Accepted → **Phase 1: `goToLocation`** (move to the quest's region/location) → **Phase 2: `goToArea`** (move to the specific area within that location) → **Phase 3: `completeObjectives`** (player completes `mainObjective`) → Completed. From Available the player may also `reject` the offer (→ `rejected`) or the quest may `expire`. From Accepted the player may `abandon` (→ `abandoned`). Valid quest statuses: `hidden`, `available`, `accepted`, `completed`, `abandoned`, `rejected`, `expired`. ### Expiry conditions From `available` state, a quest expires when: - Expiry tick reached (3 ticks after offer) - Party leaves the location where the quest was offered - Quest giver dies, becomes incapacitated, or is no longer near the party Acceptance and rejection are immediate — there is no pending state between offer and decision. ### Quest chains Use a `quests-completed` trigger condition to detect completion, then fire `quest-init` for the next quest. ### acceptQuest UI prompt When a quest becomes available, the engine surfaces an accept prompt to the player as a UI element after the turn ends — accepted quests are tracked in the journal (top-left in the game). Use `quest-init` triggers with `story` conditions for quest discovery logic rather than relying on prose dialogue. ### Trigger naming convention Use the pattern `{questId}_objective` or `{questId}_objective_N` (e.g. `the_missing_documents_objective`, `the_missing_documents_objective_2`) to name objective-phase triggers consistently. Triggers named with this pattern are automatically filtered out of the active pool while the quest is unaccepted or abandoned — they will not fire unless the quest is in an accepted state. ## Authoring tips ### Coverage requirement **Every quest must have at least one trigger with a `quest-init` effect pointing to it, or it will never become available to the player.** Quests without triggers remain permanently in the `Hidden` state — they exist in the data but can never be discovered or accepted. This is the most common cause of "quests not showing up." Ensure 100% coverage: one trigger per quest at minimum. ### Quality checklist - `questStatement` — reads as a briefing: who, what, where, why, and how success is judged. One sentence to a full paragraph depending on quest complexity. For authored quest chains, consider opening with a category label (`Premade Quest`, `AI Generated Quest`) on its own line before the narrative setup. - `mainObjective` — starts with an imperative verb. The engine parses this to evaluate completion. - `completionCondition` — write what "done" looks like in plain language. Be specific. - `detailType: "detailed"` + `questLocation` for pre-built locations; `detailType: "basic"` + `spatialRelationship` for AI-generated locations. ### Discovery pattern (first quests at a location) 1. Arrival trigger (`start_[location]`) sets scene and writes a boolean flag — no `quest-init`. 2. Discovery trigger (`discover_[quest_slug]`) checks the flag + a `story` AI condition ("has the player spoken with the quest-giver?") → fires `quest-init`. 3. The quest appears in the player's journal only after they have organically encountered the hook. ### Chain pattern (multi-step storylines) 1. Quest A surfaces via the two-step discovery pattern above. 2. Quest B trigger has condition `quests-completed contains "Quest A"` — fires only after A is done. 3. Quest C trigger chains from B in the same way. This creates a natural investigation/escalation arc without the player being handed everything at once. ### Encounter quests For encounter/spawn-able enemies: create one quest per enemy faction with `questSource: "Regional Danger"`. These quests don't need complex objectives — they serve as AI context for encounter spawning. ### NPC reference warnings The editor shows "NPC X is not referenced by any story start or quest" for NPCs that have no structural linkage in the scenario. The quest schema has no built-in NPC linkage field — `questSource` is intentionally a plain string. To resolve these warnings, add every NPC to the `npcs` array of at least one quest. For enemy/encounter NPCs, create dedicated encounter quests (`questSource: "Regional Danger"`) with an `npcs` array listing the enemies. These quests give the AI context for encounter spawning and resolve the warnings entirely.