Basic Campaign Example
A complete, simple campaign demonstrating core concepts: placeholders, quest actions, linear progression, and dynamic locations. Every field below is one the app actually reads - this would work if pasted into a real campaign file.
Verified against reality: uses name (not title), levelRange as an object, episodeNumber + title on the episode, and the real <div class='read-aloud'> wrapper on every readAloud string. There is no top-level quests array with a status field - quests live in each episode's questTemplates.
Campaign Overview
The Bandit Threat
Level: 1-3
Duration: 2-3 sessions
Synopsis: Bandits are threatening the local area. The party must track them down and defeat them before they strike again.
Features Demonstrated:
- Basic placeholder usage ({nearestBurg}, {nearestDungeon})
- Quest acceptance and completion
- Linear scene progression
- Simple choices
- Scope control (local adventure)
Complete Campaign JSON
{
"id": "bandit-threat",
"name": "The Bandit Threat",
"description": "A simple adventure where heroes must stop bandits threatening the local settlement.",
"levelRange": { "min": 1, "max": 3 },
"episodes": [
{
"id": 1,
"episodeNumber": 1,
"title": "The Threat Emerges",
"description": "Bandits attack a merchant near town",
"questTemplates": [
{
"name": "Stop the Bandits",
"type": "combat",
"description": "Defeat the bandit group threatening {nearestBurg}",
"rewards": {
"gold": 100,
"xp": 300
}
}
],
"scenes": [
{
"sceneId": "ep1-scene1",
"title": "Trouble in Town",
"readAloud": "<div class='read-aloud'><p>You stand in the market square of {nearestBurg}, a small frontier settlement. The morning bustle is interrupted by shouts - a bloodied merchant stumbles through the gates.</p><p>'Bandits!' he gasps. 'They attacked my caravan just outside town!'</p></div>",
"choices": [
{
"text": "Help the merchant",
"nextScene": "ep1-scene2"
},
{
"text": "Ignore him and continue shopping",
"nextScene": "ep1-scene1-ignore"
}
]
},
{
"sceneId": "ep1-scene1-ignore",
"title": "A Missed Opportunity",
"readAloud": "<div class='read-aloud'><p>You turn away, but the town guard quickly surrounds you. 'We need all able-bodied adventurers,' the captain says firmly. 'This is a town emergency.'</p></div>",
"choices": [
{
"text": "Agree to help",
"nextScene": "ep1-scene2"
}
]
},
{
"sceneId": "ep1-scene2",
"title": "The Quest",
"readAloud": "<div class='read-aloud'><p>The town elder, Gareth, approaches you with a worried expression.</p><p>'These bandits have been raiding for weeks. If we don't stop them, {nearestBurg} won't survive the winter. Will you help us?'</p></div>",
"choices": [
{
"text": "Accept the quest",
"nextScene": "ep1-scene3",
"questActions": ["accept:Stop the Bandits"]
},
{
"text": "Decline (quest ends)",
"nextScene": "ep1-scene2-decline"
}
]
},
{
"sceneId": "ep1-scene2-decline",
"title": "Not Your Problem",
"readAloud": "<div class='read-aloud'><p>Gareth's face falls. 'I understand,' he says quietly. 'We'll have to manage on our own.'</p><p>You leave {nearestBurg} behind, but the memory of their desperate faces haunts you.</p><p><strong>Campaign Ended</strong></p></div>",
"choices": []
},
{
"sceneId": "ep1-scene3",
"title": "The Investigation",
"readAloud": "<div class='read-aloud'><p>You examine the attack site. Tracks lead north toward the hills. The bandits aren't being subtle - they want to be found.</p></div>",
"choices": [
{
"text": "Follow the tracks",
"nextScene": "ep1-scene4"
},
{
"text": "Return to {nearestBurg} to gather information",
"nextScene": "ep1-scene3-investigate"
}
]
},
{
"sceneId": "ep1-scene3-investigate",
"title": "Gathering Intel",
"readAloud": "<div class='read-aloud'><p>The townsfolk tell you the bandits have been using {nearestDungeon:type=Cave} as a hideout. Armed with this knowledge, you set out.</p></div>",
"choices": [
{
"text": "Travel to the hideout",
"nextScene": "ep1-scene4"
}
]
},
{
"sceneId": "ep1-scene4",
"title": "The Bandit Hideout",
"readAloud": "<div class='read-aloud'><p>You arrive at {nearestDungeon:type=Cave}. Smoke rises from the entrance - they're inside.</p><p>You can hear voices and the clatter of weapons. About 8 bandits, by your count: {monster:Bandit|count=8}.</p></div>",
"dmNotes": {
"enemies": {
"bandits": { "count": 8, "hp": 11, "ac": 12, "tactics": "Fight in the open, flee if reduced to half their number" }
}
},
"choices": [
{
"text": "Attack directly",
"nextScene": "ep1-scene5-combat"
},
{
"text": "Attempt to sneak in",
"nextScene": "ep1-scene5-stealth"
},
{
"text": "Try to negotiate",
"nextScene": "ep1-scene5-negotiate"
}
]
},
{
"sceneId": "ep1-scene5-combat",
"title": "The Battle",
"readAloud": "<div class='read-aloud'><p>You charge in, weapons drawn. The bandits are caught off-guard.</p><p>After a fierce battle, the last bandit falls. Their threat to {nearestBurg} is ended.</p></div>",
"choices": [
{
"text": "Return to {nearestBurg} victorious",
"nextScene": "ep1-scene6-victory"
}
]
},
{
"sceneId": "ep1-scene5-stealth",
"title": "The Ambush",
"readAloud": "<div class='read-aloud'><p>You creep through the shadows, catching the bandits unaware.</p><p>After defeating the bandits, you search the cave for stolen goods.</p></div>",
"dmNotes": {
"skillChecks": { "stealth_dc_12": "Success grants a surprise round in the combat that follows; failure proceeds to combat normally." }
},
"choices": [
{
"text": "Return to {nearestBurg} with the recovered goods",
"nextScene": "ep1-scene6-victory"
}
]
},
{
"sceneId": "ep1-scene5-negotiate",
"title": "Failed Diplomacy",
"readAloud": "<div class='read-aloud'><p>'Negotiate?' the bandit leader laughs. 'We don't negotiate with dead men!'</p><p>They attack. Roll for initiative.</p></div>",
"choices": [
{
"text": "Fight your way out",
"nextScene": "ep1-scene5-combat"
}
]
},
{
"sceneId": "ep1-scene6-victory",
"title": "Heroes' Welcome",
"readAloud": "<div class='read-aloud'><p>You return to {nearestBurg} as heroes. The townsfolk cheer your arrival.</p><p>Gareth greets you with tears in his eyes. 'You've saved us all,' he says, handing you a pouch of gold. 'This is everything we can spare - you've earned it.'</p></div>",
"choices": [
{
"text": "Accept the reward",
"nextScene": "ep1-scene7-end",
"questActions": ["complete:Stop the Bandits"]
}
]
},
{
"sceneId": "ep1-scene7-end",
"title": "A New Dawn",
"readAloud": "<div class='read-aloud'><p>With the bandits defeated, peace returns to {nearestBurg}. The townsfolk can sleep safely once more.</p><p>Your names will be remembered here for years to come.</p><p><strong>Campaign Complete!</strong></p></div>",
"choices": []
}
]
}
]
}
Key Features Explained
1. Dynamic Locations
Notice how the campaign uses placeholders instead of hardcoded names:
{nearestBurg}
{nearestDungeon:type=Cave}
This works for any party - Campaign Cookbook generates a fresh set
of named towns per party+campaign, so the same campaign JSON adapts
automatically without you hardcoding a town name.
2. Quest Lifecycle
The quest follows the standard accept → complete pattern:
- Scene 2: Player accepts quest
- Scenes 3-5: Quest is active
- Scene 6: Player completes quest
3. Linear with Branches
The main story is linear (Scene 1 → 2 → 3 → 4 → 5 → 6 → 7), but offers tactical branches:
- Investigation path (Scene 3 → 3-investigate → 4)
- Combat approaches (Direct, Stealth, Negotiate)
4. Meaningful Choices
Choices affect approach but not outcome - all paths lead to victory:
- Attack directly = standard combat
- Sneak in = combat with potential advantage
- Negotiate = combat (bandits refuse)
Customization Ideas
Add Failure Path
Create consequences for declining or failing:
{
"text": "Flee from the bandits",
"nextScene": "ep1-scene5-flee",
"questActions": ["fail:Stop the Bandits"]
}
Add More Locations
Use additional placeholders for variety:
"The bandits are also raiding {randomBurg:maxPopulation=300}."
Add Side Quests
Add another entry to the episode's questTemplates array - there's no "hidden"/"available" status field, a quest simply exists in the array and becomes active whenever a scene's questActions accepts it:
{
"name": "Rescue the Captives",
"type": "side-quest",
"description": "Free prisoners held by the bandits",
"rewards": { "gold": 50, "xp": 150 }
}
Testing Checklist
- ✓ Placeholders resolve to real names (or the expected fallback text) when loaded
- ✓ Quest acceptance triggers correctly
- ✓ Quest completion awards rewards
- ✓ All choice paths are reachable
- ✓ No dead-end scenes (unless intentional ending)
What to Try Next