Storylet Studio

Core Concepts

This page explains the building blocks of a Storylet Studio storyworld. Understanding these terms makes authoring much easier.


The big picture

A storyworld is a self-contained interactive narrative. As an author you define the geography (zones and sites), the state (properties), and the story beats (storylets). Storylet Studio handles which beat is appropriate at any moment; the player's choices and the world state determine what they encounter next.


Storylets

A storylet is a single narrative beat — a scene, an encounter, a moment. It is the atomic unit of authoring.

Every storylet has:

  • Condition — an expression that must be true for the storylet to be eligible. If absent, the storylet is always eligible.
  • Priority — controls ordering when multiple storylets are eligible. Higher priority = appears first.
  • Redraw — what happens after the storylet is played: always (keep returning), never (one-shot), or a cooldown number (re-eligible after N more turns).
  • Content — the title and prose description shown to the player.
  • Outcomes — the choices available to the player. Each outcome has its own title and description (shown as a choice label and feedback), plus world property changes applied when that outcome fires.
  • Tags — classification labels used in conditions (count_played_tag, rounds_since_tag) and filtering. (Storylet tags do NOT participate in site_has_tag() — that operator reads the site's own tags, not the storylets running there.)

Properties

Properties are named values that track the state of the world. Conditions are evaluated against them; outcomes can mutate them (world-scoped only — see below).

Property types:

Type Example Notes
boolean has_visited_mill = true
number reputation = 7, morale = 0.85 Whole numbers and decimals share one type — there's no separate integer / float distinction. The value's appearance tells them apart in the editor.
string player_name = "Asha"
enum season = autumn (one of: spring, summer, autumn, winter) Exactly one value active at a time
flags quests = [blacksmith_met, tree_task] (any of: blacksmith_met, tree_task, herbs_found) Any number of named flags active simultaneously

Property names must use only letters, numbers, and underscores (e.g. visit_count, season_state). No spaces.

Property scopes

Properties exist at one of six scopes. Each scope has a distinct expression syntax:

Scope Where defined Expression syntax Writable by outcomes
World Properties page @propName or @world.propName Yes — full runtime support
Deck Deck editor — Properties tab @deck.propName No — read-only in expressions
Act Act editor — Properties tab @act.propName Yes — when the storylet belongs to that Act
Zone Storymap zone editor @zone.propName Yes — pass zoneGameId or siteGameId to play()
Site Storymap site editor @site.propName Yes — pass siteGameId to play()
Story (system) Auto-synthesised @story.act (current Act gameId) Yes — via outcomes; definition is locked

World properties are global — visible in any draw and writable by any outcome. The authoring tool stores them with a stable internal prop_xxxxxx document ID, but you don't see or use that ID anywhere — expressions reference world properties by name (e.g. @season). World property names are unique within the storyworld, so the short form is unambiguous.

Deck properties are local to a single deck. They are declared in the deck's Properties tab and are visible to storylets in that deck. A good use is per-deck state that tracks progress through a narrative arc independently of the rest of the world (e.g. whether the player has seen the opening scene for a particular quest). Reference them in conditions as @deck.propName.

Zone properties are local to a zone. They are declared in the Properties tab of the zone's side panel on the Storymap Plot page, and are visible in draws scoped to that zone or its sites. Use them for state that is inherently regional (e.g. the current weather in a specific area of the map). Reference them in conditions as @zone.propName.

Site properties are local to a single site. Declared in the Properties tab of the site's side panel on the Storymap Plot page, and visible only in draws at that site. Use them for state that belongs to one specific location (e.g. whether the mill has been searched). Reference them in conditions as @site.propName.

Scoped properties (deck/zone/site) are initialised from their declared default values at the start of each session. Outcome changes can target scoped properties using @scope.propName in the target field — enter a value expression the same way as world properties.


Geography: zones and sites

A storyzone is a named logical region of the world (e.g., "The Forest", "The Village"). Zones can declare their own local properties.

A storysite is a specific location within the world (e.g., "Old Mill", "The Tavern"). Sites have:

  • A slot count — how many storylets can be active at this site simultaneously.
  • Their own local properties.
  • A list of storylets that can appear here.
  • Free-form tags (edited from the Tags tab on the site's right-hand pop-out in the Plot editor). Test them in expressions with site_has_tag("name") to gate world- or zone-scoped storylets selectively per site (e.g. tag certain sites indoors, then write not site_has_tag("indoors") on a weather storylet so it skips covered locations).

A site can belong to one or more zones — zones are advisory groupings, not ownership. A site placed inside two overlapping zones belongs to both. A site not inside any zone is simply unassigned.


Storymap

The storymap is split into three separate pages, all accessible from the Storymap group in the left sidebar. All three share the same pan/zoom position — switching between them keeps your place on the map. Use the floating Reset View button (top-left of the canvas) to return to the saved start position.

All three storymap canvases share the same canvas mechanics as the Whiteboards: pan by dragging empty canvas (or one-finger pan on touch), zoom with the scroll wheel (or two-finger pinch on touch), a faint theme-aware gridded background that tracks pan + zoom, and a ? button in the toolbar listing the relevant gestures.

Design

The design canvas is where you lay out zones and sites visually. It handles map structure only — naming, geometry, colours, slots, zone membership, and game-data values. Anything that gates which storylets are eligible (zone/site conditions, scoped properties, site tags) lives on the Plot page below, since those are story content rather than map structure.

Zones are drawn as colour-coded polygons. To create one, click Draw Zone, click to place each point of the polygon, then click Close & create (or press Enter) when done. The zone is created with an auto-generated name which is immediately selected for editing. Press Escape at any time to cancel.

Sites are placed with the Place Site tool — click anywhere on the canvas to drop a pin. The site is named automatically and its name field is highlighted for immediate editing. A site is auto-assigned to every zone whose polygon contains it; dragging a site to a new position updates zone membership automatically.

Pins are informational markers — landmarks, NPC locations, plot waypoints. Place one with the Place Pin tool (the diamond icon). Pins display as a small amber diamond with the name underneath. Each pin has a name, a Game ID (auto-generated from the name; the host game references the pin via this ID), a Description (short text shown when you hover the pin on the Design or Plot map), and free-form Notes. Like sites, pins join every zone whose polygon contains them and follow zone moves when you confirm at the prompt. Pins do not host storylets — they exist purely as map context and as queryable references for the host game (getAllPins, getPinsInZone).

Background images can be added via Add background. Each image has independent position, scale, opacity, lock, and visibility controls.

Polygon editing (select a zone then select it in the detail panel):

  • Drag a point (●) to move it.
  • Click a midpoint (○) on an edge to insert a new point.
  • Click a point to select it, then press Delete to remove it (minimum 3 points).
  • Drag the centre handle (✥) to move the whole zone; if the zone contains sites or pins you are asked whether to move that content along with it.

Keyboard shortcuts:

  • Enter — confirm zone creation (when ≥ 3 points drawn)
  • Esc — cancel zone drawing
  • Delete / Backspace — delete the selected point, zone, or site (when no input field is focused)

Plot

A read-only canvas for inspecting and managing storylet assignments without leaving the map. Click any zone or site pin to open its side panel.

  • Filters — narrow the storylet list by deck or tag.
  • Condition passing filter — shows only storylets whose condition evaluates true against current property defaults.
  • Click a zone or site pin to see which storylets are assigned there, add new assignments, or remove existing ones.
  • Site badges show the count of assigned storylets (filtered by any active filter).

The Plot side panel for a selected zone or site has tabs for Storylets, Condition, Properties, and — for sites — Tags:

  • Condition is the gating expression for the zone or site. If it evaluates false, no storylets are drawn for any draw scoped to that zone (or sites within it) / that site. Use @propName for world properties, @zone.propName / @site.propName for the zone's or site's own scoped properties.
  • Properties edits the zone- or site-scoped state vars. Reference them in expressions as @zone.propName or @site.propName.
  • Tags (sites only) is a free-form list of labels tested by site_has_tag("name") in storylet conditions. Tag a site indoors once, then any number of weather storylets can write not site_has_tag("indoors") to skip it.

Conditions, scoped properties, and site tags are all considered story content: they're wiped by Clear All Story Content and only copied by Duplicate Storyworld when Include all story content is ticked. The map structure (zones, sites, polygons, pins, backgrounds) is never wiped and is always copied.

Simulate

Runs the draw/play loop live on the map. The simulation builds automatically when the page loads.

  • Click a site pin to navigate to it and see its current hand (storylets currently in slot). Navigating costs 1 turn.
  • Click a storylet card to select it, then choose an outcome. The outcome is applied, slots are re-primed across all sites, and the turn counter advances.
  • Storylets not in the current hand are listed in the right sidebar as Not in hand, with a reason (cooldown remaining, condition not met, or lower priority / slot full).

Simulation controls (pinned at the bottom of the right sidebar):

  • Turn counter — shows the current game turn.
  • Reset sim (|◀) — restarts the simulation from scratch.
  • Advance turn () — advances the clock by one turn and reprimes all slots, without playing a storylet.
  • Save state — enter a name and save the current world state as a named snapshot; the date and time are prepended automatically.
  • Restore state — load a previously saved snapshot.
  • Log — expand an inline log of every draw, eviction, and play event, with Copy and Download options.

Draw and play

The draw is when Storylet Studio evaluates all eligible storylets and returns an ordered list (the hand). The host (the game or the Storymap Simulate page) presents the hand to the player.

Playing a storylet means applying one of its outcomes to the world state. After play:

  • The outcome's world property mutations are applied.
  • The storylet's redraw cooldown is updated.
  • Empty slots at active sites are refilled.

The card-dealing analogy

A useful mental model for how storylets flow through the world:

  • Each storylet is a card.
  • Each site holds a hand of cards (up to its slot count).
  • The draw deck is the pool of storylets not currently in any hand — those that are eligible given the current world state (season, properties, cooldowns, etc.).

When a slot becomes empty — because a card was played — that site draws a replacement from its eligible pool. The eligible pool is re-evaluated fresh at that moment: conditions are checked, cooldowns are respected, and only storylets scoped to that site (or its zone, or the whole world) are considered.

Two important nuances:

  1. Hands persist. A site keeps its hand between turns and only tops up empty slots — the remaining cards stay put until played.
  2. Each site draws from its own pool. There is no single shared deck rebuilt at the start of a turn. Each site's eligible pool is computed independently when it draws. A zone-scoped storylet already sitting in one site's hand is not available to another site in the same zone until it has been played and its cooldown cleared.

Conditions and expressions

See also: Writing Conditions and Outcome Values — a tour of the pill editor used for every condition and outcome change in the authoring tool.

Conditions are written as expressions. All property references use @ as a prefix:

@season == autumn and @reputation > 3

Scoped properties use @scope.propName:

@deck.troubled_seen == false
@zone.weather != storm
@site.searched == true

You can also write @world.season to be explicit about world scope — it is equivalent to @season.

Outcome change targets work the same way: type @propName or @scope.propName in the target field, then enter the value expression.

Syntax notes:

  • @season — world property reference (case-insensitive: @Season and @season are the same)
  • @deck.visited, @zone.weather, @site.searched — scoped property references (also case-insensitive: @zone.Weather and @zone.weather are the same)
  • @world.season — explicit world scope, equivalent to @season
  • == or = — equality; != or <> — not equal
  • Enum values do not need quotes: autumn and "autumn" are both valid
  • Flags properties use check_flags() — see below

Built-in functions:

Function Returns Meaning
site_has_tag("indoors") boolean True if the site this storylet is running on is tagged "indoors". Use this to apply a blanket world- or zone-scoped storylet selectively per site (e.g. tag certain sites indoors then write not site_has_tag("indoors") on a weather storylet).
random(1, 6) integer A random whole number from a to b inclusive — seeded, so runs are reproducible. random(1, 6) is a six-sided die; random(1, 100) <= 30 is a 30% chance.
count_played_tag("npc") integer How many times any storylet tagged "npc" has been played
rounds_since_tag("bard") integer Clock ticks since any storylet tagged "bard" was last played; 9999 if never played
check_flags(@quests, +a, -b) boolean True if every listed flag satisfies its constraint: +flag requires the flag to be set, -flag requires it to be unset. All constraints must pass (AND logic).

Flags conditions and changes

flags properties hold a set of named flags active at the same time. Use check_flags() to test which flags are set or unset:

check_flags(@quests, +blacksmith_met)                    # one flag must be set
check_flags(@quests, -herbs_found)                       # one flag must be unset
check_flags(@quests, +blacksmith_met, -herbs_found)      # both at once (AND)

+flag requires the flag to be set; -flag requires it to be unset. List as many constraints as you need — they're all ANDed. Both the storylet table and the condition editor render the call compactly as ⚑ quests ( +blacksmith_met , −herbs_found ) so you can scan and edit flag checks at a glance — clicking the ⚑ opens the same delete / NOT-wrap menu the function-name pill used to.

For outcome changes, the editor presents the flag pills directly - one chip per declared flag, click to cycle no-change -> set (+) -> clear (−) -> no-change. The row stores the change as a set_flags() expression internally, but you don't need to think about that:

set_flags(@quests, +tree_task, -tree_singing)

This sets tree_task and clears tree_singing on @quests. Flags properties can be written by outcomes at any writable scope (world, act, zone, site) - deck-scoped properties stay read-only as always.


Location-based storylet assignment

Storylets can be constrained to specific zones or sites using the Location tab in the storylet editor. The tab shows a three-level tree:

  • World — no constraint; the storylet is eligible in any draw. Active when nothing else is checked.
  • Zone — the storylet is eligible in draws scoped to that zone (and all sites within it).
  • Site — the storylet is eligible only in draws scoped to that specific site.

Sites with no zone are grouped under a No Zone header at the bottom of the tree.

Mutual exclusion: checking a zone automatically unchecks any of its sites that were individually selected (a zone assignment already covers them).

Autosave: location checkbox changes are saved automatically — no need to click Save after ticking a zone or site.

You can also assign storylets directly from the storymap editor: the zone and site detail panels each include a search field to find and add storylets by name or gameId.


Decks

A deck is an authoring folder for organising storylets. Each deck has a colour — a vivid hex colour chosen when the deck is created. The colour appears as a border and background tint on deck cards, storylet cards, and storylet list rows. The colour is authoring-only and has no runtime effect.

Decks can carry an optional condition. When a deck condition is set and evaluates to false, every storylet in that deck is excluded from the current draw. This is the recommended way to gate a whole quest branch: put those storylets in a dedicated deck and write a single condition on the deck itself.

Decks can also declare deck-scoped properties in the deck's Properties tab. These properties are local to the deck and can be referenced in conditions and priority expressions using @deck.propName. They are initialised from their declared defaults.

Every storyworld auto-creates one deck named "General" marked as the default deck. The default deck is where new storylets land when you don't put them anywhere else, and it acts as the safe destination for storylets when another deck is deleted. You can rename the General deck or designate any other deck as the default from the deck's detail form. The last remaining deck cannot be deleted — every storyworld must have at least one deck for storylets to live in.


Acts

An Act is a phase of the story. Each storylet belongs to exactly one Act. At runtime, only storylets in the current Act are eligible to draw, with one exception: storylets in the Anytime Act are always eligible regardless of phase.

Every storyworld has one Anytime Act, auto-created and undeletable. Anytime is the catch-all bucket for storylets that should be drawable throughout the whole story — ambient flavour, recurring NPCs, the "spine" of the storyworld. The Anytime Act cannot itself be set as the current Act; it's a backdrop that's always on.

The current Act is tracked by a system property @story.act. Outcomes change it like any other enum write, and the change ripples through every subsequent draw — storylets in the new Act become eligible, storylets in the old Act stop appearing.

Acts also support Act-scoped properties declared on the Act itself. Reference them in conditions or priority expressions as @act.propName. They behave like deck-scoped properties: only injected when evaluating storylets that belong to that Act.

The Starting Act of a storyworld is the value @story.act initialises to on a fresh playthrough. On a brand-new storyworld with only the Anytime Act, the Starting Act is Anytime — the bootstrap state. The first time you add a non-Anytime Act, the Starting Act is automatically promoted to it. After that, you can change it manually from the Storyworld settings whenever you reorder your story.


Storylet views: Table, Grid, Whiteboard

Every surface that lists storylets, decks, or acts (the All Storylets page, the per-deck and per-act detail panes, the Decks page, the Acts page) lets you switch between three view modes via the icon toggle in the top-right of the surface:

  • Table — a dense row-per-storylet view with sortable columns. Best for bulk inspection, editing tags, or filtering across hundreds of storylets.
  • Grid — a card grid with the deck colour as the card's accent. Best for visual triage when card titles are the main thing you scan by.
  • Whiteboard — a free-pan-and-zoom canvas where each storylet (or act, or deck) is a movable rect. Best for spatial layout: arranging storylets in clusters, sketching the shape of an act's beats, or seeing decks side-by-side at a glance.

The toggle is app-wide: choose Whiteboard once and every list surface that supports it switches over. A few tight side panels (e.g. the storyworld overview) fall back to Grid because there isn't room for a real canvas - your preference is preserved and reapplied when you return to a full surface.

Sort modes

Two sort modes apply across all three views, picked from the toolbar next to the view toggle:

  • Free — the order you've arranged things in. In Whiteboard view this means the X-position of cards on the canvas. In Grid and Table it means a manual drag-to-reorder. Free order is per-surface (each deck, each act, the All Storylets surface, etc.).
  • Alphabetical — sorted by storylet/deck/act name, with leading articles ("The", "A", "An") discounted so "The Mill" and "Mill" sort together.

When the whiteboard's Sync sort toggle is on (the default for per-deck and per-act surfaces), the canvas's left-to-right order drives the Table and Grid sort. Drag a card on the canvas to a new column and the same storylet jumps to a new row in the Table. This is one-way: while sync-sort is on, you can't drag-to-reorder in the Table (the canvas owns the order). Toggle sync off in the whiteboard toolbar to reorder from the Table again.

The whiteboard canvas

All whiteboard surfaces share the same canvas mechanics:

  • Pan — drag empty canvas, or one-finger pan on touch.
  • Zoom — scroll wheel, or two-finger pinch on touch.
  • Show All — the toolbar's expand-arrows icon frames every object in view.
  • Snap to grid — the toolbar's grid icon (storylet whiteboards only) snaps drops to an 8px lattice. Off by default.
  • Grid background — every whiteboard and storymap canvas shows a faint, theme-aware gridded background that tracks the canvas as you pan and zoom. Purely a spatial reference; never affects gameplay.
  • ? — every canvas toolbar has a ? button listing the relevant gestures and shortcuts for that surface.

Selecting and arranging cards

On a storylet whiteboard:

  • Click a card to open its editor.
  • Shift-click to add or remove from the selection.
  • Shift-drag (or right-drag) on empty canvas to marquee-select multiple cards.
  • Cmd/Ctrl-A selects every card on the current whiteboard.
  • Drag a single card to move it. With multiple cards selected, dragging any one moves all of them.
  • Right-click (or long-press on touch) for Move-to-deck / Move-to-act / Arrange / Delete. Right-clicking a card already in the selection acts on the whole selection; right-clicking outside the selection acts on just that one.
  • Arrange offers Horizontal strip, Vertical strip, Grid (4 / 5 / 6 / 10 columns), Align (left / right / top / bottom / centre H / V), and Distribute (horizontal / vertical).

Acts master and Decks master

The Acts and Decks pages use the whiteboard view for a top-level "master" canvas:

  • Acts master — a horizontal timeline of act rects laid out left-to-right by their order, with an Anytime region below. Drag an act header to reorder; the order ripples to the Acts table. Drag a storylet card from one act rect into another to move it between acts. Anytime auto-tracks the strip's width and grows with its contents.
  • Decks master — a free canvas of deck rects. Each rect auto-fits to its contents. Drag rects around manually, or click Auto-pack to snap them all back to a tight grid. Overlapping rects show a soft amber dashed warning so you can spot collisions.

In both masters, every deck/act rect contains its actual storylet cards rendered at the same body-local positions they have in the per-deck or per-act drilldown - so the master is always a faithful overview rather than a separate layout.

Focus mode (drill-in)

Single-click any deck rect or act rect (or the Anytime label) on a master canvas to drill in. The page swaps to that single deck's or act's whiteboard, fitted to content so you can immediately see all of it. Esc (with the editor closed) returns to the master; the browser back button works too. Esc with the right-pane editor open closes the editor first, leaving the drill-down on screen.

The same drilldown is available via the Whiteboard tab on a deck's or act's detail page, so you reach it from either direction.


Location conditions

Zones and sites can each carry an optional condition. A location condition gates the entire draw at that location:

  • Zone condition — if the condition evaluates to false, no storylets are drawn for any draw scoped to that zone, including draws at sites within it. Use this to mark a whole region as inaccessible until some world-state is reached (e.g. @dungeon_unlocked == true).
  • Site condition — if the condition evaluates to false, no storylets are drawn at that site. Checked after the zone condition — if either fails, the draw is empty.

Location conditions are set in the Condition tab of the zone or site editor in the Storymap design view.

Evaluation order for a site draw:

  1. Zone condition(s) — if any zone containing the site fails, the draw is empty.
  2. Site condition — if the site's own condition fails, the draw is empty.
  3. Deck conditions — for each remaining storylet, its deck's condition (if any) is checked.
  4. Individual storylet conditions.

Properties page

The Properties page (accessible from the Content group in the left sidebar) has two tabs:

All Properties — a master view across all four scopes. Shows every world, deck, zone, and site property in the storyworld with its scope, type, and purpose. Scope badges colour-code each entry. Click a property name to navigate directly to its editor; click the declaring entity (zone name, deck name, etc.) to navigate to that entity's properties list.

World Properties — create, edit, and delete world-scoped properties. These are the global variables of the storyworld.

Deck, zone, and site properties are managed in the editor for each entity (deck editor Properties tab; storymap zone/site detail panel Properties section), not on this page.


Management

The Management group in the left sidebar holds three sub-pages for maintaining the storyworld:

  • Storyworld — duplicate, clear, delete, and import/export operations on the storyworld as a whole (the rest of this section).
  • Game Data — author-defined custom metadata fields. See the dedicated Game Data page.
  • Settings — storyworld-level project settings.

Duplicate Storyworld

Creates an independent copy of the current storyworld in your workspace. The new name is pre-filled as {original name} Copy {date/time} and can be edited before confirming.

The map structure — zones, sites, pins, and background images — is always copied. Zone and site conditions are only copied when story content is included, because they may reference world properties that would otherwise not exist in the copy.

Check Include all story content to also copy all decks, storylets, and world properties. Property references in conditions and outcomes are automatically remapped to the new property copies, so the duplicate is a fully self-contained storyworld. A progress bar shows the number of documents written as the operation runs.

Game Data: field definitions are always copied (so the new storyworld has the same custom-metadata schema). Per-entity values are only copied when story content is included.

Typical uses:

  • Making a backup before a large structural change.
  • Creating a variant of an existing storyworld.
  • Starting a new storyworld on the same map structure without carrying over story content.

Clear All Story Content

Wipes everything narrative from the storyworld while leaving the map intact. Properties, decks, storylets, and outcomes are deleted. Zone and site conditions are cleared, and per-entity Game Data values are wiped from zones, sites, and pins. Game Data field definitions are kept, as is the map structure (zones, sites, pins, background images), so the storyworld is ready to be repopulated with new narrative content.

Game Data

Author-defined custom metadata fields on storylets, zones, sites, and pins — see the dedicated Game Data page for the full walkthrough. Use this when your host game needs implementation-side data attached to entities (e.g. a dialogue script to run when a storylet plays, or an audio cue tied to a site). Game Data is not visible to storylet conditions or outcomes — if a value needs to drive narrative logic, model it as a Property instead.

Export / Import Storylets

Export your decks and storylets to a JSON file for backup, version control, or migration between storyworlds.

Export: Choose a specific deck or all decks, then click Download JSON.

Import: Select a previously exported JSON file. The tool computes a diff against the current storyworld and shows a summary: new decks, storylets to add, storylets to update, and storylets to delete. Review the summary and click Apply to confirm, or Cancel to discard.


The .storyworld bundle

When you publish a storyworld, the authoring tool generates a .storyworld file. This is everything the runtime needs:

  • All world properties (name, type, default value)
  • Decks that carry a condition or deck-scoped properties
  • All zones and sites, including their scoped properties and conditions
  • All storylets (conditions, outcomes, content) — authoring metadata stripped

The player shell loads this bundle and runs it entirely in the browser.

Publish options

The Publish page has a small set of toggles that control what travels in the bundle. Settings are remembered per storyworld.

  • Include map geometry (off by default): when ticked, each zone's polygon vertices and each site's / pin's canvas position are included in the bundle. Useful when the host game wants to render the storymap itself rather than rely on the standalone player. Most storyworlds leave this off.

Hosting the runtime in your own game

If you're embedding @storylets/engine in a game shell rather than using the standalone player, every API entry point speaks the gameIds you authored — the URL-safe slugs you see and edit on the Properties, Storymap, and Storylets pages. You never deal with the internal prop_xxxxxx / site_xxxxxx document ids.

There are three host-facing surfaces, layered:

  • Pure functional engine (loadWorld, createContext, draw, play, primeAllSlots, evaluateSiteCondition, getSiteHand, getPinsInZone, saveContext / restoreContext). Every call takes/returns the storyworld's gameIds (e.g. play(world, ctx, "mill-first-visit", "visited", { siteGameId: "mill" })). Use this if you want to manage WorldContext yourself.
  • Structured PropertyAccess (getProperty, setProperty, setFlags, etc.) — read or write any property at any scope from host code, with full validation. Useful when an inventory pickup, a UI button, or a save-game restore needs to update a property without authoring a storylet+outcome whose only job is to set a value.
  • RuntimeSession (createSession(world)) — a stateful wrapper that owns the live WorldContext, dispatches change events to subscribers when an outcome (or any other engine call) writes a property, and offers an ergonomic session.state.world.reputation = 5 proxy in addition to the structured form.

Player saves are durable across author renames: rename a property, zone, site, deck, act, storylet, or outcome's gameId and re-publish, and existing player saves keep working — the runtime stores values by stable internal id under the hood, and every public API translates id ↔ gameId at the boundary.

A player shell or game-engine plugin (like StoryletEngine for Unreal) wraps the runtime in an engine-friendly surface. The full engine API is engineering-facing; if you're building one of those, the relevant material is in the developer documentation.