9. Add Victory and the Ending
The Drowned Belfry and Echo Warden are playable, but Checkpoint 8 deliberately stops before their events advance Quest 2. This chapter completes that wiring without turning every dungeon entry or combat victory into campaign progress.
Start from Checkpoint 8 or download Checkpoint 9.
What you will learn
Section titled “What you will learn”- How an event producer and a state-based objective consumer work together
- How
targetIdprevents unrelated dungeon and combat events from advancing the story - Why stable, once-only triggers should write durable flags
- How to separate a boss-victory milestone from the actual campaign ending
- Why final dialogue branches must appear before broader active-quest branches
- How an ending condition can become true during dialogue without interrupting the conversation
Connect Quest 2 to durable state
Section titled “Connect Quest 2 to durable state”In Checkpoint 8, the enter_belfry and defeat_warden objectives had marker targets but no completion conditions. Add completeWhen to both objectives:
{ "id": "enter_belfry", "text": "Enter the Drowned Belfry.", "detail": "Travel north of Greyhaven and descend into the ruined belfry.", "marker": { "targetId": "tutorial_codex:drowned_belfry" }, "completeWhen": { "hasFlag": "tutorial_codex:belfry_entered" }},{ "id": "defeat_warden", "text": "Defeat the Echo Warden.", "detail": "Reach the Bell Chamber on the final level and put the drowned guardian to rest.", "marker": { "targetId": "tutorial_codex:echo_warden" }, "completeWhen": { "hasFlag": "tutorial_codex:warden_defeated" }}These conditions are consumers. They observe durable campaign state, but they do not create it.
The matching producers will be two targeted triggers:
| Event | Target | Durable state |
|---|---|---|
enterDungeon |
tutorial_codex:drowned_belfry |
tutorial_codex:belfry_entered |
combatVictory |
tutorial_codex:echo_warden |
tutorial_codex:warden_defeated |
This division is deliberate:
specific runtime event→ targeted trigger→ durable flag→ objective condition→ quest progressionThe event happens once in the moment. The flag remains available after the moment has passed and after the game is saved.
Record entry into the Drowned Belfry
Section titled “Record entry into the Drowned Belfry”Append this trigger after the Lysa recruitment trigger:
{ "id": "tutorial_codex:record_belfry_entry", "event": "enterDungeon", "targetId": "tutorial_codex:drowned_belfry", "once": true, "when": { "questActive": "tutorial_codex:silence_drowned_bell" }, "actions": [ { "setFlag": "tutorial_codex:belfry_entered" }, { "showMessage": "Cold water moves across the Sunken Steps as though disturbed by a distant toll." }, { "playSFX": "quest_progress" } ]}Why the target matters
Section titled “Why the target matters”enterDungeon is a broad event name. The player may enter many dungeons during a campaign.
The targetId narrows this trigger to:
tutorial_codex:drowned_belfryEntering another dungeon must not complete the Belfry objective.
Why the quest condition matters
Section titled “Why the quest condition matters”The when condition prevents an early visit from recording quest progress before Quest 2 begins. The campaign wants to recognize entering this dungeon while pursuing this quest, not merely discovering the location at any time.
That choice is safe here because dungeon entry can happen again. If the player visits early, they can return after the quest starts and produce a new enterDungeon event.
Resolve the Echo Warden victory
Section titled “Resolve the Echo Warden victory”Append the boss trigger:
{ "id": "tutorial_codex:resolve_echo_warden", "event": "combatVictory", "targetId": "tutorial_codex:echo_warden", "once": true, "when": { "questActive": "tutorial_codex:silence_drowned_bell" }, "actions": [ { "completeQuestObjective": [ "tutorial_codex:silence_drowned_bell", "defeat_warden" ] }, { "setFlag": "tutorial_codex:warden_defeated" }, { "openMessageBox": { "title": "The Fourth Toll", "text": "The Echo Warden collapses beneath the broken arch.\n\nFor one breath, the bell sounds from everywhere—the flooded stones, the dark water, the memories of the dead—and then the chamber falls silent.\n\nFar above, Greyhaven's windows stop trembling.", "position": "center", "blocking": true, "buttons": [ { "label": "Continue", "action": [ { "showMessage": "Return to Brother Oren so he can complete the shrine's rite." } ] } ] } }, { "playSFX": "quest_progress" } ]}Target the actual boss
Section titled “Target the actual boss”The event is combatVictory, but the trigger is not intended for every won fight.
This target:
"targetId": "tutorial_codex:echo_warden"means that defeating a skeleton, wolf, or unrelated boss cannot set the Warden flag.
Preserve immediate and durable feedback
Section titled “Preserve immediate and durable feedback”The trigger does two related jobs:
completeQuestObjectivegives immediate quest progress at the victory moment.setFlagpreserves the result for dialogue, save data, and the objective’s state condition.
The durable flag is the important downstream dependency. Brother Oren’s final branch does not depend on the combat event still being recent.
The milestone is not the ending
Section titled “The milestone is not the ending”The message box is titled The Fourth Toll, but its button is only Continue. It does not claim the campaign is complete and does not teleport the player.
The player still has one explicit responsibility:
Return to Brother OrenThis creates a clean separation:
boss defeated→ victory milestone→ return to Oren→ quest resolution→ campaign-complete flag→ ending presentationAdd Oren’s final resolution branch
Section titled “Add Oren’s final resolution branch”Place this branch near the top of Oren’s quest branches, after the already-complete branch and before the broader Quest 2 branches:
{ "when": { "all": [ { "questActive": "tutorial_codex:silence_drowned_bell" }, { "hasFlag": "tutorial_codex:warden_defeated" } ] }, "text": "The guardian is at rest. I can finish the rite now, but Greyhaven will remember what you carried back from the dark.", "actions": [ { "completeQuestObjective": [ "tutorial_codex:silence_drowned_bell", "return_to_oren" ] }, { "completeQuest": "tutorial_codex:silence_drowned_bell" }, { "setFlag": "tutorial_codex:campaign_complete" }, { "showMessage": "Brother Oren completes the shrine's rite. The Drowned Bell is silent." }, { "playSFX": "quest_progress" }, { "endDialogue": true } ]}Order the specific branch first
Section titled “Order the specific branch first”A later Oren branch matches any active Quest 2 state:
{ "when": { "questActive": "tutorial_codex:silence_drowned_bell" }}If that broad branch appears before the Warden-defeated branch, it may capture the conversation first and prevent the final resolution from running.
Use this order:
campaign already completeWarden defeated while Quest 2 is activeLysa not recruitedgeneral active Quest 2 guidanceearlier Quest 1 branchesfallbackThe more specific conditions belong before the broader conditions they overlap.
Use one final completion producer
Section titled “Use one final completion producer”Oren’s branch is the only producer of:
tutorial_codex:campaign_completeThat gives the ending one clear dependency and prevents a boss trigger, incidental dialogue, or unrelated event from completing the campaign independently.
Add post-completion dialogue
Section titled “Add post-completion dialogue”Checkpoint 9 also adds short campaign_complete branches for Mara, Oren, and Lysa.
For example, Mara’s completion branch is:
{ "when": { "hasFlag": "tutorial_codex:campaign_complete" }, "text": "The bell is silent. That is all the reward Greyhaven needed.", "actions": [ { "endDialogue": true } ]}These branches are not required to fire the ending. They keep repeated conversations coherent after completion and prevent NPCs from continuing to give obsolete quest instructions.
Add the campaign ending
Section titled “Add the campaign ending”Replace the empty ending object with:
{ "id": "tutorial_codex:when_the_bell_falls_silent", "title": "When the Bell Falls Silent", "completeWhen": { "hasFlag": "tutorial_codex:campaign_complete" }, "actions": [ { "playSong": "fanfare" }, { "openMessageBox": { "title": "When the Bell Falls Silent", "text": "Greyhaven sleeps through the night.\n\nAt dawn, Mara opens the tavern shutters without first listening for the bell. Oren seals the courier's letter inside the shrine archive, and Lysa watches the northern hills until the last mist leaves the ruined tower.\n\nNo one calls the silence a victory. In Greyhaven, silence is simply something worth keeping.\n\nVictory: you have completed The Drowned Bell. Free-roam exploration is now available, and you may continue adventuring throughout the world.", "position": "center", "blocking": true, "buttons": [ { "label": "Continue Exploring" } ] } }, { "showMessage": "The Drowned Bell is complete." } ]}The ending observes tutorial_codex:campaign_complete; it does not produce that flag itself.
The button says Continue Exploring because the game remains available after the ending. The text explicitly tells the player that the campaign is complete and free-roam play may continue.
Dialogue-triggered ending timing
Section titled “Dialogue-triggered ending timing”Oren sets the completion flag during dialogue and then ends the conversation. The runtime defers the ending presentation until the dialogue can close rather than placing a blocking ending box on top of an active conversation.
You still need to playtest that handoff. Static validation cannot prove that input focus, message ordering, and controls resume correctly.
Validate the completed graph
Section titled “Validate the completed graph”The validator should resolve this chain:
record_belfry_entry.targetId→ tutorial_codex:drowned_belfry
resolve_echo_warden.targetId→ tutorial_codex:echo_warden
enter_belfry.completeWhen→ tutorial_codex:belfry_entered
defeat_warden.completeWhen→ tutorial_codex:warden_defeated
Oren final branch→ tutorial_codex:silence_drowned_bell / return_to_oren
ending.completeWhen→ tutorial_codex:campaign_completeAlso confirm:
- every trigger ID is unique,
- both new triggers use
once: true, - the boss trigger targets the custom monster ID,
- the final Oren branch appears before the general Quest 2 branch,
- the ending uses a stable namespaced ID,
- the ending button does not invent an unsupported teleport or close action.
Validation can establish that the shapes and references are acceptable. It cannot prove that the events occur in the intended order or that the ending feels correct.
Playtest the completion flow
Section titled “Playtest the completion flow”Use a fresh game and test the normal path:
- Complete The Missing Courier.
- Recruit Lysa.
- Enter the Drowned Belfry.
- Confirm
enter_belfrycompletes once. - Defeat a normal dungeon enemy and confirm the boss trigger does not fire.
- Defeat the Echo Warden.
- Confirm The Fourth Toll appears once.
- Confirm the next instruction is to return to Oren.
- Save, reload, and return to Oren.
- Complete Oren’s final conversation.
- Confirm When the Bell Falls Silent appears after the dialogue closes.
- Select Continue Exploring and confirm normal controls resume.
- Speak with Mara, Oren, and Lysa again and confirm their completed-state dialogue.
- Save and reload after completion; confirm the ending does not repeat incorrectly.
Also test the negative targets:
- enter another dungeon, when available,
- defeat a non-boss enemy,
- repeat Oren’s dialogue after completion.
Checkpoint 9
Section titled “Checkpoint 9”The campaign now has a complete intended path from opening to ending:
Mara→ courier letter→ Oren→ Lysa→ Drowned Belfry→ Echo Warden→ Oren→ campaign endingCheckpoint 9 adds:
- durable Belfry-entry and Warden-victory state,
- targeted, once-only event producers,
- automatic completion conditions for the two event-driven objectives,
- a boss-victory milestone,
- final Oren resolution,
- post-completion NPC dialogue,
- a one-shot campaign ending with continued free-roam play.
The campaign can now validate cleanly and still contain a serious progression flaw. Chapter 10 demonstrates exactly that.
Continue to Chapter 10: Diagnose a Validator-Clean Soft Lock.