Examples
plotknot ships with example stories in the examples/ directory. Each demonstrates specific features. All examples are used in the test suite — they compile, load, and run through the runtime.
hello.plotknot
Demonstrates: sections, choices, conditional choices, variables, call, if/else, goto.
A simple forest branching story — the "hello world" of plotknot:
# The Forest
set courage = 5
You stand at the edge of a dark forest. The path ahead splits in two.
choice:
- Take the left path -> left_path
- Take the right path -> right_path
- Turn back -> turn_back [if courage < 3]
## Left Path
You follow the mossy trail deeper into the woods.
call check_weather() -> weather
if weather == "rain":
Rain patters on the canopy above.
else:
Sunlight filters through the leaves.
-> ending
## Right Path
The right path leads to a sunlit clearing. A deer watches you pass.
-> ending
## Turn Back
Courage fails you today. The forest can wait.
-> ending
## Ending
Your journey through the forest ends here.
Key concepts:
setinitializes a variable- Conditional choice
[if courage < 3]only appears when the condition passes callinvokes a game function and stores the resultif/elseshows different text based on runtime state->navigates between sections
characters.plotknot
Demonstrates: character metadata, dialogue attribution.
A tavern scene with named speakers:
character: Mara
character: Aldric
character: Narrator
# The Tavern
Narrator: The tavern is warm and loud. Mara sits alone.
Mara: You look lost, traveler.
Aldric: Just passing through.
Mara: Nobody just passes through Thornwall.
choice:
- Ask about the road -> road_info
- Ask about the town -> town_info
Key concepts:
character:declarations register speakersName:prefix attributes dialogue lines- The runtime delivers
speakerfields for UI binding
variation.plotknot
Demonstrates: variation blocks, cycle and once modes.
A crossroads that changes description on revisit:
# The Crossroads
variation: cycle
You arrive at a weathered crossroads.
The crossroads again. The signpost's paint is peeling.
You know this crossroads by heart now.
choice:
- Take the forest path -> forest
- Take the river path -> river
- Rest here -> rest
## Rest
variation: once
You sit on the milestone and catch your breath.
-> the_crossroads
Key concepts:
variation: cyclerotates through text lines on each visitvariation: onceshows text only the first time- The runtime tracks visit counts per section
conditions.plotknot
Demonstrates: built-in functions, visited tracking, resource management.
A dungeon crawl with state-dependent content:
# Dungeon Entrance
set torches = 3
set depth = 0
The dungeon mouth yawns before you.
choice:
- Descend -> descend
- Turn back -> turn_back
## Descend
set depth = depth + 1
if torches > 0:
set torches = torches - 1
Your torch flickers, casting long shadows.
else:
Darkness presses close.
if visited("descend"):
You recognize this stretch of corridor.
else:
The corridor is new. Every shadow could hide danger.
choice:
- Go deeper -> descend
- Search for treasure -> treasure [if depth >= 3]
- Retreat to surface -> surface
Key concepts:
visited("section")checks if a section was previously entered- Variables track resources (torches, depth)
- Conditional choices gate content behind progress
functions.plotknot
Demonstrates: custom functions, built-in math functions.
An alchemist shop with calculations:
function: calculate_dosage(2)
# Alchemist Shop
set gold = 50
set reputation = 10
set herb_count = 5
set potency = 3
call calculate_dosage(herb_count, potency) -> dose
if dose > 10:
"A potent mix," the alchemist mutters.
else:
"Mild stuff. Good for beginners."
choice:
- Buy healing potion (20 gold) -> buy_potion [if gold >= 20]
- Ask about rare ingredients -> rare_info [if reputation >= 15]
- Leave -> leave
## Buy Potion
set gold = gold - 20
set health = min(health + 30, 100)
The potion glows faintly blue.
-> alchemist_shop
Key concepts:
function:declares a custom function signaturecallinvokes it and stores the result- Built-in
min()clamps values - Choices gate on variable state
interpolation.plotknot
Demonstrates: text interpolation, inline tags, character properties, dismissible choices, nested choice content, stop.
A cafe scene with dynamic dialogue and styling cues:
declare gold = 50
declare visited_cafe = false
# The Cafe
set barista.friendship = 0
<ambient> The cafe hums with quiet conversation. Steam curls from the espresso machine.
cycle
You push through the door for the first time. A bell chimes.
--
The bell chimes again. The barista looks up.
--
You walk in like you own the place.
$barista polishes a glass behind the counter.
choice:
-- Ask about the special blend -> special
set barista.friendship = barista.friendship + 1
- Order a coffee -> order
- Leave -> leave
## Special
<friendly> "The special? You've got good taste," says $barista.
set gold = gold - 8
You sip something extraordinary. Worth every coin.
-> the_cafe
## Order
A plain coffee. Reliable. You have $gold gold left.
choice:
- Another round -> order [if gold >= 3]
set gold = gold - 3
- Leave -> leave
## Leave
if gold <= 0:
<tense> Your pockets are empty. Time to go.
stop
You step back into the street. Maybe tomorrow.
stop
Key concepts:
$varand${expr}insert live values into narrative text<tag>and<tag prop=val>attach styling metadata for the game UIcyclevariation changes the entrance text on each visit--dismissible choices disappear after being selected- Indented statements under choices run before navigation
stopends the story explicitlydeclareseeds initial variable values
variation_blocks.plotknot
Demonstrates: all five variation modes (sequence, cycle, once, pick, shuffle).
A guard post and market square showing each mode:
# Guard Post
sequence
The guard eyes you suspiciously. "First time in town?"
--
The guard nods. "Back again?"
--
The guard waves you through without a word.
-> market
## Market
shuffle
A merchant hawks fresh bread.
--
Children chase a stray dog between the stalls.
--
A musician plays a lilting tune by the fountain.
--
Two old women argue about the price of turnips.
choice:
- Browse the stalls -> browse
- Visit the fountain -> fountain
## Browse
pick
You find a dusty old map.
--
A vendor offers you a free sample of cheese.
--
Someone's dropped a shiny button.
-> market
## Fountain
once
The fountain burbles peacefully. You toss in a coin.
--
The fountain again. Your coin glints at the bottom.
cycle
Pigeons coo on the fountain's rim.
--
A child splashes in the shallows.
--
An old man fills his waterskin.
choice:
- Return to the market -> market
- Leave town -> leave
## Leave
You pass the guard one last time.
-> guard_post
Key concepts:
sequenceplays items in order and sticks on the lastshuffleplays all items in random order, then reshufflespickchooses a random item each time (items can repeat)onceplays items in order, then produces nothingcycleloops through items indefinitely- Items are separated by
--and can contain multiple lines
imports.plotknot
Demonstrates: multi-file imports, namespaced section IDs.
A story split across files. The main file imports two modules:
import characters
import "scenes/tavern.plotknot"
# Village Square
declare reputation = 0
The village square bustles with morning activity.
choice:
- Visit the tavern -> scenes.tavern
- Talk to the elder -> characters.elder
- Leave the village -> leave
## Leave
You wave goodbye. Your reputation here is $reputation.
stop
The imported characters.plotknot file:
# Elder
The elder sits on a weathered bench, feeding pigeons.
set elder.wisdom = 10
if elder.wisdom > 5:
"Sit," the elder says. "Let me tell you about this place."
-> village_square
The imported scenes/tavern.plotknot file:
# Tavern
The tavern is warm and loud. A fire crackles in the hearth.
choice:
- Order a drink -> drink
- Return to the square -> village_square
## Drink
set reputation = reputation + 1
The ale is good. The company is better.
-> tavern
Key concepts:
import nameresolves toname.plotknotin the same directoryimport "path"uses a literal file path- Imported sections are namespaced:
characters.elder,scenes.tavern - Namespaced IDs are used in goto targets and choice destinations
- Variables and character properties are shared across all imported files
complete.plotknot
Demonstrates: all features combined in a substantial story.
A sci-fi narrative ("The Awakening") with 15 sections exercising:
- Hub-and-spoke structure
- Multiple conditional choices
- Variable tracking (alert level, trust, power)
- Game function calls
- if/else branching
- Multiple endings based on accumulated state
This is the best reference for how all features compose in a real story. Read it in examples/complete.plotknot.
Running examples
Compile any example:
./plotknot compile examples/hello.plotknot --output /tmp/
Run through the Lua runtime:
local plotknot = require("plotknot")
local data = require("hello")
local story = plotknot.load(data)
story:start()
for _, line in ipairs(story:current()) do
if line.type == "text" then print(line.text) end
end
All examples are verified in the test suite (tests/test_runtime.lua) — they compile, load, start, and produce expected output.