Storylet Studio

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 .storyworld bundle. 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"): each DrawnOutcome has an available boolean (false when its condition is unmet), and play() throws if you play an unavailable outcome. Render unavailable outcomes disabled rather than hidden; the helper isOutcomeAvailable(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 .storyworld bundle 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.js and minified .umd.min.js) for <script src=…> users with no build step. It picks the right entry automatically via the package's exports block.
  • 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. See inspector/README.md in the zip.
  • Bundled sample under samples/riverbend-inn/. A single static index.html that drives the full engine loop against a small Riverbend Inn storyworld - no npm 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 the StoryletEngine/ 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's vendor/ (or wherever you keep third-party deps), then add "@storylets/engine": "file:./vendor/StoryletEngine/engine" to your package.json#dependencies. npm install resolves the file: path, and import { loadWorld, createSession } from "@storylets/engine" works.
  • Vanilla browser script. Drop engine/dist/index.umd.min.js somewhere your HTML can reach and reference it with a <script> tag. The IIFE exposes a single StoryletEngine global. No build step, no node_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:

  1. Pick an install route from above.
  2. Load your .storyworld bundle. Fetch it, import it, read it from disk - whatever your host environment supports. The result is parsed JSON.
  3. Compile + open a session. const world = loadWorld(bundle); const session = createSession(world); returns a live runtime over the bundle.
  4. Prime the slots. session.primeAllSlots() fills every site with the storylets the engine reckons are eligible right now. Without it, sites are empty.
  5. 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.
  6. Play an outcome. When the player picks one, call session.play(outcome) with the chosen DrawnOutcome. Then re-prime so evictions and new conditions resolve: session.primeAllSlots().
  7. 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.