Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/the-beach-mystery/llms.txt

Use this file to discover all available pages before exploring further.

The entire narrative of The Beach Mystery lives as a plain JavaScript object called STORY_NODES in data/story.js. Each entry in that object is a node — a single unit of story that can display text, present a choice, or pose a riddle. Nodes are connected to one another through next properties and options[].next arrays, forming a directed graph you can extend at any point simply by adding new objects and updating a single reference. This guide walks you through every step needed to splice in a new branch, from identifying the right anchor point to wiring up a custom ending.

How the Node Graph Works

Every node has a unique string id that other nodes reference to hand control forward. When the game engine finishes displaying a node, it looks up the next node ID and transitions to it. The three node types determine what players see and how control flows:
TypeWhat it rendersHow it advances
narrationA sequence of text lines, then a Continue buttonFollows the single next property
choiceA prompt and two or more labelled buttonsFollows options[i].next for whichever button the player taps
riddleRiddle text, a text input, and an optional hintFollows next only after the correct answer is entered
A node may also carry optional metadata that affects the run-time game state:
  • setState — an object whose keys are written into the game state when the node is reached (e.g. { hasJewel: true })
  • ending — the ID of an ending declared in the ENDINGS object; reaching this node triggers the victory screen
Nodes are loaded at start-up and never fetched from a server — the full graph is in memory for the entire session. Adding a node only requires a deploy; there is no database to migrate.

Adding a New Branch

1

Identify where to branch

Open data/story.js and find the node that should lead into your new content. Look at its next or options[].next values to confirm which node currently follows it.For example, suppose you want to add a new scene between caveSkippedWithJewel and its ending. Currently that node ends the story immediately:
caveSkippedWithJewel: {
  id: 'caveSkippedWithJewel',
  type: 'narration',
  scene: 'beach-sunset',
  lines: [
    // ...existing lines
  ],
  ending: 'peacefulWalk',   // ← ends here today
},
Your goal is to remove the direct ending from this node, point its next to a new node you will create, and then let the new node carry the ending reference.
2

Add a narration node

Narration nodes are the simplest building block. Add a new entry to STORY_NODES with a unique camelCase id, a scene key that maps to an illustration, an array of lines, and a next pointing to whatever follows your new content.
// data/story.js — add inside STORY_NODES
jewelGlow: {
  id: 'jewelGlow',
  type: 'narration',
  scene: 'beach-sunset',          // reuses existing artwork
  lines: [
    '{firstName}, the {favoriteColor} {favoriteJewel} pulses once in your hand.',
    'A faint path of footprints leads away from the shoreline — {favoriteAnimal} tracks.',
    'You follow them until they disappear at the water\'s edge, smiling at the quiet mystery.',
  ],
  next: 'peacefulWalkEnd',        // points to another new node or an existing one
},
You can reuse any existing scene key to display a familiar illustration without adding a new PNG. Scene keys like beach-sunset, cave-entrance, or treasure-chamber are all valid here.
3

Add a choice node (if branching)

To give players a decision at your new narrative beat, add a choice node. Each entry in options needs a label (the button text), a value (an internal string), and a next (the node ID that branch leads to).
// data/story.js — add inside STORY_NODES
jewelChoice: {
  id: 'jewelChoice',
  type: 'choice',
  scene: 'beach-sunset',
  prompt: 'Do you follow the tracks?',
  options: [
    { label: 'Yes', value: 'yes', next: 'jewelGlow' },
    { label: 'No',  value: 'no',  next: 'peacefulWalkEnd' },
  ],
},
Choice nodes do not have a lines array or a next property — all flow comes through options[].next. Do not add both.
4

Add a new ending (if needed)

Endings are declared in the ENDINGS object near the top of data/story.js and are distinct from story nodes. Add a new key whose value contains id, name, description, and icon:
// data/story.js — add inside ENDINGS
mysteriousTracks: {
  id: 'mysteriousTracks',
  name: 'The Vanishing Trail',
  description:
    'You followed the jewel\'s glow and the animal tracks to the water\'s edge — and left the beach with a smile and a secret.',
  icon: '🐾',
},
Then reference it from the terminal narration node by setting ending: 'mysteriousTracks' instead of a next property.
5

Update the STORYLINES array

STORYLINES is the list that powers the Storylines reference page and the post-game replay prompt. Add one entry for each complete path through your new branch:
// data/story.js — add inside STORYLINES
{
  id: 8,                                   // increment from the last existing id
  name: 'The Jewel Keeper\'s Trail',
  path: [
    'Open Chest → Yes',
    'Follow Map → No',
    'Enter Cave → No',
    'Follow tracks → Yes',
  ],
  ending: 'mysteriousTracks',
},
The path array is display-only — it does not affect game logic. Write it as a human-readable summary of the choices that lead to this storyline.
6

Wire up the preceding node

Now connect the node that currently leads somewhere else to your new branch entry-point instead. For the caveSkippedWithJewel example, remove the ending property and add next:Before:
caveSkippedWithJewel: {
  id: 'caveSkippedWithJewel',
  type: 'narration',
  scene: 'beach-sunset',
  lines: [ /* ... */ ],
  ending: 'peacefulWalk',        // ← remove this
},
After:
caveSkippedWithJewel: {
  id: 'caveSkippedWithJewel',
  type: 'narration',
  scene: 'beach-sunset',
  lines: [ /* ... */ ],
  next: 'jewelChoice',           // ← add this
},
A narration node must have either next or ending — never both, and never neither. A node with neither will cause the game to hang on a Continue button that goes nowhere.
7

Add a new scene image key (if using new artwork)

If your new node uses a scene key that does not already exist in data/sceneImages.js, you must register it. See the Scene Artwork guide for the full process. In brief:
  1. Drop your PNG into assets/story-art/.
  2. Add an entry to the s object in data/sceneImages.js:
// data/sceneImages.js — inside the s object
'tracks': {
  src: a('animal-tracks-beach.png'),
  alt: 'Storybook illustration of mysterious animal tracks leading into the surf',
},
  1. Use 'tracks' as the scene value in your new node.
If you prefer not to add new artwork, any existing scene key (beach-sunset, beach-walk, animal-den, etc.) can be reused across multiple nodes without side effects.

Personalization Variables

Lines in any node type can include {placeholder} tokens that are substituted with the player’s answers from the opening questionnaire. The full set of available variables is:
VariableSource
{firstName}Player’s first name
{favoriteColor}Player’s favourite colour
{favoriteAnimal}Player’s favourite animal
{cartoonChar}Player’s favourite cartoon character
{favoriteJewel}Player’s favourite jewel
{formatted_time}Current time of day (formatted on render)
Use them freely in lines arrays of any narration node, as in the beachOpening node ("Hello, {firstName}!") and chestDiscovery ("you glance at your {cartoonChar} watch").

Using setState Flags

A narration node can write boolean flags into game state by including a setState object. These flags can be read later by other nodes or by conditional rendering logic. For example, chestOpened sets three flags at once:
chestOpened: {
  id: 'chestOpened',
  type: 'narration',
  scene: 'chest-open',
  lines: [ /* ... */ ],
  setState: { hasOpenedChest: true, hasJewel: true, hasMap: true },
  next: 'mapChoice',
},
Downstream nodes like caveEnteredWithJewel vs caveEnteredWithoutJewel exist as separate nodes precisely because the jewel state determines which descriptive text is appropriate. If your new branch needs to behave differently depending on whether the player opened the chest, create two parallel node variants and route to them from the appropriate choice options.
There is no runtime conditional-lines feature inside a single node — if you need divergent prose, the idiomatic pattern is two separate nodes with the same scene but different lines, connected by a prior choice.

Preventing Broken References

The node graph is validated at read-time only — the app will silently fail to advance if a next or options[].next value references a node ID that does not exist.
Never rename or delete an existing node ID without first updating every node that references it. Search data/story.js for the old ID string before making any removals. The affected properties are next on narration and riddle nodes, and options[].next on choice nodes.
A safe workflow when restructuring:
  1. Add the new nodes first.
  2. Update all next / options[].next references to point to the new IDs.
  3. Only then remove or rename old nodes.
  4. Run npm run dev and walk through the affected branch manually to confirm every Continue button and every choice button reaches a valid next node.

Build docs developers (and LLMs) love