StoryletEngine for JavaScript
Storylet Studio publishes .storyworld bundles - precompiled storylet data ready to play at runtime. StoryletEngine for JavaScript is a JS / TypeScript distribution of the runtime engine that loads those bundles inside your web app, Node service, Electron app, or anywhere else JavaScript runs.
This page orients you: what storylets are, what the distribution gives you, and what's involved in shipping a Storylet Studio storyworld through a JS-based game or app. The distribution ships with full installation and API docs of its own, so treat this as the overview.
What's a storylet?
A small, self-contained narrative beat: a title, a description, optional conditions on game state, and one or more outcomes that change game state when chosen. The runtime works out which storylets are eligible right now, applies the chosen outcome, and lets the surrounding game read or write game state by canonical key. You author storyworlds in Storylet Studio and export them as .storyworld bundles that the engine loads.
A few terms that come up below:
- Storyworld - the published
.storyworldbundle. One JSON file holding every storylet, site, zone, act, property declaration, and game-data field your storyworld has. - Session - the live runtime over a storyworld. It owns the game-state context, fires events on change, and is the thing your code calls into.
- Site - a place in the storyworld (an inn, a glade). Storylets get "dealt" into a site's slots and stay there until played or evicted.
- Hand - the current set of storylets at a site - what the player can choose to do there right now. An empty hand means nothing to do at that site.
- Outcome - one choice on a drawn storylet. Calling
play(outcome)applies that outcome's state changes. An outcome can carry an optional availability condition (a "gated outcome"): eachDrawnOutcomehas anavailableboolean (false when its condition is unmet), andplay()throws if you play an unavailable outcome. Render unavailable outcomes disabled rather than hidden; the helperisOutcomeAvailable(world, ctx, storyletGameId, outcomeGameId)answers the same question on demand.
Want a deeper introduction to the model before you carry on? The Concepts page covers it in more detail.
What you need
- A modern JavaScript runtime - any of: a browser (Chrome / Firefox / Safari current), Node
>=20, Bun, Deno, Cloudflare Workers, Electron, Tauri. - A
.storyworldbundle from Storylet Studio - published from the authoring tool via Publish. - The StoryletEngine JavaScript zip, from download.storylet.studio under "Engine plugins".
What the distribution gives you
- Engine under
engine/. Multi-format build: ESM (dist/index.js) for modern bundlers (Vite, webpack, esbuild, Next.js), CJS (dist/index.cjs) for older Node / Jest without ESM config, and IIFE / browser-global (dist/index.umd.jsand minified.umd.min.js) for<script src=…>users with no build step. It picks the right entry automatically via the package'sexportsblock. - TypeScript definitions (
dist/index.d.ts) for IntelliSense in any JS / TS editor. - Source maps alongside every emitted file for stack-trace fidelity in dev.
- Full runtime API.
loadWorld,createSession, plus the session surface:draw,play(outcome),primeAllSlots,getSiteHand,advanceTurn, property read / write / subscribe, save / restore, action log. - Zero runtime dependencies. No DOM APIs, no Node-only APIs - the same source runs in every environment listed above.
- Dev-time inspector under
inspector/. The JS counterpart to the Unity / Unreal in-editor inspector panes.mountInspector(session, options?)renders a floating panel with State / Properties / Log tabs into any DOM container; no framework dependency, works inside React / Vue / Svelte / vanilla. The State tab's Slot Contents shows what's currently dealt at each site; the Properties tab edits any declared property inline with type-appropriate controls; the Log tab is the chronological action-log stream. Seeinspector/README.mdin the zip. - Bundled sample under
samples/riverbend-inn/. A single staticindex.htmlthat drives the full engine loop against a small Riverbend Inn storyworld - nonpm install, no build step, no framework. It mirrors the Riverbend Inn samples the Unity and Unreal ports ship, so all three are directly comparable. Serve theStoryletEngine/root via any static HTTP server and open/samples/riverbend-inn/to run it.
Install routes
Two options:
- Bundler project (Vite / webpack / Next.js / Remix / etc.). Unzip the
StoryletEngine/folder into your project'svendor/(or wherever you keep third-party deps), then add"@storylets/engine": "file:./vendor/StoryletEngine/engine"to yourpackage.json#dependencies.npm installresolves the file: path, andimport { loadWorld, createSession } from "@storylets/engine"works. - Vanilla browser script. Drop
engine/dist/index.umd.min.jssomewhere your HTML can reach and reference it with a<script>tag. The IIFE exposes a singleStoryletEngineglobal. No build step, nonode_modules.
(Browsers block fetch() from file://, so the vanilla route needs a static HTTP server in front of your HTML during development - python3 -m http.server, npx http-server, and so on.)
Quickstart (3 minutes)
The full quickstart lives in the package's own README. The short form:
- Pick an install route from above.
- Load your
.storyworldbundle. Fetch it, import it, read it from disk - whatever your host environment supports. The result is parsed JSON. - Compile + open a session.
const world = loadWorld(bundle); const session = createSession(world);returns a live runtime over the bundle. - Prime the slots.
session.primeAllSlots()fills every site with the storylets the engine reckons are eligible right now. Without it, sites are empty. - Read a hand at a site.
session.getSiteHand("site_inn01")returns the storylets currently dealt there - what the player can choose to do at that location. An empty array means nothing to do right now. - Play an outcome. When the player picks one, call
session.play(outcome)with the chosenDrawnOutcome. Then re-prime so evictions and new conditions resolve:session.primeAllSlots(). - Subscribe to updates.
session.subscribe(() => { /* re-render */ })fires whenever state mutates, so your UI can repaint.
The bundled samples/riverbend-inn/index.html walks through this end-to-end in vanilla DOM.
The inspector
A small "Storylet Engine" pill shows up in the bottom-right of any page that mounts the inspector. Click it to expand a debug panel with three tabs:
- State. Storyworld id / name / bundle version, turn, random seed, per-site slot contents (in the authored gameIds you recognise), Save / Restore for the current session.
- Properties. Every declared property across every scope, editable inline - checkboxes for booleans, dropdowns for enums, toggle chips for flags, text inputs for strings / numbers.
- Log. Chronological action-log stream (draws, plays, property changes, act transitions) with per-kind filter and Copy / Download / Clear.
Mount it during dev with:
import { mountInspector } from "@storylets/inspector";
mountInspector(session);
For production builds, gate the import so it tree-shakes out of the shipped bundle entirely:
if (process.env.NODE_ENV !== "production") { // or `import.meta.env.DEV` on Vite
const { mountInspector } = await import("@storylets/inspector");
mountInspector(session);
}
The inspector/README.md in the zip has the full surface plus theming hooks.
Cross-runtime parity
The JS engine is one of three ports - JavaScript, Unity, Unreal - that share a single behavioural contract and the same .storyworld bundle format. A storyworld authored in Storylet Studio runs the same way in all three. Any port can read a save produced by any other via the portable .storyworldstate file envelope - the same files the authoring tool's Simulate view reads and writes.
Where to next
- The distribution's own
README.md- install routes, full API surface, step-by-step walkthrough of the draw / play loop, tested-on matrix. - The bundled
inspector/README.md- debug-panel tour and theming hooks. - The bundled
samples/riverbend-inn/README.md- walkthrough for the Riverbend Inn sample. - Concepts - storylet, site, zone, act, slot, hand, outcome - the model in detail.
- For Game Developers - the overall publish-and-load workflow, how the engines compare, and licensing / source.
- Simulate - what running a storyworld looks like in the authoring tool's reference UI; the JS distribution exposes the same draw / play primitives.
- Game Data - the per-storylet custom fields a player shell typically reads to drive presentation (UI, dialogue, audio).
- StoryletEngine for Unity - the sibling Unity port.
- StoryletEngine for Unreal - the sibling Unreal port.