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:

Unity (C#) — official port

The parlance-unity package provides a pure C# .NET runtime for Unity projects:

Unreal Engine (C++) — parked at v0.9.0

The parlance-unreal plugin integrates Parlance natively into Unreal Engine using Core C++ types:

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:

  1. 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".
  2. Implement the contract. Published in parlance-spec, the runtime contract defines each function — evaluate, applyEffect, resolveCheck, stepDialogue, resolveCharacterDialogue, resolveQuests — including how dice consume the injected rng() (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.
  3. 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.
  4. 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:

  1. Copy the importer skill from the parlance-spec repository into your agent's skills directory — for example .claude/skills/ for Claude Code or .agents/skills/ — together with the shared importers/lib/ folder its commands run (lib/parse_<format>.py, lib/check.py). Coding agents that support SKILL.md load it natively; with any other agent, point it at the skill's SKILL.md as its instructions.
  2. Instruct your agent to run the import against your source files.
  3. 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