Skip to content

The Engine API

This is the deep API reference for the JavaScript reference engine, @patterkit/runtime. If you just want it running, start with the JavaScript & web quickstart; for the cross-engine model, see the play loop. The native Unity, Unreal, and Godot ports mirror this same shape.

The Engine owns the world: the bundle, shared global state, and every running flow. A Flow is one independent position through the story: its own cursor, PRNG, and per-flow state. Many flows can run at once over the same data, sharing the shared state; that’s how you’d run two NPCs from one project. One live instance, one shared state: there’s no whole-story copy per branch.

import { Engine } from "@patterkit/runtime";
const engine = new Engine(bundle); // bundle = parsed .patterc JSON
const flow = engine.openFlow("main", { scene: "square" });
for (;;) {
const step = flow.advance();
if (step.type === "line") render(step.character, step.characterName, step.text);
else if (step.type === "text") narrate(step.text);
else if (step.type === "gameEvent") host(step.gameData); // your side-effects
else if (step.type === "choice") flow.choose(pick(step.options).id);
else if (step.type === "end") break;
}
new Engine(bundle, options?)

options (all optional): seed (the default per-flow PRNG seed), locale (the active locale; defaults to the bundle’s default), replayPromptOnChoose (replay a chosen option’s prompt as its first beat), and foreignScopes (host-owned property scopes). There’s also an rng override, but for resumable, save-safe runs use the built-in seeded PRNG.

engine.openFlow(id, { scene?, block?, seed? })

scene and block accept either a host-facing gameId/address or an internal id; both default sensibly (first scene, first block). Re-opening an existing id replaces it. Other engine methods: getFlow(id), flows(), closeFlow(id), and reset() (drop all flows and re-seed shared state).

  • flow.advance() → the next step (line, text, game event, choice, or end).
  • flow.advanceToStop(){ played, stop }: collects beats until the next choice or the end (handy when you render a whole exchange at once).
  • flow.getChoices() → the options of a pending choice.
  • flow.choose(id): pick an eligible option by id; the next advance() runs it. (Throws if there’s no pending choice, or the id is unknown or ineligible.)
  • flow.isEnded(), flow.currentScene: state for tooling that follows the story across scenes.
  • flow.reset(scene?, block?): forget this flow’s position, keep shared state.
step.typeFields
"line"id, text, character?, characterName?, direction?, gameData?, tags?
"text"id, text, gameData?, tags?
"gameEvent"id, gameData?, tags?: no text; the host-event beat
"choice"groupId, options: ChoiceOption[]
"end":

text is interpolated for you (against current property values), except on voiced lines, which are static. characterName is the localised display name; if a character has none, it’s absent and you fall back to the character token. A ChoiceOption is { id, prompt?, eligible, gameData? }: ineligible options are still present (greyed) unless they’re secret; pass id to choose(). tags is the beat’s accumulated author tags (its own plus every ancestor’s), absent when empty: see Tags at runtime.

Read and write game state from the host:

engine.getProperty("@gold"); // shared @patter globals
engine.setProperty("@gold", 10);
flow.getProperty("@scene.locked"); // @scene props live on a flow
flow.setProperty("@scene.locked", false);

@patter globals are reachable from the engine; @scene properties (and per-flow property values) are read and written on a Flow. The @patterkit/play-helpers package adds conveniences like setProperties(engine, { "@hp": 10 }).

MIT-licensed open source · Made by · patterkit.com