Skip to content

6. Make Progression Out-of-Order Safe

Checkpoint 4 works in the expected order: speak with Mara, find the satchel, then visit Oren. A campaign should also survive the player doing the sensible but unexpected thing—finding the satchel before accepting the quest.

Start from your corrected Checkpoint 4 file or download Checkpoint 6.

  • The difference between a one-time event and current state
  • How completeWhen reevaluates an active objective
  • How to order specific dialogue branches before general ones
  • How to support both quest-first and letter-first play
  • Why validator-clean data still needs route-based playtesting

An acquisition-only design might listen for the moment the letter enters inventory:

itemAcquired: sealed_letter
→ complete recover_letter

That works only when the quest is already active at that exact moment. If the player finds the letter first and starts the quest later, the acquisition event has already passed.

The durable question is not:

Did the item-acquired event happen while this quest was active?

It is:

Does the player currently have the sealed letter?

That is what hasItem expresses.

Make the objective depend on current state

Section titled “Make the objective depend on current state”

Replace the recover_letter objective with:

{
"id": "recover_letter",
"text": "Recover the courier's sealed letter.",
"detail": "Search near Greyhaven for the courier's satchel. The objective recognizes the letter even if you found it before accepting the quest.",
"marker": {
"targetId": "tutorial_codex:courier_satchel"
},
"completeWhen": {
"hasItem": "tutorial_codex:sealed_letter"
}
}

When The Missing Courier is active, the runtime reevaluates this condition after story-changing action batches and story events. If the letter is already in inventory when Mara starts the quest, recover_letter can complete from current state.

No itemAcquired trigger is required.

The order of these branches matters. Put the most specific conditions first.

"quest": [
{
"when": {
"all": [
{
"questActive": "tutorial_codex:missing_courier"
},
{
"hasItem": "tutorial_codex:sealed_letter"
}
]
},
"text": "That is the courier's letter. The seal belongs to the neighboring shrine. Take it to Brother Oren.",
"actions": [
{
"showMessage": "Mara directs you to Brother Oren near Greyhaven's town center."
},
{
"endDialogue": true
}
]
},
{
"when": {
"questActive": "tutorial_codex:missing_courier"
},
"text": "The courier should have come from the north road. Search near town and look for a satchel or shrine seal.",
"actions": [
{
"endDialogue": true
}
]
},
{
"when": {
"all": [
{
"not": {
"questActive": "tutorial_codex:missing_courier"
}
},
{
"not": {
"questCompleted": "tutorial_codex:missing_courier"
}
},
{
"hasItem": "tutorial_codex:sealed_letter"
}
]
},
"text": "You already found the courier's letter? Then the road was kinder to you than it was to them. Take it to Oren.",
"actions": [
{
"startQuest": "tutorial_codex:missing_courier"
},
{
"completeQuestObjective": [
"tutorial_codex:missing_courier",
"hear_maras_request"
]
},
{
"showMessage": "The Missing Courier begins. The recovered letter already satisfies its search objective."
},
{
"playSFX": "quest_progress"
},
{
"endDialogue": true
}
]
},
{
"when": {
"all": [
{
"not": {
"questActive": "tutorial_codex:missing_courier"
}
},
{
"not": {
"questCompleted": "tutorial_codex:missing_courier"
}
}
]
},
"text": "A courier from the eastern shrine never reached Greyhaven. Search near the north road and bring back whatever they carried.",
"actions": [
{
"startQuest": "tutorial_codex:missing_courier"
},
{
"completeQuestObjective": [
"tutorial_codex:missing_courier",
"hear_maras_request"
]
},
{
"showMessage": "The Missing Courier begins. Search near Greyhaven for the courier's satchel."
},
{
"playSFX": "quest_progress"
},
{
"endDialogue": true
}
]
},
{
"text": "The north road is quiet now, but quiet and safe are not the same thing.",
"actions": [
{
"endDialogue": true
}
]
}
]
Branch State it handles
Active quest + letter owned Send the player to Oren
Active quest Remind the player to find the satchel
Quest not started + letter owned Start the quest without losing early progress
Quest not started Start the ordinary route
Unconditional fallback Keep dialogue reachable after completion or other later states

If the general active-quest branch came first, it would hide the more useful active-plus-letter response. Ordered dialogue is first-match logic.

Replace Oren’s quest array with:

"quest": [
{
"when": {
"all": [
{
"questActive": "tutorial_codex:missing_courier"
},
{
"hasItem": "tutorial_codex:sealed_letter"
}
]
},
"text": "This is a funerary seal. The warning says the old guardian has awakened beneath the ruined belfry and must be put to rest before the fourth night.",
"actions": [
{
"setFlag": "tutorial_codex:letter_read"
},
{
"completeQuestObjective": [
"tutorial_codex:missing_courier",
"show_letter_to_oren"
]
},
{
"completeQuest": "tutorial_codex:missing_courier"
},
{
"removeItem": "tutorial_codex:sealed_letter"
},
{
"showMessage": "The Missing Courier is complete. Brother Oren has read the warning."
},
{
"playSFX": "quest_progress"
},
{
"endDialogue": true
}
]
},
{
"when": {
"questActive": "tutorial_codex:missing_courier"
},
"text": "Without the courier's letter, I can only tell you this: the bell beneath the belfry was never meant to summon the living.",
"actions": [
{
"endDialogue": true
}
]
},
{
"text": "The shrine has old records of the belfry, but no warning recent enough to explain tonight's toll.",
"actions": [
{
"endDialogue": true
}
]
}
]

Oren completes the final objective, completes Quest 1, records the durable letter_read flag, and removes the delivered quest item.

Chapter 7 will extend this successful branch by starting the second quest. Do not invent that quest reference before the quest exists.

The Author validator should now confirm that:

  • hasItem uses the existing sealed-letter ID,
  • completeWhen uses a supported condition,
  • every objective action names a declared objective,
  • Mara and Oren reference the existing quest,
  • the letter removed by Oren is a declared item.

It still cannot prove that both routes work. That requires playtesting.

Use a fresh game for each route.

  1. Speak with Mara.
  2. Confirm hear_maras_request completes.
  3. Find and open the courier’s satchel.
  4. Confirm recover_letter completes.
  5. Return to Mara and receive direction to Oren.
  6. Speak with Oren and confirm the quest completes.
  1. Find and open the courier’s satchel before speaking with Mara.
  2. Confirm the letter is in inventory.
  3. Speak with Mara.
  4. Confirm the quest starts and recover_letter completes from current inventory state.
  5. Speak with Oren and confirm the quest completes.

Also check:

  • speaking with Oren before owning the letter does not advance the quest,
  • repeated conversations do not restart the quest,
  • the letter is removed only after Oren reads it,
  • no itemAcquired trigger was added.

The campaign now supports both intended discovery orders:

Mara → letter → Oren
letter → Mara → Oren

That is stronger evidence than “the happy path worked once.”

Continue to Chapter 7: Add Lysa and the Second Quest.