Interactive Fiction Companion
Interactive Fiction Companion
Wherever your story is, this is the next move.
This is not a schedule and it is not a syllabus. It is a flow, and it starts wherever you already are — with nothing, with something that runs and feels flat, with something that has got away from you, or with something you think might be finished. Pick the one that is true right now. You can sit in a state for five minutes or five weeks; nothing here expires.
You write in The Spaces Between’s Workshop app, in your browser. No Inky, no installs, and nobody asks who you are until you want to publish. This page is the other window.
Nobody is going to tell you that you are learning to program. But variables, conditionals, state machines and functions are all in here, and by the end you will have used every one of them to make something you actually wanted.
Where is your story right now?
Be honest rather than optimistic. The wrong state gives you the wrong advice.
I cannot see your story from here — there is no analyser on this page reading your .ink file. You tell me where you are and I will tell you what to do next. Structure detection that genuinely looks at your manuscript is being built into the Workshop app, which is the only place that can honestly do it.
Already know what you are looking for?
Nothing yet
“What am I even making?”
You are here because there is no story yet. Good. This is the shortest state and the one people spend the longest dreading.
You do not need an idea. You need a skeleton.
An idea will not save you and a blank page will not give you one. What gets you moving is a structure that already runs, so that the first thing you do is decorate rather than invent. So take mine.
Paste this into the Workshop app. It runs the moment you paste it, so you have a working game before you have made a single decision. It also loops forever — every room eventually leads back to another room, and the ending loops right back to the start. That is by design. Don't fix the loop. Decorating it is the point: the structure is given to you free, and your creativity goes on top of it.
Welcome to The Nine Rooms!
-> room_1
=== room_1
You are in the first room. There is a door to the north.
+ Go north
-> room_2
+ Sit Here
-> room_1
=== room_2
You are in the Second Room.
There is a door to the south.
There is a door to the east.
There is a door to the west.
There is a door to the north.
+ Go north
-> room_3
+ Go south
-> room_1
+ Go east
-> room_4
+ Go west
-> room_5
=== room_3
You have entered the third room.
There is a door to the south.
There is a door to the north.
+ Go north
-> room_6
+ Go south
-> room_2
=== room_4
You have entered the fourth room.
There is a door to the west.
+ Go west
-> room_2
+ Stay Here
-> room_4
=== room_5
You have entered the fifth room.
There is a door to the east.
+ Go east
-> room_2
+ Stay Here
-> room_5
=== room_6
You have entered the sixth room.
There is a door to the south.
There is a door to the north.
There is a door to the west.
There is a door to the east.
+ Go north
-> room_9
+ Go south
-> room_3
+ Go east
-> room_8
+ Go west
-> room_7
=== room_7
You have entered the seventh room.
There is a door to the east.
+ Go east
-> room_6
+ Stay Here
-> room_7
=== room_8
You have entered the eighth room.
There is a door to the west.
+ Go west
-> room_6
+ Stay Here
-> room_8
=== room_9
This is the Final Room. There is no escape from here.
Make your choice:
+ Win the Game
-> end_game
+ Stay Here
-> room_9
=== end_game
You have won the game! Or have you?
-> room_1What you are actually looking at
Two things, and that is genuinely all so far.
A knot is just a named section of your story. You mark it with ===. Think of knots like chapters or scenes. A divert sends the reader to a knot, and it looks like an arrow, ->. Think of -> as “go to.” That's all it means.
So a room is just a named place in the graph, and it can stand for anything you want it to. It doesn't have to be a dungeon — it can be outside, in a labyrinth, anywhere. It doesn't have to be places, it can be choices. It doesn't have to be choices, it can be events. It doesn't have to be choices made by the protagonist at all.
Three premises, if you want one handed to you
These are all mine, and you are welcome to them. Take one and stop deliberating.
You walk into a dimly lit tavern. The bartender looks up. What'll it be?
Why it works: the reader is in a place, with a person, being asked a question, in three lines. Nothing needs explaining. You can start writing choices immediately.
=== tavern ===
You walk into a dimly lit tavern.
The bartender looks up.
What'll it be?
* Ale, please.
The bartender slides a foamy mug your way.
-> tavern
* Just water.
The bartender raises an eyebrow but pours you a glass.
-> tavern
* I'm not thirsty.
The bartender shrugs and turns away.
-> market
=== market ===
You step outside into the bright sun.You wake up, groggy, in an unfamiliar place. There are nine rooms and one of them is a greenhouse.
Why it works: it gives you a reason to grow things, which gives you numbers that climb, which is state before you have had to think about state. This is the Nine Rooms skeleton with a mood poured into it.
VAR has_key = false
VAR onion_seeds = 0
VAR onion_sprouts = 0
=== room_4
This is a greenhouse - there are many pots and soil here, and a cabinet with a lock on it.
{ onion_sprouts > 0: You see {onion_sprouts} onion sprouts. }
There is a door to the west.
+ Go west
-> room_2
+ { has_key } Unlock the cabinet.
There is a secret tunnel in the cabinet!
-> room_7
+ { onion_seeds > 0 } Plant an onion seed
~ onion_sprouts = onion_sprouts + 1
~ onion_seeds = onion_seeds - 1
You plant an onion seed
-> room_4The ferryman does not look up when you board.
Why it works: one character, one crossing, one thing you are tracking — whether he warms to you. Small enough to finish, and the whole thing is a single meaningful choice repeated.
VAR trust = 0
=== arrival
The ferryman does not look up when you board.
+ ["It's cold."]
~ trust = trust + 1
"It is," he agrees, and something in him unlocks a notch.
-> crossing
+ [Say nothing.]
~ trust = trust - 1
The silence sets, like something cooling.
-> crossing
=== crossing
The water goes by underneath, {trust > 0: companionably|like an argument you are losing}.
{trust > 0: He offers you his coat.|He does not offer you his coat.}
-> ENDThat is the assignment. Add descriptions of what is in the rooms. Don't add new rooms yet, don't add variables yet, don't restructure anything. Nine rooms, decorated, all the way through. Anything else is a way of not starting.
Your dungeon is yours. Every room says something only you would have written, and you have played it through at least once from the first room to the last.
It runs
“Why does it feel flat?”
It runs. You played it. And something is wrong with it that you cannot name, which is much worse than something being wrong with it that you can.
Here is the thing nobody tells you: the flatness is almost never in the prose. It is in the shape. Your reader makes a choice and the story does not carry it anywhere. So before you rewrite a single sentence, go and find out what shape you actually built.
Diamond first. Always.
If you learn exactly one shape, learn this one, because it is the shape that fixes flat.
Open, differ, converge. The reader picks a path, something changes, and the story comes back together. Divergence is cheap — three short branches. Convergence is rich — one long shared trunk, seasoned by what they picked. You write the trunk once, and it reads differently for everybody.
VAR approach = ""
The cave mouth yawns before you. How do you enter?
* [Torch raised high]
Light floods the walls, revealing ancient paintings.
~ approach = "bold"
* [Creeping in the shadows]
You slip inside silently. Something skitters away.
~ approach = "stealthy"
* [Calling out a greeting]
Your voice echoes back threefold. Then silence.
~ approach = "friendly"
- -> cave_interior
=== cave_interior ===
The cave opens into a vast chamber.
{approach == "bold": The paintings continue deeper, guiding your way.}
{approach == "stealthy": Your eyes have adjusted. You see everything.}
{approach == "friendly": A faint voice answers from the dark.}
Something glints on the far wall.
-> ENDLook at what that costs you: three short branches and one paragraph. Look at what it buys: the cave chamber knows how you walked in. That is the entire trick, and every other shape on this page is a variation on it.
I want to be straight with you about that. There is no analyser here reading your .ink and telling you what you built — I am asking you, and you are answering. Real structure detection is being built into the Workshop app, where the story actually lives and where a tool can genuinely see it. Until then, the diagnosis is yours to make, and the questions below are how you make it.
Name the shape you built
Answer these about your own story. Answer honestly rather than aspirationally.
- Do any two paths ever meet again? If no — every choice leads somewhere unique and nothing reconverges — you built a Time Cave, and that is why it feels unfinishable.
- Is there one room the reader keeps returning to? Then you built a Weave or a Hub-and-Spoke — a Weave if the options run out, a Hub-and-Spoke if they unlock each other.
- Does everything reconverge, every time, immediately? That is Branch-and-Bottleneck, and if it feels flat it is because nothing is surviving the bottleneck.
- Does it reconverge and the trunk knows what you did? Congratulations, that is a Foldback, and you are further along than you thought.
- Is it a straight line of obstacles? Gauntlet. Fine shape. Check that failing one does not end the story.
- None of the above — it just goes forward and the choices are flavour? Then you have a spine with no shape on it yet, and a Diamond is the first one to put there.
The ramp
The shapes are tiered, and the tiers are not a ranking — they are an order of operations. Every shape in the Basic column can be built out of knots, choices and diverts alone. Everything in Intermediate wants a variable. Everything in Advanced wants a state model you already trust, which is why starting there goes so badly.
Where everyone starts. One choice, one consequence, no bookkeeping. You can write either of these knowing nothing but knots, choices and diverts.
These need a variable or two. You are no longer branching the text — you are branching the state and letting it colour a shared path. This is where most finished stories live.
These are what you graduate into, and they all want the same thing: a state model you trust. Do not start here. Arrive here.
Every shape’s way out
This is the part I actually want you to keep. Each shape has a characteristic way of going wrong and a specific shape it turns into when you fix it. You are not abandoning what you built — you are growing it.
| If you built | and it feels like this | go here next |
|---|---|---|
| Diamond | Your reader picks, something happens, and by the next paragraph it may as well not have happened at all. | Foldback |
| Time Cave | You are on scene twelve of twenty-seven, you have four more endings to write, and you cannot face scene thirteen. The story feels unfinishable because it is. | Branch-and-Bottleneck |
| Branch-and-Bottleneck | The bottlenecks are so tight nothing survives them. The reader chose, and by chapter three it is the same story it would have been anyway. This is on rails with extra steps. | Foldback |
| Foldback | Your spine remembers, but only for one sentence per checkpoint. The seasoning is thinner than the branches were, and the reader can feel it. | QBN |
| Gauntlet | Your challenges can only be failed. One stumble and the reader is dead, restarting, and not coming back. | Hub-and-Spoke |
| Hub-and-Spoke | Everything is unlocked at once. The reader is looking at eleven options with no reason on earth to pick any particular one. | QBN |
| Weave | The reader keeps coming back to your hub and it says the exact same thing every single time. The room stops feeling like a place and starts feeling like a menu. | Loop-and-Grow |
| Loop-and-Grow | The reader loops, and nothing has changed. It does not read as a loop — it reads as a bug. | Weave |
| Parallel Paths | Two storylines, and you are writing every scene twice. Track B is three beats behind Track A and getting further behind. | Branch-and-Bottleneck |
| QBN | You have accumulated so much state that you genuinely cannot predict what the reader will be looking at. You are debugging your own story. | Branch-and-Bottleneck |
Full cards for all ten, with the code for each:
The most common flatness of all, and the easiest to fix. You built a diamond, the reader chose, the paths converged — and the trunk doesn't mention it. Nothing came through. Give each branch a VAR to set on its way past, and read it back in the trunk. That one line turns a diamond into a Foldback, and a Foldback is a story that remembers.
You can say out loud what shape your story is, and name the one it should grow into next.
It has a shape
“How do I make it remember me?”
You have a shape. Now you want the story to remember you, and this is the single move that separates a branching story from a good one.
It is also, and I am going to say this once and then leave you alone about it, the moment you start programming. Variables, conditionals, state. Nobody is going to make you call it that. But that is what you are doing, and you are about to find out you can.
VAR and ~
Sometimes you need the story to remember something — did the player pick up the key? Are they friendly or rude? Declare a variable at the top of the file with VAR, and change it anywhere with ~, which just means ‘do something behind the scenes’ — the reader never sees that line.
VAR has_key = false
VAR friendliness = 0
* Pick up the key.
~ has_key = true
You grab the rusty key.
+ Look around the room.
The room is cold and empty.Conditions
Now that you can remember things, you can check them with conditions. Read { has_key } as ‘if has_key is true.’ Gate a whole block of text, or gate a single choice — and gating the choice is the stronger move, because a reader who never found the key never even sees that the option existed.
{ has_key:
You unlock the door and step through.
- else:
The door is locked. You need a key.
}
* { has_key } Unlock the door.
The door creaks open.
* Try to force it open.
It won't budge.Plant, remind, payoff
Having a variable is not the same as the reader feeling it. The most satisfying use of state has three beats, and skipping the middle one is why so many stories with variables still read as flat.
- Plant. The reader does something — picks up an item, helps someone, reads a note — and a flag flips. They should barely notice.
- Remind. Later, one sentence that lets them know the story kept it. This is the beat everybody skips, and it is the one that does the work.
- Payoff. Something is different. A path opens, a character reacts, an ending exists that otherwise would not have.
VAR knows_password = false
// PLANT
=== office ===
A sticky note on the monitor reads: "DELTA-7"
~ knows_password = true
You memorize it and move on.
-> corridor
// REMIND
=== terminal ===
A login prompt blinks on the screen.
{knows_password: The sticky note. DELTA-7. Could that be it?}
// PAYOFF
* {knows_password} [Type DELTA-7]
Access granted. Files flood the screen.
-> secret_files
* [Try to guess]
You type "password." Denied. "admin." Denied.
-> terminal_lockedSeason the trunk, don’t duplicate it
Here is the discipline that keeps this from exploding on you. When state changes a scene, you do not write two copies of the scene. You write one, and you season it.
VAR trust = 0
=== arrival
The ferryman does not look up when you board.
+ ["It's cold."]
~ trust = trust + 1
"It is," he agrees, and something in him unlocks a notch.
-> crossing
+ [Say nothing.]
~ trust = trust - 1
The silence sets, like something cooling.
-> crossing
=== crossing
The water goes by underneath, {trust > 0: companionably|like an argument you are losing}.
{trust > 0: He offers you his coat.|He does not offer you his coat.}
-> ENDOne crossing. Not two. The {trust > 0: companionably|like an argument you are losing} is doing the entire job — same paragraph, different story. Write the trunk first, plain, all the way through. Season it afterwards. Do it in that order and you will never once find yourself maintaining two versions of the same scene.
Two things you get for free
Ink counts visits for you. A knot name evaluates to how many times it has been visited, and you never had to declare anything:
=== market ===
{market == 1: The market is overwhelming on first visit. So many stalls.}
{market == 2: The market feels more familiar now. You know where things are.}
{market > 2: You navigate the market on autopilot.}
+ [Browse the stalls] -> stalls
+ [Leave the market] -> town_squareAnd an irreversible choice needs no new syntax at all — just an assignment guarded so the choice that fires it can never fire again. Turning the key sets ~ door_sealed = true, and the choice that turned it required not door_sealed to appear at all. So the instant it fires, its own condition goes false, and the option is gone for the rest of the story. Once turned, it doesn't turn back.
VAR has_key = false
VAR door_sealed = false
=== room_9
The final room. {has_key: The lock is waiting for what you are carrying.|There is a lock, and you have nothing to put in it.}
+ {has_key and not door_sealed} [Turn the key]
~ door_sealed = true
It turns once. It will not turn back.
-> end_game
+ [Stay here] -> room_9
+ [Go back south] -> room_6Three to seven. Go past seven and you will lose track of what the reader can possibly be seeing; go under three and the choices stop feeling consequential. And decide the list before you write the branches — state you retrofit is state you will get wrong.
There is at least one moment where you can point at the screen and say “that sentence is there because of something the reader did three scenes ago.”
It is getting messy
“This is unmanageable.”
You are here because it has got away from you. That is not a sign you are bad at this. It is a sign you are far enough in for the structure to start mattering, and every branching story goes wrong in exactly one of two ways.
The two ways a branching story dies
Knowing which one you are drifting toward is half the craft.
You write a choice with 3 options, each leads to a new scene with 3 options, each leads to… and suddenly you are writing 27 scenes for a 3-choice story. You burn out at scene 12. The player only ever sees 3 of them.
Cause: treating every choice as a fork that needs its own downstream content.
You have built: a Time Cave. Grow it into: Branch-and-Bottleneck — let the paths reconverge, and write the next chapter once.
The player makes choices, but the story barrels toward the same scenes regardless. Choices feel cosmetic. Players disengage.
Cause: over-correcting for explosion and converging everything immediately, with no consequence carried forward.
You have built: a bottlenecked spine with nothing getting through. Grow it into: a Foldback — same spine, but it remembers which way you came.
Don't branch the text — branch the variables, then let those variables colour a shared path. Explosion happens when you duplicate scenes; rails happen when you refuse to. State is the third option, and it is the only one that scales.
Five heuristics, in order
- Decide your shape first. Diamond? Branch-and-bottleneck? Quality-based? Pick it before you write a word of prose. That is what are for.
- Pick your tracked state. What 3–7 variables actually matter? Go past ~7 and you'll lose track; go too few and choices stop feeling consequential.
- Plan your bottlenecks before your branches. Know where the river reconverges before you let it split.
- Aim for diamonds two to four beats long. Branch, let the player feel the difference, converge. Repeat.
- Make divergence cheap and convergence rich. Short branches, long shared trunks, coloured by state.
Untangling what you already have
You do not have to start over. You almost never have to start over. Three moves, in this order:
- Draw it. You cannot fix a shape you cannot see. Get a map of your own story out of an AI — it is genuinely good at this, and it will show you the branch you forgot to terminate.
- Find the widest point and put a bottleneck just after it. Every path from that fan-out gets a divert to one shared knot. Everything downstream of that knot you now only have to write once.
- Push the difference into variables. Whatever made those branches feel different — that goes into a
VARnow, and gets read back in the trunk you just merged them into.
Draw a mermaid flowchart of the knots in this Ink story and how they connect -- which choice from which room leads to which knot. Use the room names as labels, and mark any tunnel or loop clearly.
AI is excellent at filling a shape and bad at choosing one. Good: mapping what you have, decorating rooms, tonal variation, expanding a diamond to size, generating the flavour text that makes convergence rich. Bad: choosing the shape, designing the state model, deciding what the story means, knowing which choice should be irreversible. Decide your state first — even a scribbled list of three things — then ask AI to help you use it.
You can draw your story on one piece of paper, and there are fewer distinct scenes on it than there were when you started untangling.
It is ready
“Is it good enough?”
You think it is done. Let's find out. Nothing here needs anybody's permission — this is all stuff you can check yourself, before you ever hit publish.
Read it like a stranger
Play it in Preview, not in the editor. Typos hide in the Manuscript and jump right out in Preview, and so does every clunky transition you stopped being able to hear.
- Every path reaches
-> ENDor loops back to a valid choice. No dead ends — a knot with no choices and no divert leaves your reader stranded. - At least one variable gets a moment where an earlier choice visibly changes the story later on. That is the whole point of tracking it.
- You have played at least two different paths yourself, start to finish, and both make narrative sense.
- Your characters' lines still sound like them when you read them out loud, from the first scene to the last.
- Your choice transitions read as prose, not as clunk. Read them aloud too.
- Your
# mood:tags match the scene rather than habit —# mood:dangeron a calm beat reads as a bug. - Nothing on any path contradicts something the reader already saw on that same path.
That last one is the hardest to check by hand, because it means walking every combination. Hand it to an AI — this is one of the things it is genuinely good at:
Here's my Ink script with variables and conditions: [PASTE YOUR FULL SCRIPT] Playtest this for me. Try these paths: 1. The "completionist" -- find everything, talk to everyone 2. The "speedrunner" -- take the most direct route to an ending 3. The "contrarian" -- choose the least obvious option every time For each path, tell me: - Does the story make sense from start to finish? - Are there any dead ends (no choices and no -> END)? - Are there moments where a variable SHOULD matter but doesn't? - Any dialogue that contradicts what happened earlier on this path?
The three errors you will actually hit
| Error | Fix |
|---|---|
not found: knot_name | Check spelling — knot names must match exactly. |
| Story stops unexpectedly | You are missing a -> END or a divert to the next knot. |
| Choice doesn't appear | Make sure it starts with * or + and is indented under the right knot. |
Handing it over
Publishing happens in the Workshop app, and there are three things it wants from you that your story does not have yet.
- Tags. A
# mood:tag on its own line before the content it colours —wonder,calm,sadness,danger. A# live:characterIdtag lights up the character voice indicator while that character is speaking. - Metadata. Title, the in-world author, themes. The metadata drawer walks you through it; underneath, it is building a JSON bundle with your compiled Ink, your characters, and that metadata block.
- Review. Submit it. Review is LLM-backed and it hard-gates publish — your story's review status has to come back
passedbefore it goes live. That is a real gate, not a formality. Work the checklist above and most stories clear it first time.
# mood:calm -> arrival === arrival # live:ferryman The ferryman does not look up when you board. + ["It's cold."] -> crossing + [Say nothing.] -> crossing === crossing # mood:sadness "Go on, then," he says. "It doesn't get easier from here." -> END
Put enough people in a room writing interactive fiction and somebody will write something dark. That is a certainty, not a risk. A mature rating does not reject your story — it routes to manual review, and there is a human on the other end of that who has approved plenty. Intense, complicated, dark work still ships.
And then the next one
Publish it, then go and play what somebody else built. That part is not optional and it is not sentimental — you will learn more about your own choices from watching a stranger's story fail to remember them than from any amount of rereading your own.
What to learn next: threads (<- knot_name, pulling a parallel storyline into the scene you are in), LISTs for state that is more than true-or-false, and functions for text your story generates rather than text you typed. All of it builds directly on what you just did. None of it was needed to finish.
I teach this as a one-day intensive, and the difference is not the material — the material is on this page. The difference is a room: somebody to play your story who is not you, somebody to play theirs, and me on hand for the stretch where you are stuck on something that turns out to be a typo. If you want that, it is on the class list. If you don't, keep going. The flow works alone.
The ten shapes
The vocabulary the rest of this page is speaking.
Ten shapes. They are composable — you can nest diamonds inside hub spokes, chain gauntlets into a branch-and-bottleneck spine, or use QBN to decide which shape fires next. Think of them like functions: pick a shape, fill in your content, connect it to the next one.
Every card carries the same three things: when to use it, the way you can tell you have accidentally built it badly, and the shape it grows into when you fix that. Paste any of the code straight into the Workshop app.
Where everyone starts. One choice, one consequence, no bookkeeping. You can write either of these knowing nothing but knots, choices and diverts.
These need a variable or two. You are no longer branching the text — you are branching the state and letting it colour a shared path. This is where most finished stories live.
These are what you graduate into, and they all want the same thing: a state model you trust. Do not start here. Arrive here.
Diamond
Open, differ, converge. Single chokepoint fans out then reconverges.
When you want a meaningful choice that flavors later text without multiplying your workload. The reader picks a path, something changes, and the story comes back together.
Your reader picks, something happens, and by the next paragraph it may as well not have happened at all.
Give the branch something to leave behind. One VAR, set inside each option, read back in the trunk. A diamond that remembers is a Foldback. Go to Foldback →
VAR approach = ""
The cave mouth yawns before you. How do you enter?
* [Torch raised high]
Light floods the walls, revealing ancient paintings.
~ approach = "bold"
* [Creeping in the shadows]
You slip inside silently. Something skitters away.
~ approach = "stealthy"
* [Calling out a greeting]
Your voice echoes back threefold. Then silence.
~ approach = "friendly"
- -> cave_interior
=== cave_interior ===
The cave opens into a vast chamber.
{approach == "bold": The paintings continue deeper, guiding your way.}
{approach == "stealthy": Your eyes have adjusted. You see everything.}
{approach == "friendly": A faint voice answers from the dark.}
Something glints on the far wall.
-> ENDTime Cave
Pure exponential branching. No convergence. Every choice leads to unique content.
When you want maximum divergence and every playthrough to feel completely different. Best for short stories because content grows exponentially. Classic "Choose Your Own Adventure" shape.
You are on scene twelve of twenty-seven, you have four more endings to write, and you cannot face scene thirteen. The story feels unfinishable because it is.
Let the paths come back together. Fan out, let the reader feel the difference, then reconverge and write the next chapter once. That is Branch-and-Bottleneck. Go to Branch-and-Bottleneck →
The letter says: "Come to the docks at midnight."
* [Go to the docks]
A figure waits in the fog.
** [Approach them]
"You came." They hand you a key.
*** [Take the key]
The key opens a warehouse. Inside: gold.
-> END
*** [Refuse the key]
"Your loss." They vanish into the fog.
-> END
** [Hide and watch]
They wait. Then leave a package on the ground.
*** [Take the package]
Inside: a map to somewhere far away.
-> END
*** [Leave it]
You walk home. Some mysteries stay unsolved.
-> END
* [Burn the letter]
The paper curls and blackens.
** [Go to bed]
You sleep soundly. Life goes on.
-> END
** [Regret it immediately]
You fish the scraps from the fire.
"...docks...mid..." Useless.
-> ENDBranch-and-Bottleneck
Repeated diamonds chained. The most common game structure (Telltale, Life is Strange, etc).
When you want an episodic structure with meaningful choices that don't spiral out of control. Each "chapter" fans out and reconverges, so you only need to write the next chapter from one starting point. This is the workhorse of commercial narrative games.
The bottlenecks are so tight nothing survives them. The reader chose, and by chapter three it is the same story it would have been anyway. This is on rails with extra steps.
Something has to get through the bottleneck. Set state in the branch, read it in the trunk, and let it keep mattering two chapters later. That is a Foldback. Go to Foldback →
VAR ch1_choice = ""
VAR ch2_choice = ""
-> chapter_one
=== chapter_one ===
The king demands your counsel.
* [Advise war]
"Strike first," you say. The king smiles.
~ ch1_choice = "war"
* [Advise diplomacy]
"Send an envoy." The king frowns but nods.
~ ch1_choice = "peace"
- -> chapter_two
=== chapter_two ===
{ch1_choice == "war": Troops gather at the border.}
{ch1_choice == "peace": An envoy rides out at dawn.}
A spy brings disturbing news.
* [Interrogate the spy]
Under pressure, they reveal a traitor.
~ ch2_choice = "traitor"
* [Follow the spy secretly]
They lead you to a hidden camp.
~ ch2_choice = "camp"
- -> chapter_three
=== chapter_three ===
{ch2_choice == "traitor": You confront the traitor at court.}
{ch2_choice == "camp": You raid the camp at dawn.}
The kingdom's fate is sealed.
{ch1_choice == "war" and ch2_choice == "traitor": A bloody but decisive victory.}
{ch1_choice == "peace" and ch2_choice == "camp": A quiet triumph. No one even knows.}
-> ENDFoldback
Branches that always rejoin the spine, but the story remembers which path you took.
When you want the illusion of major divergence while keeping a manageable story spine. The player takes different scenic routes but always arrives at the same checkpoints, and the prose acknowledges their specific journey.
Your spine remembers, but only for one sentence per checkpoint. The seasoning is thinner than the branches were, and the reader can feel it.
Stop adding branches and start adding state. Fewer forks, more qualities, and let the accumulated qualities decide what the scene even offers. That is QBN. Go to QBN →
// Foldback: paths FEEL different but arrive at the same outcome. // Compare with Branch-and-Bottleneck where endings genuinely diverge. -> prison === prison === The cell door is rusted. You need to get out. * [Pick the lock] Click. Click. The door swings open. You slip into the corridor, heart pounding. -> courtyard * [Smash the wall] Brick dust everywhere. A hole, barely big enough. You squeeze through, scraping skin on stone. -> courtyard === courtyard === // Same destination regardless of path above. The courtyard stretches before you. The gate is ahead. * [Climb the wall] Your fingers find holds in the old mortar. Over the top. You drop to the other side. -> road * [Bluff the gatekeeper] "Shift change." He squints, shrugs, waves you through. -> road === road === // The ending is IDENTICAL. That's the point of Foldback: // the player felt agency, but the story spine never moved. Miles later, you stop to breathe. You're free. The road stretches ahead. Whatever happened back there — it's done now. -> END
Gauntlet
Gated stations, fail forward. Linear sequence where you can pass clean or messy.
When you want a sequence of challenges that the player always gets through, but their performance accumulates. Great for tension-building where the final outcome depends on how many gates you passed cleanly.
Your challenges can only be failed. One stumble and the reader is dead, restarting, and not coming back.
Fail forward. Every gate gets a way through that always works and a penalty that counts up, and the ending reads the tally. If it still feels like a corridor, let the reader pick the order and it becomes a Hub-and-Spoke. Go to Hub-and-Spoke →
VAR wounds = 0
VAR has_rope = true
-> gate_one
=== gate_one ===
A chasm blocks the path. Far side, ten feet away.
* {has_rope} [Throw the rope across]
The rope catches! You cross safely.
* [Jump for it]
You leap — and barely catch the edge.
~ wounds = wounds + 1
Your hands are bleeding. But you made it.
- -> gate_two
=== gate_two ===
A locked door. The lock is rusted.
* {wounds == 0} [Pick the lock with steady hands]
Click. It opens smoothly.
* [Force it open]
You slam your shoulder into it. It gives.
~ wounds = wounds + 1
Something in your shoulder pops.
- -> ending
=== ending ===
You stumble into daylight.
{wounds == 0: Not a scratch on you. Perfect run.}
{wounds == 1: Battered, but alive. Could be worse.}
{wounds >= 2: You collapse. Barely made it.}
-> ENDHub-and-Spoke
Central hub with gated spokes. Return to hub after each spoke, new options unlock.
When you want the player to explore a location or conversation where completing one area unlocks another. Classic RPG town or detective investigation structure. Each spoke is a self-contained scene.
Everything is unlocked at once. The reader is looking at eleven options with no reason on earth to pick any particular one.
Gate the spokes. A flag set in one spoke opens the next, and suddenly there is an order the reader discovers instead of one you enforced. Gate on accumulated qualities instead of single flags and you have grown a QBN. Go to QBN →
VAR talked_to_guard = false
VAR searched_alley = false
-> town_square
=== town_square ===
The town square. People bustle past.
+ {not talked_to_guard} [Talk to the guard]
-> guard
+ {not searched_alley} [Search the alley]
-> alley
+ {talked_to_guard and searched_alley} [Enter the locked tower]
The clues align. You know the password.
-> tower
+ [Leave town]
You walk away, mystery unsolved.
-> END
=== guard ===
"Saw someone in the alley last night," the guard mutters.
~ talked_to_guard = true
-> town_square
=== alley ===
Behind the crates: a torn note. "The password is MOONRISE."
~ searched_alley = true
-> town_square
=== tower ===
"MOONRISE," you whisper. The door swings open.
Inside, the truth awaits.
-> ENDWeave
Sticky hub, visit branches, return. Hub knot loops, tracks what you've visited.
When you want the player to explore a space at their own pace, examining things in any order. Each option disappears after being used, and the story moves on when everything has been seen.
The reader keeps coming back to your hub and it says the exact same thing every single time. The room stops feeling like a place and starts feeling like a menu.
Use the visit count you already get for free, or a sequence, so the hub reads differently on the second pass. If the whole scene should change and not just the sentence, grow it into Loop-and-Grow. Go to Loop-and-Grow →
VAR saw_fish = false
VAR saw_herbs = false
VAR saw_fortune = false
-> market_square
=== market_square ===
The market bustles around you.
+ {not saw_fish} [Visit the fish stall]
The fishmonger waves a gleaming trout.
"Fresh today! Caught it meself!"
~ saw_fish = true
-> market_square
+ {not saw_herbs} [Browse the herb cart]
Lavender and rosemary scent hits you.
The herbalist nods knowingly.
~ saw_herbs = true
-> market_square
+ {not saw_fortune} [See the fortune teller]
She turns a card. The Tower.
"Change is coming," she whispers.
~ saw_fortune = true
-> market_square
+ {saw_fish and saw_herbs and saw_fortune} [Leave the market]
You've seen it all. Time to move on.
-> END
+ [Leave the market early]
You slip away through the crowd.
-> ENDLoop-and-Grow
Same scene revisited with accumulated state changing what's available.
When you want to model progression through repetition, like a Groundhog Day loop, a growing relationship, or a place that changes over time. Each visit feels different because state has evolved.
The reader loops, and nothing has changed. It does not read as a loop — it reads as a bug.
Every trip round has to move a number or set a flag, and the text has to show it. If nothing accumulates, you do not have a loop-and-grow, you have a Weave — so make it one and let the options run out. Go to Weave →
LIST Knowledge = seeds, water, sunlight
-> garden
=== garden ===
{LIST_COUNT(Knowledge) == 0: The garden is dead earth. Nothing grows.}
{Knowledge ? seeds: Small green shoots push through the soil.}
{Knowledge ? water: The earth is dark and damp.}
{Knowledge ? sunlight: Golden light pools between the leaves.}
+ {not (Knowledge ? seeds)} [Plant the seeds you found]
You press seeds into the earth, one by one.
~ Knowledge += seeds
-> garden
+ {not (Knowledge ? water)} [Dig a channel to the stream]
Water trickles in, soaking the ground.
~ Knowledge += water
-> garden
+ {not (Knowledge ? sunlight)} [Cut back the dead branches overhead]
Light floods in. The soil warms.
~ Knowledge += sunlight
-> garden
+ {Knowledge ? (seeds, water, sunlight)} [Sit in your garden]
It took time. But look at it now.
-> ENDParallel Paths
Two sustained storylines with occasional bridge points where you can cross over.
When you want two fundamentally different experiences that occasionally intersect. Good for dual-protagonist stories or "upstairs/downstairs" structures where the player commits to a perspective but can switch at key moments.
Two storylines, and you are writing every scene twice. Track B is three beats behind Track A and getting further behind.
Collapse them. One trunk, one track variable, and the scenes read differently depending on who is walking through them. You lose almost nothing and you halve the writing — that is Branch-and-Bottleneck again. Go to Branch-and-Bottleneck →
VAR current_path = "none"
Two letters arrive. One sealed in red, one in blue.
* [Open the red letter]
~ current_path = "red"
-> red_1
* [Open the blue letter]
~ current_path = "blue"
-> blue_1
=== red_1 ===
The Red Court summons you. Intrigue and poison.
* [Continue on the red path]
-> red_2
* [Defect to the blue side]
~ current_path = "blue"
-> blue_2
=== blue_1 ===
The Blue Fleet needs a navigator. Salt and stars.
* [Continue on the blue path]
-> blue_2
* [Defect to the red side]
~ current_path = "red"
-> red_2
=== red_2 ===
The courtiers whisper your name.
{current_path == "red": You've been here all along. They trust you.}
{current_path == "blue": A defector. They watch you carefully.}
-> finale
=== blue_2 ===
The horizon stretches forever.
{current_path == "blue": The crew knows you. You belong.}
{current_path == "red": A landlubber on deck. You have much to prove.}
-> finale
=== finale ===
War comes regardless. But where you stand determines everything.
-> ENDQBN (Quality-Based Narrative)
State determines which content appears. Different text and choices surface based on accumulated qualities.
When you want the story to feel responsive to the player's accumulated identity or stats. Instead of explicit branching, content appears or hides based on who the player has become. This is how Fallen London works.
You have accumulated so much state that you genuinely cannot predict what the reader will be looking at. You are debugging your own story.
Cut back to three to seven qualities that actually matter, and pin a spine back underneath so there is always a next scene even when every check fails. Branch-and-Bottleneck is the floor you put under it. Go to Branch-and-Bottleneck →
LIST Traits = brave, clever, kind
VAR reputation = 0
-> tavern
=== tavern ===
The tavern is loud tonight.
{Traits ? brave: The scarred fighter nods at you from across the room.}
{Traits ? clever: You notice the barkeep's loaded dice.}
{Traits ? kind: A child tugs your sleeve, eyes wide.}
* {not (Traits ? brave)} [Stand up to the bully]
You shove him back. The room goes quiet.
~ Traits += brave
-> tavern
* {not (Traits ? clever)} [Challenge the barkeep's game]
"Those dice are weighted." He goes pale.
~ Traits += clever
-> tavern
* {not (Traits ? kind)} [Buy the child a meal]
The kid grins. Warmth blooms in your chest.
~ Traits += kind
-> tavern
* {LIST_COUNT(Traits) >= 2} [You've made your mark. Leave.]
~ reputation = LIST_COUNT(Traits)
The door swings shut behind you.
Reputation earned: {reputation}.
-> ENDThe escape routes, all in one place
| If you built | and it feels like this | go here next |
|---|---|---|
| Diamond | Your reader picks, something happens, and by the next paragraph it may as well not have happened at all. | Foldback |
| Time Cave | You are on scene twelve of twenty-seven, you have four more endings to write, and you cannot face scene thirteen. The story feels unfinishable because it is. | Branch-and-Bottleneck |
| Branch-and-Bottleneck | The bottlenecks are so tight nothing survives them. The reader chose, and by chapter three it is the same story it would have been anyway. This is on rails with extra steps. | Foldback |
| Foldback | Your spine remembers, but only for one sentence per checkpoint. The seasoning is thinner than the branches were, and the reader can feel it. | QBN |
| Gauntlet | Your challenges can only be failed. One stumble and the reader is dead, restarting, and not coming back. | Hub-and-Spoke |
| Hub-and-Spoke | Everything is unlocked at once. The reader is looking at eleven options with no reason on earth to pick any particular one. | QBN |
| Weave | The reader keeps coming back to your hub and it says the exact same thing every single time. The room stops feeling like a place and starts feeling like a menu. | Loop-and-Grow |
| Loop-and-Grow | The reader loops, and nothing has changed. It does not read as a loop — it reads as a bug. | Weave |
| Parallel Paths | Two storylines, and you are writing every scene twice. Track B is three beats behind Track A and getting further behind. | Branch-and-Bottleneck |
| QBN | You have accumulated so much state that you genuinely cannot predict what the reader will be looking at. You are debugging your own story. | Branch-and-Bottleneck |
These ten cards are condensed from the Ink Shape Catalog, which also carries a vibe prompt for each shape. For nesting them inside one another, the Shape Composition Guide. For where these shapes come from and what published work uses them, the Story Structure Atlas. For the AI prompts that fill them, the Vibe Authoring Toolkit.