Engine integrations
A shipping game loads data/ — the same files the editor writes — and
executes them with a runtime that implements
the published contract. There is no export
step; "integration" means reading files and honoring semantics, and the
conformance suite tells you when you've honored them.
Godot (GDScript) — official port
The parlance-gdscript addon executes Parlance JSON natively in Godot 4:
- Install: copy
addons/parlance/into your project. Static classes — no plugin to enable, no autoloads. - Immutable state: every entry point returns a new state, never mutates input — save/load and rewind stay trivial.
- Conformance-verified against the v0.15.0 vectors: all 207 passing, every
family ported, including quest resolution, progression and
nextContinuations.
Unity (C#) — official port
The parlance-unity package provides a
pure C# .NET runtime for Unity projects:
- Install: Add the git repository URL via the Unity Package Manager (UPM). It is a framework-agnostic C# library without
MonoBehavioursingletons. - Plain-dictionary API: the project, dialogues and every result are
Dictionary<string, object>; state is aStateclass ofDictionaryandHashSetfields, deep-copied on every change. The package ships no loader or JSON parser: readdata/with your own (for example Unity's Newtonsoft package) into plain dictionaries and lists. - Conformance-verified: an NUnit suite runs the v0.15.0 vectors headless with
dotnet test, outside Unity — all 207 passing, every family ported, including quest resolution, progression andnextContinuations.
Unreal Engine (C++) — parked at v0.9.0
The parlance-unreal plugin integrates Parlance natively into Unreal Engine using Core C++ types:
- Install: Drop the plugin into your project's
Plugins/folder and rebuild. - Blueprints First: The core runtime logic is exposed via
UBlueprintFunctionLibrarywrappers. You can drive your entire narrative system visually without writing C++. - Conformance-verified against v0.9.0 only: Uses the native Unreal Automation Testing framework (
BEGIN_DEFINE_SPEC) to run JSON vectors natively against the C++ code. 136 of the v0.9.0 vectors pass, 0 fail; quest resolution and progression were never ported.
Parked. This port is not being moved forward for now: running Unreal in CI costs more than it is worth at present. It resolves the pre-0.14 character ladders rather than dialogue offers, does not apply check modifiers, and lacks the 0.15 dialogue shapes (line-only gates, fallback and locked choices, the engine effect). Use it as a starting point, not as a conforming runtime, or use the GDScript or Unity port.
TypeScript — the reference runtime
@orbitope/parlance-runtime
is the reference implementation, published on npm from v0.16.0: the same code
that powers the editor's playtest and
share builds, extracted into its own
package. It is MIT-licensed, so it can ship inside any game, commercial
ones included. Zero dependencies and pure functions — no filesystem, no DOM,
no clock — so it runs in browsers, Node, Deno, Bun, Electron and embedded JS
engines.
npm install @orbitope/parlance-runtime
The loader reads no files itself: hand it every .json under data/, keyed
by its data-relative path (from fs, fetch, an asset bundle — whatever your
platform has), then step a dialogue:
import { readFileSync, readdirSync } from "node:fs";
import { loadProjectFromFileMap, createDefaultState, stepDialogue, chooseChoice, applyEffects } from "@orbitope/parlance-runtime";
const paths = readdirSync("data", { recursive: true, encoding: "utf-8" }).filter((p) => p.endsWith(".json"));
const project = loadProjectFromFileMap(new Map(paths.map((p) => [p.replaceAll("\\", "/"), readFileSync(`data/${p}`, "utf-8")])));
const dlg = project.dialogues["dlg_gatekeeper_intro"]!;
let state = createDefaultState(project);
const step = stepDialogue(dlg, dlg.entry, state, project); // may skip a gated node
state = applyEffects(step.onEnterEffects, state, project);
console.log(step.node.text, step.visibleChoices.map((c) => c.text));
const out = chooseChoice(dlg, step.node.id, step.visibleChoices[0]!.id, state, project, Math.random);
out.newState and out.nextNodeId carry the loop on. The
integration guide
in the spec repo has the full loop — locked choices, cutscenes, which dialogue
a character offers next, quest resolution and saves.
Porting to any other engine
The path every port follows:
- Load the files. One JSON entity per file (skills/variables/items/ portraits as flat registries); file name = entity id. Any JSON parser is the whole "SDK".
- Implement the contract. Published in
parlance-spec, the runtime contract defines each function —evaluate,applyEffect,resolveCheck,stepDialogue,resolveCharacterDialogue,resolveQuests— including how dice consume the injectedrng()(one call per die, in order), clamping rules, and the edge cases where ports usually drift. The contract doesn't fix an RNG algorithm; you inject your own. - Run the conformance vectors. Machine-readable given-state/expect-output cases per function. Green vectors = correct port; the scoreboard is your integration test forever after.
- Mind the host's half. Your engine owns cutscene playback, calling quest resolution after state transitions, persistence, and presentation — the contract marks each boundary explicitly.
Contract, vectors, and schemas are all MIT-licensed, so a port of any license — including closed-source commercial — is fine.
Asset bindings
An optional data/bindings/<profile>.json per engine or build target maps
portrait ids, VO keys and cutscene ids to asset paths (the format is
schema/binding.schema.json in the spec). No runtime function
reads bindings: your own game code or build pipeline looks the ids up.
Only the Python reference validator
checks them, as BIND warnings for an asset that's used but unbound or bound but
missing. The editor and parlance ci-check don't read data/bindings/, so run
validate.py in CI if you rely on bindings. Whether an engine port loads a
binding profile for you is up to that port; check its own README. Neither the Godot
nor the Unity port does today.
Coming from another tool
Importers ship as MIT skill bundles,
separate from the editor: each is an agent skill in the open SKILL.md format that drives a plain Python
parser and a Python check script, which you can also run yourself. There are seven: Yarn
Spinner, ink, Twine (Harlowe), Twine (SugarCube), ChoiceScript, Arcweave and Ren'Py.
To run a migration:
- Copy the importer skill from the
parlance-specrepository into your agent's skills directory — for example.claude/skills/for Claude Code or.agents/skills/— together with the sharedimporters/lib/folder its commands run (lib/parse_<format>.py,lib/check.py). Coding agents that supportSKILL.mdload it natively; with any other agent, point it at the skill'sSKILL.mdas its instructions. - Instruct your agent to run the import against your source files.
- The agent reads your script, emits Parlance JSON, and then checks every string in the output against the source, byte for byte.
Conversion is not authorship: if a line came out different from how you wrote it, that's a bug, not tidying.
What they will not do is guess. A construct Parlance can't carry is reported by name, with its source line and the reason — never approximated, never quietly dropped. A migration that reports three declared losses is a good outcome honestly stated; one that came out clean because the awkward lines were reworded is a failure wearing a success.
Five real stories by other people have been migrated this way, each published with the author's original beside the result and a report of what was lost: The Intercept (ink), Cyberharcèlement (Yarn Spinner), Not Weird. Queer (Harlowe), Aesthetics Over Plot (SugarCube) and Ren'Py's sample game The Question.
Conditional text is the case worth knowing about. { knows_poison: … } in ink and
<<if $knows_poison>> in Yarn are first-class idioms, and until v0.11.0 Parlance had no
faithful target for them at all. Conditional narration
closed that gap in the format, and the importers map guards onto it — including the else
branch, which needs the negation of its if's guard. Mapped to the same guard, an
else would show both lines whenever the guard holds, and no string comparison would
notice, so the importers' check compares the conditions as well. A guard Parlance can't
express exactly comes back as a declared loss rather than an approximation.
Since v0.15.0 the importers also carry three shapes they used to declare lost: a guarded
line before a choice list, a guarded last line, and a choice list with no line of its own.
A Yarn custom command such as <<shakeCamera 0.5>>, on its own line or on an option,
becomes an engine command; one
written inline in a line of narration is still named as a loss.
In the worked examples, declared loss fell from 120 to 67 units for The Intercept, from
101 to 37 for Cyberharcèlement, and from 182 to 145 for Not Weird. Queer.
Editorial audits
Six review-only skills that read a project and report on it — how a character's offers resolve against their arc, whether a character sounds like themselves, whether a line can be reached in a state where it isn't true yet, journal coherence, state reachability, and whether the project's use of the pattern cookbook's recipes falls into each recipe's documented pitfall.
They never draft. Every command is a read; none writes to data/. An audit that can't
judge without inventing the intent stops and asks you for it.
Like the importers, the audits are agent skills in the open SKILL.md format. Install
one in your agent's skills directory — for example .claude/skills/ for Claude Code or
.agents/skills/ — or point any other agent at its SKILL.md as instructions, then ask
your agent to run it.
MCP server — for LLM agents
The MCP server exposes a project to AI agents: twelve
tools, including id rename and custom game data, with dry_run support. Every
write is validated before it lands, a write that would give an entity a schema
error is refused, and every result carries the project's validation issues.
Agent output lands as canonical JSON in git — one reviewable diff. The server
ships inside the desktop app and runs on the app's own runtime: Help ▸
Connect an AI Agent… shows the ready-to-paste config for your install and
project (how).
AI drafting
In-editor drafting is optional and talks to Anthropic (Claude) or any OpenAI-compatible provider — whichever you choose, configured with your endpoint and API key. Drafted content is visually marked (the purple "AI" accent in the app's own palette) until a human accepts it — drafts propose, writers decide. Local-first still applies: nothing leaves your machine except the drafting request you explicitly make.
Related: the engine contract · the open spec · configuration