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

"Durable goals that can be started by story starts, triggers, or completion effects."

Editor location
Files → World → Advanced → Quests
Editor type
Edit in Studio + View JSON
In the world file
parties[""].quests
Size limits

No character-length caps for this section.

Schema

json
{
  "parties": {
    "": {
      "quests": {
        "<key>": {
          "name": "string",
          "questSource": "string",
          "questLocation": "string",
          "detailType": "basic | detailed",
          "status": "hidden | planned | available | expired | accepted | completed | abandoned | rejected",
          "completionCondition": {
            "type": "story",
            "query": "string"
          },
          "onCompleteEffects": [
            {
              "type": "story",
              "instruction": "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
{
  "parties": {
    "": {
      "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.",
          "onCompleteEffects": [
            {
              "type": "quest-accept",
              "operator": "set",
              "value": "The Final Reckoning"
            },
            {
              "type": "player-resource",
              "target": "satisfyingPlayers",
              "resource": "gold",
              "operator": "add",
              "value": 50
            }
          ]
        },
        "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 most keyed sections, quests do not have to match their key to name; matching them anyway keeps quests easy to find. Quest references (quest-init / quest-plan / quest-accept values, questId fields, startingQuests) resolve by quest key first, then by unique display name, so the key is the stable reference to author.

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

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

  • { "type": "story", "query": "..." } -- the semantic text matched against the story to detect completion. Write the query as a natural-language description of what "done" looks like.
  • { "type": "narrative-event-completed", "eventId": "..." } -- the quest completes, if accepted, when that narrative event completes.

detailed quests auto-generate a completion trigger from a story condition (the query becomes the trigger's semantic condition, firing quest-progress); basic quests never do and need manual triggers. An empty query generates no trigger. Without a completionCondition, the quest completes only through explicit effects (quest-progress, quest-complete) from triggers, narrative events, or another quest's onCompleteEffects.

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.

status

Required string naming the quest's position in the status flow: hidden, planned, available, accepted, completed, abandoned, rejected or expired.

An authored quest carries the status it enters play in. hidden is the usual choice, since quest-plan and quest-init move it on from there.

A quest entry without the field is rejected.

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

The quest's specific predefined location, a key from locations, used on detailed quests; the engine uses this exact location. A detailed quest with a blank questLocation is locationless: it skips the travel phases and starts straight in objective mode. On basic quests the engine fills the quest's location itself at runtime.

questDesignBrief

Internal design guidance for the AI: how the quest should feel and be run. It is read when the engine generates the quest's location details, and NPCs and locations the quest spawns carry it into their own detail generation. Not player-visible.

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).

onCompleteEffects

Trigger effects that fire once, the moment this quest completes. They take the same shape as a trigger's effects, so anything a trigger can do the quest can do on completion — open the next quest, start a narrative event, reveal an objective, grant a resource. This is the direct way to chain quests: the quest that finishes names its successor, with no separate trigger watching for it.

They fire last in the completion sequence, and the story phase sees them, so their consequences can be narrated. They are party-scoped: target: "satisfyingPlayers" resolves to nobody here. Effects beyond the per-trigger cap are dropped.

name

The quest's display name. Unlike most keyed sections, quests do not have to match their key to name; matching them anyway keeps quests easy to find. Quest references (quest-init / quest-plan / quest-accept values, questId fields, startingQuests) resolve by quest key first, then by unique display name, so the key is the stable reference to author.

completionCondition

Optional, and omitting it is a deliberate choice for trigger-driven quests. When present 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 hidden completion trigger named __generated_quest_progress__/<quest> from a story condition; don't give your own triggers names starting with that prefix. Basic quests and narrative-event-completed conditions build no trigger.

detailType

Determines which location field is required and how the AI handles quest location:

detailTypeLocation sourceWhat the AI does
"detailed"questLocation -- exact pre-built location key; blank for a locationless questQuest 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

Used when detailType is "detailed". Must be a location, not a region. Must match a key in locations — a specific location 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.

questStatement

The situation that creates the quest. A strong one reads as a briefing: who is involved, what the player must do, where it happens, and how success is judged.

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:

  • 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 key, not a region. Region names produce "Invalid questLocation" warnings.
  • detailType accepts only "basic" and "detailed" - "brief" is invalid.

Quest lifecycle

Status flow

Hidden → (optionally quest-plan) → Planned → (quest-init) → Available → Accepted → Completed. A story start's startingQuests makes hidden quests available at game start.

planned is invisible to players but reserves the storyline: the AI quest generator won't generate a duplicate of it. From Available the player may also reject the offer (→ rejected) or the offer may expire (→ expired). From Accepted the player may abandon it (→ abandoned). A hidden or planned quest can still accumulate objective progress silently.

Valid quest statuses: hidden, planned, available, accepted, completed, abandoned, rejected, expired.

Step phases

Once accepted, a quest moves through goToLocation (reach the quest's location), goToArea (reach a specific area in it), and completeObjectives, advancing automatically as the party arrives. Where it starts follows spatialRelationship: existingLocalArea and newLocalArea start at goToArea; existingLocationNewAreas starts at goToLocation and then goToArea; nearbyNewLocation and distantNewLocation start at goToLocation and skip straight to completeObjectives on arrival. A quest with a blank questLocation starts in completeObjectives.

Expiry conditions

From available state, a quest expires when:

  • Its expiry tick is reached, if it has one
  • Party leaves the location where the quest was offered
  • Quest giver dies, becomes incapacitated, or is no longer near the party

A standing offer without an expiry stays valid indefinitely. Acceptance and rejection are immediate; there is no pending state between offer and decision.

Three ways to move a quest forward

Three trigger effects act on a quest by key, and they differ in how visible the result is:

EffectWhat it does
quest-planMoves a hidden quest to planned: reserved, not shown. Use it to claim a storyline early so the quest generator does not invent something that overlaps it.
quest-initMoves a hidden or planned quest to available, which is what surfaces the accept prompt. It doesn't re-offer a quest in any other status.
quest-acceptMoves a hidden, planned, or available authored quest straight to accepted. Re-accepting a concluded quest starts a fresh run with its objectives reset.

More than one quest can be accepted at a time, so accepting a new one does not close out the others. A quest planned, offered, or accepted by an effect this turn suppresses the AI's generated quest for that turn.

Quest chains

Give the finishing quest an onCompleteEffects entry naming its successor — quest-accept to hand it straight over, quest-init to offer it. Nothing has to watch for the completion.

A quests-completed trigger condition still works when the follow-up depends on more than one quest being done, or on conditions the finishing quest cannot see.

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.
  • mainObjective — starts with an imperative verb; it is the step players see when nothing more specific applies.
  • 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 A's onCompleteEffects offers or accepts Quest B, or a trigger with quests-completed contains "Quest A" does when B depends on more than A.
  3. Quest C chains from B in the same way.

This creates a natural investigation/escalation arc without the player being handed everything at once.