Quests

quests

Quests are pre-authored story objectives that start hidden. Surface them deliberately -- a trigger firing quest-init behind a two-step discovery gate, or a story start's startingQuests; with light or no gating the engine can throw a quest at the player the moment they engage its quest giver.

In the editor

"Quests that can be used by story starts or triggers"

Editor location
Files → World → Advanced → Quests
Editor type
Edit in Studio + View JSON
Size limits

No character-length caps for this section.

Schema

json
{
  "quests": {
    "<key>": {
      "name": "string",
      "questSource": "string",
      "detailType": "basic | detailed",
      "questLocation": "string",
      "completionCondition": {
        "type": "story",
        "query": "string"
      },
      "questStatement": "string",
      "mainObjective": "string",
      "questDesignBrief": "string",
      "objectives": {
        "<key>": {
          "id": "string",
          "text": "string",
          "status": "hidden | active | completed"
        }
      },
      "activeObjectiveId": "string",
      "nextStep": {
        "text": "string",
        "source": "objective | narrative-event"
      },
      "spatialRelationship": "existingLocalArea | newLocalArea | nearbyNewLocation | distantNewLocation | existingLocationNewAreas",
      "conclusive": "boolean"
    }
  }
}

Example

json
{
  "quests": {
    "The Missing Documents": {
      "name": "The Missing Documents",
      "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",
      "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

name

The quest's display name. Unlike npcs, locations and most keyed sections, quests are exempt from key/name parity — the engine's parity check skips them, and a key that differs from name is not reported. Matching them anyway keeps quests consistent with the rest of the world and makes them easier to find.

questSource

Where the quest originates (NPC name, location, object). Preserved unchanged into the running quest.

questStatement

One sentence describing the situation that creates the quest.

Format: [Subject] [situation that creates urgency or motivation]

mainObjective

What the player must accomplish -- shown in the quest log UI. Write it as a player action in imperative form.

Format: [Verb] the [target] or [Verb] [what] from/in/at [where]

completionCondition

How the engine detects that the quest is complete. Two shapes:

  • { "type": "story", "query": "..." } -- the semantic text the trigger system matches against the story to detect quest completion. Write the query as a natural-language description of what "done" looks like.
  • { "type": "narrative-event-completed", "eventId": "..." } -- keyed to a narrative event by id.

detailed quests auto-generate a completion trigger from this field (the query becomes the trigger's semantic condition, firing quest-progress); basic quests need manual triggers. If the condition is left empty, no auto-trigger is generated.

detailType

TypeUse whenLocation handling
basicQuest location should be AI-generatedUses spatialRelationship to generate dynamically
detailedQuest has a specific predefined locationUses exact questLocation from locations

Use detailed for hand-crafted narrative quests. Use basic for procedural or emergent quests.

spatialRelationship

Required when detailType: "basic" -- defines where the AI generates the quest location.

ValueDescription
existingLocalAreaCurrent location, existing area
newLocalAreaCurrent location, new area created
nearbyNewLocationNew location generated nearby
distantNewLocationNew location generated far away
existingLocationNewAreasExisting location, new areas added

questLocation

Required when detailType: "detailed" -- the quest's specific predefined location, a key from locations. The engine uses this exact location. On basic quests the engine fills the quest's location itself at runtime.

questDesignBrief

2-4 sentence internal design guidance for the AI -- drives NPC, location, and trigger generation for this quest.

conclusive

Whether completing this quest concludes its parent story arc. Defaults to false.

objectives

Optional Record<string, object>. Every entry requires id (string), text (string) and status, where status is "hidden", "active" or "completed".

Objectives are shown to players. One with status "hidden" is left out of that list, and a listed objective reads as done when its own status is "completed" or the quest itself has completed.

The record is what activeObjectiveId and nextStep resolve against, and neither is kept without it.

activeObjectiveId

Optional string naming the objective that should be active when the quest begins. Leave it out for a quest that starts with no active authored objective.

It is matched against the keys of objectives, not the id inside each entry, and is dropped when it does not name one.

Warning: this is the record key of the objective, not the id value stored inside the entry. Where the two differ, writing the id silently drops the field and the quest begins with no active objective.

nextStep

Optional temporary next-step guidance shown to players. Both keys are required when it is present: text (string) is the player-facing instruction shown as the current next step, and source is "objective" or "narrative-event". Any other source is rejected.

"objective" ties the step to quest progress, and it is kept only when its text matches the text of one of the quest's objectives exactly. "narrative-event" marks it as temporary guidance owned by an active narrative event.

It is not fixed for the life of the quest: a trigger can replace or clear it with the quest-next-step-set and quest-next-step-clear effects (see Triggers).

name

Display name of the quest. Quests are exempt from the key/name parity the other keyed sections enforce — the engine's parity check skips them, so a key that differs from name is not reported. Building the map as { quest["name"]: quest } anyway keeps quests consistent with the rest of the world and easier to find.

completionCondition

Required. Missing it causes a deeply nested decoder 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 completes. Use this to end a quest on the resolution of a multi-turn arc rather than a single detected moment.

Note: A plain string is also accepted and wrapped into { "type": "story", "query": <string> } on load. 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:

detailTypeLocation sourceWhat the AI does
"detailed"questLocation (required) -- exact pre-built location nameQuest is pinned to that location; AI generates quest content there
"basic"spatialRelationship (required) -- spatial hint enumAI 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 decoder 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 — 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 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 decoder 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.

Quest lifecycle

Status flow

Hidden → (trigger fires quest-init, or a story start's startingQuests) → 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. The name is a label only: gate the trigger on the quest's status yourself if it should not fire before the quest is accepted.

Authoring tips

Activation coverage

A quest starts hidden. The two deliberate activation paths are a trigger with a quest-init effect and a story start's startingQuests entry.

The working method is to gate quest-init behind a two-step discovery trigger -- an arrival trigger that sets a flag, then a story-condition trigger that fires quest-init once the player has actually met the hook (see the pattern on the Triggers page) -- so the quest surfaces naturally and reliably.

Light or missing gating does not keep a quest hidden: engaging the NPC the quest originates from can surface it immediately, landing it on the player with no narrative lead-in.

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.