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.
What you will learn
Section titled “What you will learn”- The difference between a one-time event and current state
- How
completeWhenreevaluates 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
The failure we are preventing
Section titled “The failure we are preventing”An acquisition-only design might listen for the moment the letter enters inventory:
itemAcquired: sealed_letter→ complete recover_letterThat 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.
Replace Mara’s quest dialogue
Section titled “Replace Mara’s quest dialogue”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 } ] }]Why this order works
Section titled “Why this order works”| 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.
Let Oren resolve the letter
Section titled “Let Oren resolve the letter”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.
Validate the corrected checkpoint
Section titled “Validate the corrected checkpoint”The Author validator should now confirm that:
hasItemuses the existing sealed-letter ID,completeWhenuses 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.
Playtest both routes
Section titled “Playtest both routes”Use a fresh game for each route.
Route A: quest first
Section titled “Route A: quest first”- Speak with Mara.
- Confirm
hear_maras_requestcompletes. - Find and open the courier’s satchel.
- Confirm
recover_lettercompletes. - Return to Mara and receive direction to Oren.
- Speak with Oren and confirm the quest completes.
Route B: letter first
Section titled “Route B: letter first”- Find and open the courier’s satchel before speaking with Mara.
- Confirm the letter is in inventory.
- Speak with Mara.
- Confirm the quest starts and
recover_lettercompletes from current inventory state. - 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
itemAcquiredtrigger was added.
Checkpoint 6
Section titled “Checkpoint 6”The campaign now supports both intended discovery orders:
Mara → letter → Orenletter → Mara → OrenThat is stronger evidence than “the happy path worked once.”
Continue to Chapter 7: Add Lysa and the Second Quest.