@patterkit/play-helpers 0.7.10 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +14 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/patterplay.min.js +4 -4
- package/dist/patterplay.min.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../../../../expr/packages/expr/src/own.ts","../../../../expr/packages/expr/src/ast.ts","../../../../expr/packages/expr/src/evaluate.ts","../../../../expr/packages/expr/src/prng.ts","../../../node_modules/@wildwinter/expr-specificity/src/index.ts","../../../../expr/packages/scoperegistry/src/state-logger.ts","../../../../expr/packages/scoperegistry/src/index.ts","../../dialect/src/engine-scopes.ts","../../dialect/src/index.ts","../../model/src/index.ts","../../runtime/src/tags.ts","../../runtime/src/engine.ts","../../runtime/src/describe.ts","../../runtime/src/gamedata.ts","../src/save.ts","../src/properties.ts","../src/logger.ts","../src/debug.ts","../src/refresh.ts","../src/inspector.ts","../src/bundle-inspector.ts","../src/audio.ts"],"sourcesContent":["// The browser drop-in: ONE classic script, no build step, defining the `Patterplay` global with the\n// runtime AND these helpers on it, so a plain page can build an engine, play, and save and load the\n// family's save text from a single script tag (and the Storylet Engine's beside it).\n//\n// Built by this package rather than the runtime because this is the one package that depends on\n// both: the runtime cannot bundle the helpers without bundling itself twice. The export names of the\n// two do not overlap (pinned by test/browser-drop-in.test.ts), so `export *` from each is exact.\n// Asked for by the Storylets side (from-storylets/browser-drop-in-carries-the-helpers, 2026-09-04),\n// whose `storyletengine.min.js` took the same shape the same day.\nexport * from \"@patterkit/runtime\";\nexport * from \"./index.js\";\n","// ---------------------------------------------------------------------------\n// Own-property reads, for every lookup by a name an author wrote.\n//\n// The language's names come from authors (`@world.constructor`, a function\n// called `toString`), and a plain JavaScript object answers the names it\n// inherits from Object.prototype. Looked up with `record[name]`, the keyword\n// table turned `constructor` into a token that was not an identifier, a static\n// scope bag read a built-in function as a property's value, and a dialect's\n// function table offered `Object` as a function to call (October 2026 review).\n// Every such lookup goes through `own`, which sees a record's own entries only.\n// ---------------------------------------------------------------------------\n\n/** `record[key]` when the record has it as its OWN property, else undefined. */\nexport function own<T>(record: Readonly<Record<string, T>> | undefined, key: string): T | undefined {\n return record !== undefined && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;\n}\n","// ---------------------------------------------------------------------------\n// AST - the in-memory expression tree and its serialised tagged-tuple form.\n//\n// The in-memory `ExprNode` is a discriminated union (kind field). The published\n// `AstNode` is the compact tagged-tuple form that goes into a compiled bundle's\n// { src, ast } envelope - what a runtime walks, never parses.\n//\n// This module is dialect-agnostic: scope tokens and function names are plain\n// strings here; meaning is supplied by a Dialect (see dialect.ts).\n// ---------------------------------------------------------------------------\n\nexport type ScalarValue = boolean | number | string | string[];\n\nexport type BinaryOp =\n | \"==\" | \"!=\" | \">\" | \">=\" | \"<\" | \"<=\"\n | \"+\" | \"-\" | \"*\" | \"/\"\n | \"and\" | \"or\";\n\nexport type UnaryOp = \"not\" | \"neg\";\n\nexport type ExprNode =\n | { kind: \"bool\"; value: boolean }\n | { kind: \"number\"; value: number }\n | { kind: \"string\"; value: string }\n // All property references are scoped: bare `@name` is canonicalised to\n // `@<defaultScope>.name` at parse time. Names are lowercased at parse time.\n | { kind: \"scopedvar\"; scope: string; name: string }\n | { kind: \"call\"; name: string; args: ExprNode[] }\n | { kind: \"unary\"; op: UnaryOp; operand: ExprNode }\n | { kind: \"binary\"; op: BinaryOp; left: ExprNode; right: ExprNode }\n // Produced only by flag-delta function argument parsing (see Dialect\n // `flagDeltaArgs`) - not valid elsewhere.\n | { kind: \"flagdelta\"; sign: \"+\" | \"-\"; name: string };\n\n/**\n * Path into an ExprNode tree. Each segment names the field on the parent node,\n * with numeric indices for array elements (call args).\n * binary.left -> [\"left\"]\n * binary.right.args[0] -> [\"right\", \"args\", 0]\n * top-level node -> []\n */\nexport type AstPath = readonly (string | number)[];\n\n// ---------------------------------------------------------------------------\n// Published tagged-tuple form (JSON arrays, opcode at index 0).\n// ---------------------------------------------------------------------------\n\nexport type AstNode =\n | [\"b\", boolean]\n | [\"n\", number]\n | [\"s\", string]\n | [\"sv\", string, string]\n | [\"u\", UnaryOp, AstNode]\n | [\"bin\", BinaryOp, AstNode, AstNode]\n | [\"call\", string, ...AstNode[]]\n | [\"fd\", \"+\" | \"-\", string];\n\n/** In-memory ExprNode -> published tagged-tuple AstNode. */\nexport function serialiseAst(node: ExprNode): AstNode {\n switch (node.kind) {\n case \"bool\": return [\"b\", node.value];\n case \"number\": return [\"n\", node.value];\n case \"string\": return [\"s\", node.value];\n case \"scopedvar\": return [\"sv\", node.scope, node.name];\n case \"unary\": return [\"u\", node.op, serialiseAst(node.operand)];\n case \"binary\": return [\"bin\", node.op, serialiseAst(node.left), serialiseAst(node.right)];\n case \"call\": return [\"call\", node.name, ...node.args.map(serialiseAst)];\n case \"flagdelta\": return [\"fd\", node.sign, node.name];\n }\n}\n\n/** Published tagged-tuple AstNode -> in-memory ExprNode. */\nexport function deserialiseAst(node: AstNode): ExprNode {\n switch (node[0]) {\n case \"b\": return { kind: \"bool\", value: node[1] };\n case \"n\": return { kind: \"number\", value: node[1] };\n case \"s\": return { kind: \"string\", value: node[1] };\n case \"sv\": return { kind: \"scopedvar\", scope: node[1], name: node[2] };\n case \"u\": return { kind: \"unary\", op: node[1], operand: deserialiseAst(node[2]) };\n case \"bin\": return { kind: \"binary\", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };\n case \"call\": {\n const args = (node.slice(2) as AstNode[]).map(deserialiseAst);\n return { kind: \"call\", name: node[1], args };\n }\n case \"fd\": return { kind: \"flagdelta\", sign: node[1], name: node[2] };\n }\n}\n","// ---------------------------------------------------------------------------\n// Evaluator - walk an ExprNode against an EvalContext, parameterised by Dialect.\n//\n// Operators (binary/unary), short-circuiting, and type-checking are generic.\n// Scope resolution uses the context's scope maps + the Dialect's per-scope\n// missing-property policy. Function calls dispatch to the Dialect's functions.\n//\n// Ported from @storylets/engine (storylets/packages/engine/src/expression.ts),\n// generalised by injecting scopes + functions from the Dialect.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode, ScalarValue } from \"./ast.js\";\nimport type { Dialect, EvalContext, ScopeResolver } from \"./dialect.js\";\nimport { own } from \"./own.js\";\n\nexport class EvalError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EvalError\";\n }\n}\n\nexport function evaluate(node: ExprNode, ctx: EvalContext, dialect: Dialect): ScalarValue {\n // Per-scope missing-property policy, precomputed once per top-level evaluate.\n const missingPolicy = new Map<string, \"false\" | \"throw\">(\n dialect.scopes.map((s) => [s.token, s.missing ?? \"false\"])\n );\n\n const rec = (n: ExprNode): ScalarValue => {\n switch (n.kind) {\n case \"bool\": return n.value;\n case \"number\": return n.value;\n case \"string\": return n.value;\n\n case \"scopedvar\": {\n const scope = own(ctx.scopes, n.scope);\n if (scope === undefined) {\n // Scope context absent -> graceful false. (A scope the dialect knows\n // about but the context didn't populate, or an unknown scope.)\n return false;\n }\n // A scope is either a static bag or a host resolver ({ get }). Bag values\n // are always ScalarValue (never functions), so a `get` function reliably\n // distinguishes a resolver.\n const val = typeof (scope as ScopeResolver).get === \"function\"\n ? (scope as ScopeResolver).get(n.name)\n : own(scope as Record<string, ScalarValue>, n.name);\n if (val === undefined) {\n // Property not declared on the present scope. Policy decides: \"false\"\n // for back-compat scopes, \"throw\" for scopes where a missing key is a\n // bug publish-time validation should have caught.\n if (missingPolicy.get(n.scope) === \"throw\") {\n throw new EvalError(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);\n }\n return false;\n }\n return val;\n }\n\n case \"call\": {\n // `advance` is the language's own, the first core built-in: the next\n // stage in the argument's ladder, saturating at the last. Core rather\n // than dialect because it IS the quality design's insertion mechanism\n // (an outcome that never names its destination routes through an\n // inserted stage automatically), and every dialect should say it the\n // same way. A dialect that defines its own `advance` wins, for\n // back-compat with any dialect that already had one.\n if (n.name === \"advance\" && !own(dialect.functions, n.name)) {\n const arg = n.args[0];\n if (n.args.length !== 1 || arg === undefined) {\n throw new EvalError(`advance() takes exactly 1 argument, got ${n.args.length}`);\n }\n const ladder = ladderOf(arg, ctx);\n if (ladder === undefined) {\n throw new EvalError(\"advance() needs a quality reference (@scope.name of a quality property)\");\n }\n const current = stageIndex(rec(arg), ladder, \"advance\");\n return ladder[Math.min(current + 1, ladder.length - 1)]!;\n }\n const def = own(dialect.functions, n.name);\n if (!def) throw new EvalError(`unknown function '${n.name}'`);\n return def.eval(n.args, { evaluate: rec, ctx });\n }\n\n case \"flagdelta\":\n throw new EvalError(\"flagdelta node is only valid as an argument to a flag-delta function\");\n\n case \"unary\": {\n if (n.op === \"not\") {\n const val = rec(n.operand);\n if (typeof val !== \"boolean\") throw new EvalError(`'not' requires a boolean operand, got ${typeof val}`);\n return !val;\n }\n // neg\n const val = rec(n.operand);\n if (typeof val !== \"number\") throw new EvalError(`unary '-' requires a numeric operand, got ${typeof val}`);\n return -val;\n }\n\n case \"binary\": {\n // Short-circuit operators first\n if (n.op === \"and\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'and' requires boolean operands, left is ${typeof l}`);\n if (!l) return false;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'and' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n if (n.op === \"or\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'or' requires boolean operands, left is ${typeof l}`);\n if (l) return true;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'or' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n\n const left = rec(n.left);\n const right = rec(n.right);\n\n // Quality: when either operand REFERENCES a quality (the node carries\n // the scope+name the channel resolves), ordering compares by ladder\n // position and arithmetic is refused. Everything else is untouched,\n // so a context with no channel behaves exactly as before.\n const lLadder = ladderOf(n.left, ctx);\n const rLadder = ladderOf(n.right, ctx);\n const ladder = lLadder ?? rLadder;\n if (ladder !== undefined) {\n if (lLadder && rLadder && !sameLadder(lLadder, rLadder)) {\n if (n.op === \">\" || n.op === \">=\" || n.op === \"<\" || n.op === \"<=\") {\n throw new EvalError(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);\n }\n }\n switch (n.op) {\n case \">\": return stageIndex(left, ladder, \">\") > stageIndex(right, ladder, \">\");\n case \">=\": return stageIndex(left, ladder, \">=\") >= stageIndex(right, ladder, \">=\");\n case \"<\": return stageIndex(left, ladder, \"<\") < stageIndex(right, ladder, \"<\");\n case \"<=\": return stageIndex(left, ladder, \"<=\") <= stageIndex(right, ladder, \"<=\");\n case \"+\": case \"-\": case \"*\": case \"/\":\n throw new EvalError(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);\n default: break; // == and != fall through to plain value equality\n }\n }\n\n switch (n.op) {\n case \"==\": return valueEquals(left, right);\n case \"!=\": return !valueEquals(left, right);\n case \">\": assertNumbers(left, right, \">\"); return (left as number) > (right as number);\n case \">=\": assertNumbers(left, right, \">=\"); return (left as number) >= (right as number);\n case \"<\": assertNumbers(left, right, \"<\"); return (left as number) < (right as number);\n case \"<=\": assertNumbers(left, right, \"<=\"); return (left as number) <= (right as number);\n case \"+\":\n if (typeof left === \"number\" && typeof right === \"number\") return left + right;\n if (typeof left === \"string\" && typeof right === \"string\") return left + right;\n throw new EvalError(`'+' requires two numbers or two strings, got ${typeof left} and ${typeof right}`);\n case \"-\": assertNumbers(left, right, \"-\"); return (left as number) - (right as number);\n case \"*\": assertNumbers(left, right, \"*\"); return (left as number) * (right as number);\n case \"/\":\n assertNumbers(left, right, \"/\");\n if ((right as number) === 0) throw new EvalError(\"division by zero\");\n return (left as number) / (right as number);\n }\n }\n }\n };\n\n return rec(node);\n}\n\n/**\n * Equality for `==` / `!=`. Primitives compare by value (JS `===`); arrays\n * (the flags value type) compare as SETS: same members, ORDER IRRELEVANT.\n *\n * Plain `===` on arrays would be reference equality - two distinct arrays with\n * the same contents would never be equal, and a fresh array (from a scope read\n * or a function result) would never equal another. Mixed array/non-array\n * operands are unequal, and never an error.\n *\n * Order was significant until 2026-09-01, and that was wrong: a flags value IS\n * a set, and its stored order is an artefact of the order somebody happened to\n * add things in. `set_flags(@f, +a)` then `+b` compared UNEQUAL to the same two\n * flags added the other way round, which is a difference no author can see and\n * none intends. The Storylet Engine papered over it by sorting in `set_flags`,\n * which only holds while every producer sorts: a bundle's declared default, or\n * a host handing a list in, does not.\n *\n * Compared as MULTISETS (sorted copies), so a duplicated flag still counts. A\n * well-formed flags value has no duplicates, but equality should not be the\n * thing that decides what happens if one appears.\n */\nfunction valueEquals(a: ScalarValue, b: ScalarValue): boolean {\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n const x = [...a].sort();\n const y = [...b].sort();\n for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;\n return true;\n }\n return a === b;\n}\n\nfunction assertNumbers(l: ScalarValue, r: ScalarValue, op: string): void {\n if (typeof l !== \"number\" || typeof r !== \"number\") {\n throw new EvalError(`'${op}' requires numeric operands, got ${typeof l} and ${typeof r}`);\n }\n}\n\n// --- quality (design: storylets-new/design/quality.md) -----------------------\n\n/** The ladder behind an operand NODE, when the context's quality channel says\n * it references a quality. Values are plain strings; the node is what carries\n * the (scope, name) the channel needs. */\nfunction ladderOf(node: ExprNode, ctx: EvalContext): readonly string[] | undefined {\n if (node.kind !== \"scopedvar\" || ctx.qualities === undefined) return undefined;\n return ctx.qualities(node.scope, node.name);\n}\n\n/** Index of a stage in a ladder; an unknown stage is an error naming the\n * value, never a silent pass (a drifted save is exactly what lands here). */\nfunction stageIndex(value: ScalarValue, ladder: readonly string[], op: string): number {\n if (typeof value !== \"string\") {\n throw new EvalError(`'${op}' on a quality compares stages, got ${typeof value}`);\n }\n const i = ladder.indexOf(value);\n if (i < 0) throw new EvalError(`\"${value}\" is not a stage of this quality (stages: ${ladder.join(\", \")})`);\n return i;\n}\n\nconst sameLadder = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((x, i) => x === b[i]);\n","// ---------------------------------------------------------------------------\n// mulberry32 - the contractual PRNG.\n//\n// A fixed, published algorithm that both product families need and neither\n// owns. It lived in `@storylet-studio/runtime` as prng.ts and inline in\n// `@patterkit/runtime`'s engine.ts, plus separately in three of Patterplay's\n// harnesses, plus a hand port in each of six native runtimes: thirteen copies\n// of forty lines. It belongs here, in the package both families already depend\n// on, for the same reason the evaluator does.\n//\n// All arithmetic is unsigned 32-bit, matching JavaScript's `>>> 0` and\n// Math.imul.\n// ---------------------------------------------------------------------------\n\nexport interface Prng {\n /** One draw in [0, 1); advances the state. */\n next(): number;\n /** The persisted state (a uint32; feed back into makePrng to restore). */\n state(): number;\n}\n\n/**\n * ECMA-262 7.1.6 ToUint32, which is what `seed >>> 0` means.\n *\n * Written out rather than spelled `seed >>> 0` because THIS is the obligation a\n * port in a language without JavaScript's shift semantics has to reproduce, and\n * the shorthand hides it. Patterplay's C++ port cast the seed straight to an\n * integer type, which is undefined behaviour outside that type's range and gave\n * two wrong answers before the parity corpus pinned them.\n */\nexport function toUint32(seed: number): number {\n if (Number.isNaN(seed) || !Number.isFinite(seed)) return 0;\n const modded = Math.trunc(seed) % 4294967296;\n return modded < 0 ? modded + 4294967296 : modded;\n}\n\n/**\n * Seed a generator. The state is UNSIGNED throughout (`>>> 0`, never `| 0`):\n * the two produce identical draws and differ only in the sign of the number a\n * save persists, and Patterplay shipped `| 0` for long enough that over half of\n * all its saves carried a negative state its own native ports could not read\n * back.\n */\nexport function makePrng(seed: number): Prng {\n let s = toUint32(seed);\n return {\n next(): number {\n s = (s + 0x6d2b79f5) >>> 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n },\n state(): number {\n return s;\n },\n };\n}\n\n/** The contractual shuffle: Fisher-Yates, descending. Runs of one element\n * consume no draws (the loop never executes for length < 2). */\nexport function shuffleInPlace<T>(arr: T[], prng: Prng): void {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(prng.next() * (i + 1));\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n }\n}\n","// ---------------------------------------------------------------------------\n// @wildwinter/expr-specificity - public surface.\n//\n// Matched-constraint specificity: score how many atomic constraints in an\n// expression are actively holding it true against the current state. An\n// evaluation-aware walk (unlike a static clause count, an `or`'s score depends\n// on which branch is currently matching). Shared by Storylet Studio (storylet\n// draw priority) and Patter (dialogue best-match), which had independently\n// grown the same algorithm.\n//\n// Built on @wildwinter/expr's `ExprNode`. The host supplies truthiness via an\n// `evalTruthy` closure, so this package stays ignorant of the eval context,\n// the dialect, and the host's truthiness rule - each host keeps its own\n// behaviour while sharing one definition of the walk.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode } from \"@wildwinter/expr\";\n\n/** A call node, narrowed from the ExprNode union. */\ntype CallNode = Extract<ExprNode, { kind: \"call\" }>;\n\n/**\n * Evaluate an expression subtree to a boolean. Host-bound: the host closes over\n * its own evaluate + eval context + dialect and applies its own truthiness\n * coercion (Storylets' `conditionPasses`, Patter's `truthy`, etc.).\n */\nexport type EvalTruthy = (node: ExprNode) => boolean;\n\n/**\n * A call treated as a conjunction of constraints rather than a single atom, so\n * it contributes its operand count instead of 1. `check_flags` is the built-in\n * example (see {@link CHECK_FLAGS_COUNTING_CALL}).\n */\nexport interface CountingCall {\n /** The function name this rule applies to. */\n name: string;\n /** How many constraints the call contributes when it holds (at least 1). */\n count: (node: CallNode) => number;\n}\n\nexport interface MatchedSpecificityOptions {\n /**\n * Root polarity - the truth value the whole condition must have. Production\n * only ever scores conditions already known eligible, so this defaults to\n * `true` and rarely needs setting.\n */\n want?: boolean;\n /**\n * Calls scored by operand count rather than as a single atom. Defaults to\n * `[CHECK_FLAGS_COUNTING_CALL]`. Supply your own to add or replace rules.\n */\n countingCalls?: readonly CountingCall[];\n}\n\n/**\n * `check_flags(v, f1..fN)` counts as N constraints - an N-ary AND over the flag\n * operands - never fewer than 1. `args[0]` is the flags source, so the operand\n * count is `args.length - 1`.\n */\nexport const CHECK_FLAGS_COUNTING_CALL: CountingCall = {\n name: \"check_flags\",\n count: (node) => Math.max(1, node.args.length - 1),\n};\n\nconst DEFAULT_COUNTING_CALLS: readonly CountingCall[] = [CHECK_FLAGS_COUNTING_CALL];\n\n/**\n * Score how many atomic constraints in `node` are actively holding it true\n * against current state, via `evalTruthy`.\n *\n * The walk carries a polarity flag `want` (\"the truth value this subtree must\n * have for the whole to hold\"), applying De Morgan as it descends:\n * - atom: 1 if its truth matches `want`, else 0\n * - and: under `want`, both must hold -> sum; under `!want`, behaves as or\n * - or: under `want`, strongest branch -> max; under `!want`, behaves as and\n * - not: recurse with `want` flipped\n * - counting call (e.g. check_flags): its operand count when it must hold and\n * does, else the negated rules apply\n *\n * @example\n * // `@x == 5 and @y > 3` with both holding -> 2\n * matchedSpecificity(ast, node => conditionPasses(evaluate(node, ctx)))\n */\nexport function matchedSpecificity(\n node: ExprNode,\n evalTruthy: EvalTruthy,\n opts?: MatchedSpecificityOptions,\n): number {\n const countingCalls = opts?.countingCalls ?? DEFAULT_COUNTING_CALLS;\n return walk(node, opts?.want ?? true, evalTruthy, countingCalls);\n}\n\nfunction walk(\n node: ExprNode,\n want: boolean,\n evalTruthy: EvalTruthy,\n countingCalls: readonly CountingCall[],\n): number {\n if (node.kind === \"binary\" && (node.op === \"and\" || node.op === \"or\")) {\n const l = walk(node.left, want, evalTruthy, countingCalls);\n const r = walk(node.right, want, evalTruthy, countingCalls);\n // De Morgan: an `and` under negation behaves like an `or`, and vice versa.\n const behaveAsAnd = (node.op === \"and\") === want;\n if (behaveAsAnd) return l > 0 && r > 0 ? l + r : 0; // both must hold -> sum\n return Math.max(l, r); // either holds -> strongest branch\n }\n if (node.kind === \"unary\" && node.op === \"not\") {\n return walk(node.operand, !want, evalTruthy, countingCalls);\n }\n if (node.kind === \"call\") {\n const rule = countingCalls.find((c) => c.name === node.name);\n if (rule) {\n const operands = rule.count(node);\n const holds = evalTruthy(node);\n if (want) return holds ? operands : 0;\n return holds ? 0 : 1; // negated: De Morgan -> at least one operand fails -> 1\n }\n }\n // Any other node is an atom worth one constraint when its truth matches want.\n return evalTruthy(node) === want ? 1 : 0;\n}\n","// ---------------------------------------------------------------------------\n// The state logger: what changed in the state kernel, as it changes.\n//\n// Both product families shipped one of these, in four runtimes each, and they\n// were not the same shape. The Storylet Engine's was PUSH-based on the\n// PropertyBag audit hook - a write logs the moment it lands, with the previous\n// value straight off the event. Patterplay's diffed whole saveGame() snapshots,\n// so it could only ever say what changed BETWEEN captures, and only for state a\n// save persists. This is the first one, because it is the better one: a diff\n// cannot tell a write from a write-and-write-back, cannot name the reason a\n// host attached to a write, and cannot see a value that changed and changed\n// back.\n//\n// A diff is still needed, and is kept, for everything that is NOT in a bag: a\n// product's own non-property state (turns, cooldowns, visit counts) arrives\n// through the adapter's `extra()` and is diffed on capture. Bags replaced\n// wholesale by a load fire no audit events either, so capture() re-reads and\n// re-mounts.\n//\n// Line format: `${label}${path}: ${from} -> ${to}`, `<unset>` for a value that\n// was not there.\n// ---------------------------------------------------------------------------\n\nimport type { PropertyBag, ScalarValue } from \"./index.js\";\n\n/** A flattened snapshot: path -> value. */\nexport type StateSnapshot = Record<string, ScalarValue>;\n\nexport interface StateChange {\n path: string;\n from: ScalarValue | undefined;\n to: ScalarValue | undefined;\n}\n\n/**\n * One bag on the logger's path space.\n *\n * Named for the LOG, not the bag: a product may already have its own type for enumerating\n * bags (the Storylet Engine's LogMount, which labels a mount \"story\" for its own purposes),\n * and in the ported runtimes both land in one namespace. They are also not the same thing,\n * which the prefix rule below is about.\n *\n * `pathPrefix` is used VERBATIM, separator included, exactly as the bag's own\n * is - it is not a scope token with a dot implied. Omit it and the bag's own\n * `pathPrefix` is used, which is what a product wants whenever its log paths\n * and its property addresses agree.\n *\n * They do not always agree, which is why this can be overridden: Patterplay\n * addresses a scene property `@scene.mood` (relative to the flow's current\n * scene) but has to LOG it as `@scene:kitchen.mood`, because a log covering\n * several scenes needs to say which one.\n */\nexport interface LogMount {\n bag: PropertyBag;\n pathPrefix?: string;\n}\n\n/** What a product supplies: its kernel bags (re-read on every capture, so a\n * product that replaces its bags on load re-mounts), and its non-property\n * state as flattened paths. */\nexport interface StateLoggerAdapter {\n mounts(): LogMount[];\n extra?(): StateSnapshot;\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to console.log. */\n sink?: (line: string) => void;\n /** Prefixed to every line, verbatim (e.g. `\"[board] \"`). */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state. Logs nothing. */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the audited writes already logged as\n * they landed, plus anything that changed WITHOUT an audit event, diffed,\n * logged and re-baselined. */\n capture(): StateChange[];\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\n/** The sorted set of paths that differ between two snapshots. */\nexport function diffState(prev: StateSnapshot, next: StateSnapshot): StateChange[] {\n const changes: StateChange[] = [];\n const paths = new Set([...Object.keys(prev), ...Object.keys(next)]);\n for (const path of [...paths].sort()) {\n const from = prev[path], to = next[path];\n if (JSON.stringify(from) !== JSON.stringify(to)) changes.push({ path, from, to });\n }\n return changes;\n}\n\nconst show = (v: ScalarValue | undefined): string => (v === undefined ? \"<unset>\" : JSON.stringify(v));\n\nconst prefixOf = (m: LogMount): string => m.pathPrefix ?? m.bag.pathPrefix;\n\nexport function createStateLogger(adapter: StateLoggerAdapter, opts: StateLoggerOptions = {}): StateLogger {\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const label = opts.label ?? \"\";\n const emit = (c: StateChange): void => { sink(`${label}${c.path}: ${show(c.from)} -> ${show(c.to)}`); };\n\n const full = (): StateSnapshot => {\n const out: StateSnapshot = {};\n for (const m of adapter.mounts()) {\n const prefix = prefixOf(m);\n for (const [name, value] of Object.entries(m.bag.values)) out[prefix + name] = value;\n }\n Object.assign(out, adapter.extra?.() ?? {});\n return structuredClone(out);\n };\n\n let baseline = full();\n let pushed: StateChange[] = [];\n let mounted: { bag: PropertyBag; off: () => void }[] = [];\n\n const hook = (prefix: string, bag: PropertyBag): (() => void) =>\n bag.onAudit((change) => {\n // The write logs as it lands, `from` straight off the event; the baseline\n // moves with it so capture() never re-reports what was already said.\n const c: StateChange = structuredClone({ path: prefix + change.name, from: change.prev, to: change.next });\n emit(c);\n pushed.push(c);\n baseline[c.path] = structuredClone(change.next);\n });\n\n const mount = (): void => {\n const mounts = adapter.mounts();\n const same = mounted.length === mounts.length && mounts.every((m, i) => mounted[i]!.bag === m.bag);\n if (same) return;\n for (const m of mounted) m.off();\n mounted = mounts.map((m) => ({ bag: m.bag, off: hook(prefixOf(m), m.bag) }));\n };\n mount();\n\n return {\n snapshot: full,\n capture(): StateChange[] {\n // Whatever arrived WITHOUT an audit event: the adapter's non-property\n // paths, and bag values replaced wholesale by a load (which fires none).\n const next = full();\n const diffed = diffState(baseline, next);\n for (const c of diffed) emit(c);\n const changes = [...pushed, ...diffed];\n pushed = [];\n baseline = next;\n mount(); // a load replaces a product's bags; re-hook them\n return changes;\n },\n dispose(): void {\n for (const m of mounted) m.off();\n mounted = [];\n pushed = [];\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only TO THE STORY; the HOST still writes it, by passing\n * `{ host: true }` (see `set`). Default is read/write. (`type`/`values` feed\n * validation.)\n *\n * The distinction is the whole point of the flag on a foreign scope, where the\n * value is the game's own: a flag carried in the story's bundle must not lock a\n * game out of its own state. Ruled 2026-09-05, after both products met it - the\n * Storylet Engine's venue clock and Patter's coverage driver were each blocked\n * from the one property they existed to move.\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n /** The author's note on what the property is for, which another tool's picker shows as a tip\n * (shared game scopes, scopes.ts). The registry itself ignores it. */\n purpose?: string;\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n /** The address this property answers to - what getProperty/setProperty take.\n * A bag composes it from its own `pathPrefix` and the name, so a row is\n * self-describing: an examiner can render and write a row without being told\n * separately where it came from.\n *\n * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,\n * because a prefix is not always a bare scope token: the Storylet Engine\n * addresses a deck's properties as `deck.<id>.name`, so the prefix is already\n * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain\n * case. (`@gold` also resolves - splitRef defaults an unqualified name to the\n * patter scope - but it is the shorthand, not the address a row reports.)\n *\n * With no prefix this is just the name. Both families forked this interface\n * to add exactly this field - once per runtime - which is the same reason\n * `stages` is here. */\n path: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies.\n *\n * It has no prototype, so a scope answers only its own properties: on a plain\n * object, `@world.constructor` read the built-in `Object` function rather than\n * \"missing\", and a write to `__proto__` vanished (October 2026 review). */\n readonly values: Record<string, ScalarValue> = Object.create(null) as Record<string, ScalarValue>;\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n /** The address prefix this bag's rows carry, separator included (`@`,\n * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */\n readonly pathPrefix: string;\n\n constructor(\n declarations: ScopeDeclaration[] = [],\n opts?: { normalise?: (name: string) => string; pathPrefix?: string },\n ) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.pathPrefix = opts?.pathPrefix ?? \"\";\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** A name as this bag keys it: its normalisation policy applied. The registry\n * uses it to key quality ladders and the validation schema the bag's own way,\n * so a case-significant (identity) bag is not quietly folded to lower case\n * one layer up. */\n normalise(name: string): string {\n return this.norm(name);\n }\n\n /** Whether a STORY write to this property is allowed: false only for a\n * declaration marked `writable: false`. A host write is always allowed. The\n * registry asks before writing, so it never has to read a refusal out of an\n * error that a listener might have raised instead. */\n writable(name: string): boolean {\n return this.decls.get(this.norm(name))?.writable !== false;\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property unless the caller says it is the\n * HOST (`host: true`), for whom `writable: false` was never a rule - it is\n * the story's promise, not the game's. `silent` and `host` are separate on\n * purpose: one is about who hears the write, the other about who may make\n * it. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string; host?: boolean }): BagChange {\n const n = this.norm(name);\n if (!opts?.host && !this.writable(n)) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n // The listeners as they stood when the write began, each told once, whatever any\n // of them does to the lists meanwhile (unsubscribing itself or another): the\n // contract every port keeps, pinned by the registry corpus.\n for (const audit of [...this.auditors]) audit(change);\n if (!change.silent) for (const fn of [...this.subscribers]) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name, this.pathPrefix));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events. */\n load(values: Record<string, ScalarValue>): void {\n for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;\n }\n}\n\nfunction rowFor(\n d: ScopeDeclaration,\n value: ScalarValue | undefined,\n writable?: boolean,\n name?: string,\n pathPrefix = \"\",\n): PropertyRow {\n const rowName = name ?? d.name.toLowerCase();\n return {\n name: rowName,\n path: pathPrefix + rowName,\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n // `stages` was added to the row so an examiner could offer a quality's ladder\n // instead of a free-text box, and then never populated here: every quality row\n // this function built came out without one. Fixed 2026-09-02.\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n owner?: string;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n norm: (name: string) => string;\n owner?: string;\n}\ntype Entry = OwnedScope | ForeignScope;\n\nconst lowerCase = (name: string): string => name.toLowerCase();\n\n/** Options for an owned scope the registry builds (`defineOwned`). */\nexport interface OwnedScopeOptions {\n /** The address prefix its examiner rows carry, separator included. Defaults\n * to `<token>.`; the address grammar is the product's, not the registry's. */\n pathPrefix?: string;\n /** Name normalisation: lower case by default; a case-significant product\n * passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it (an engine's name). Named in a clash error and carried\n * on examiner rows, so one examiner can group a combined game by engine. */\n owner?: string;\n}\n\n/** Options for a foreign scope (`defineForeign`). */\nexport interface ForeignScopeOptions {\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Name normalisation for the names passed to the resolver: lower case by\n * default; a case-significant product passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it. See `OwnedScopeOptions.owner`. */\n owner?: string;\n}\n\n/** Options for a context or schema built from the registry. */\nexport interface AliasOptions {\n /**\n * Expression token -> registered key. `{ scene: \"patter/flow-2/scene/tavern\" }`\n * makes `@scene` read that instance bag, for this context only.\n *\n * The registry learns nothing about what the token MEANS: which flow, which\n * scene, which deck is an engine's own idea, and the engine names the key per\n * evaluation. It has to be the registry's mechanism rather than the engine\n * patching the context afterwards, because quality ladders and validation are\n * looked up by token too, and an alias applies to all three alike.\n *\n * An alias to a key that is not registered throws: a condition evaluated\n * against a scope that is not there is an engine bug, not a graceful false.\n */\n aliases?: Record<string, string>;\n}\n\n/** Options for `remove`. */\nexport interface RemoveOptions {\n /** Park an owned scope's values, to be handed back when the same key is next\n * registered (a live reload rebuilding an engine). No effect on a foreign\n * scope, whose values were never the registry's. */\n keep?: boolean;\n}\n\n/** Options for `load`. */\nexport interface LoadOptions {\n /** Keep what earlier loads parked, adding this blob's unclaimed sections to\n * it (a section for the same key replaces the parked one). Without it, each\n * load replaces whatever an earlier load parked. For an engine moving an\n * older save's values into a registry the game has already loaded. */\n keepParked?: boolean;\n}\n\n/**\n * The versioned owned-state fragment.\n *\n * @deprecated Versioning belongs to the save that embeds the values, not to the\n * registry; no engine ever called this. Embed `save()` in your own versioned\n * save instead. Removed at the next breaking release.\n */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\n/** @deprecated See `OwnedStateFragment`. Removed at the next breaking release. */\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n /** Values loaded for keys nobody has registered yet, waiting to be claimed. */\n private readonly parked = new Map<string, Record<string, ScalarValue>>();\n private rev = 0;\n\n /**\n * A counter that moves whenever a scope is registered or removed, and at no\n * other time: it starts at 0 and each registration or removal adds 1. Values\n * changing does not move it. A caller that caches a context built by\n * `toEvalContext()` rebuilds it when this moves, because the context's set of\n * scopes is fixed when it is built while the values it reads stay live.\n */\n get revision(): number {\n return this.rev;\n }\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n *\n * The third argument may be the path prefix alone (the pre-0.7 form) or an\n * options object.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[], opts?: string | OwnedScopeOptions): this {\n const o: OwnedScopeOptions = typeof opts === \"string\" ? { pathPrefix: opts } : opts ?? {};\n // The scope knows its own token, so its rows can address themselves: `world.hp`.\n // The ADDRESS GRAMMAR is the product's, though, not the registry's - Patterplay\n // writes `@patter.gold` where the Storylet Engine writes `world.gold` - so a\n // caller may say how its addresses look. A bag MOUNTED here keeps whatever prefix\n // its holder gave it: the holder owns the addressing.\n const bag = new PropertyBag(declarations, {\n pathPrefix: o.pathPrefix ?? `${token}.`,\n ...(o.normalise ? { normalise: o.normalise } : {}),\n });\n return this.mountOwned(token, bag, o.owner !== undefined ? { owner: o.owner } : undefined);\n }\n\n /**\n * Attach an EXISTING bag as an owned scope: an engine (or a host) holds the\n * bag and this registry reads, writes, lists and saves it like its own.\n *\n * If values were loaded for this key before anyone registered it, the bag\n * claims them now: laid over its seeded defaults by the bag's own `load` rule.\n */\n mountOwned(token: string, bag: PropertyBag, opts?: { owner?: string }): this {\n this.assertFree(token, opts?.owner);\n this.scopes.set(token, { kind: \"owned\", bag, ...(opts?.owner !== undefined ? { owner: opts.owner } : {}) });\n this.rev++;\n const waiting = this.parked.get(token);\n if (waiting) {\n bag.load(waiting);\n this.parked.delete(token);\n }\n return this;\n }\n\n /**\n * Unregister a scope. With `{ keep: true }` an owned scope's values are parked\n * and handed back when the same key is next registered, which is how a live\n * reload hands an engine's state to its replacement. Throws on an unknown key.\n */\n remove(token: string, opts?: RemoveOptions): this {\n const e = this.scopes.get(token);\n if (!e) throw new Error(`unknown scope '@${token}'`);\n if (opts?.keep && e.kind === \"owned\") this.parked.set(token, e.bag.save());\n this.scopes.delete(token);\n this.rev++;\n return this;\n }\n\n /**\n * Drop parked values nobody claimed. Parked values are kept in the next save by\n * default, so nothing loaded is lost to a flow or deck that simply has not\n * reopened yet; a game that knows they are dead drops them here.\n *\n * With a `prefix`, only keys starting with it are dropped: an engine resetting\n * itself drops its own instance keys (`my-engine/`) and leaves every other\n * engine's alone.\n */\n discardParked(prefix?: string): this {\n if (prefix === undefined) this.parked.clear();\n else for (const key of [...this.parked.keys()]) if (key.startsWith(prefix)) this.parked.delete(key);\n return this;\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n opts: boolean | ForeignScopeOptions = true,\n ): this {\n // A boolean is the pre-0.7 form: the scope-level writable default alone.\n const o: ForeignScopeOptions = typeof opts === \"boolean\" ? { writable: opts } : opts;\n this.assertFree(token, o.owner);\n const norm = o.normalise ?? lowerCase;\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(norm(d.name), d);\n this.scopes.set(token, {\n kind: \"foreign\", resolver, decls, scopeWritable: o.writable ?? true, norm,\n ...(o.owner !== undefined ? { owner: o.owner } : {}),\n });\n this.rev++;\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(e.norm(name));\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown scope.\n *\n * `writable: false` is the STORY's promise, so a story write is refused and\n * a HOST write is not: pass `{ host: true }` from a host's own surface (its\n * `setProperty`, its tooling, a coverage driver) and never from the path an\n * outcome or effect takes. A foreign scope whose resolver has no `set` is\n * refused for everyone, host included - that is not a rule to bypass, it is\n * a game that gave no way to write. */\n set(scope: string, name: string, value: ScalarValue, opts?: { host?: boolean }): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n // Asked first, never caught afterwards: catching whatever the bag's write raised\n // reported a listener's own error (a game's hook) as a read-only refusal.\n if (!opts?.host && !e.bag.writable(name)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.bag.set(name, value, opts?.host ? { host: true } : undefined);\n return;\n }\n const n = e.norm(name);\n if (!e.resolver.set) throw new Error(`'@${scope}.${name}' is read-only`);\n if (!opts?.host && !this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string; owner?: string } & PropertyRow)[] {\n const out: ({ scope: string; owner?: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n const owner = e.owner !== undefined ? { owner: e.owner } : {};\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...owner, ...row });\n } else {\n for (const [n, d] of e.decls) {\n out.push({\n scope: token, ...owner,\n ...rowFor(d, e.resolver.get(n), this.foreignWritable(e, n), n, `${token}.`),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>, opts?: AliasOptions): EvalContext {\n const view = this.view(opts?.aliases);\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of view) scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders(view);\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => {\n const e = view.get(scope);\n return e ? qualities.get(scope)?.get(normOf(e)(name)) : undefined;\n },\n };\n }\n\n /**\n * The scopes an expression sees: every registered key under its own token,\n * then each alias token pointing at its key's entry (an alias shadows a key of\n * the same name). Keys an engine uses for instance bags (`engine/flow-2/...`)\n * are not valid expression tokens, so they are present but unreachable.\n */\n private view(aliases?: Record<string, string>): Map<string, Entry> {\n const out = new Map(this.scopes);\n for (const [token, key] of Object.entries(aliases ?? {})) {\n const e = this.scopes.get(key);\n if (!e) throw new Error(`alias '@${token}' names '${key}', which is not registered`);\n out.set(token, e);\n }\n return out;\n }\n\n /** Every quality declaration's ladder, keyed scope token then name (the\n * scope's own normalisation). */\n private qualityLadders(view: Map<string, Entry>): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of view) {\n for (const [n, d] of declsOf(e)) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(n, d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation. Aliases\n * apply as they do to `toEvalContext`, so a condition written against `@scene`\n * validates against the instance bag the engine names.\n */\n toSchema(opts?: AliasOptions): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.view(opts?.aliases)) {\n const decls = declsOf(e);\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const [n, d] of decls) m.set(n, {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes (foreign scopes are the game's, and the game\n * saves them), as bare bags keyed by token, plus any values still parked, so\n * a save taken before every engine has re-registered loses nothing. The\n * registry knows nothing about game saves: a game embeds this in its own. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n for (const [token, vals] of this.parked) out[token] = structuredClone(vals);\n return out;\n }\n\n /**\n * Restore from a `save` blob. An owned scope lays its section over its current\n * values (the bag's `load` rule). A section for a key nobody has registered\n * yet is PARKED and handed over when that key registers, so a game can load\n * its registry before its engines have reopened their flows or decks. A\n * section for a foreign scope is ignored: those values are the game's.\n *\n * A load replaces whatever was parked before it: it is a whole restore, and\n * residue from an earlier load must not leak into this one.\n *\n * Changed in 0.7.0: sections for unregistered keys used to be dropped.\n */\n load(blob: Record<string, Record<string, ScalarValue>>, opts?: LoadOptions): void {\n if (!opts?.keepParked) this.parked.clear();\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") e.bag.load(vals);\n else if (!e) this.parked.set(token, structuredClone(vals));\n }\n }\n\n /**\n * `save()` wrapped with a version stamp.\n *\n * @deprecated Versioning belongs to the save that embeds the values; no\n * engine ever called this. Embed `save()` in your own versioned save.\n * Removed at the next breaking release.\n */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /**\n * Restore from a versioned fragment; an unsupported version throws.\n *\n * @deprecated See `saveFragment`. Removed at the next breaking release.\n */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n /**\n * A token is taken once. There is no reserved-token list: a clash surfaces\n * here, the moment a game combines its engines, which is the only moment\n * anyone knows which engines are present. With owners recorded the error says\n * whose token it already is.\n */\n private assertFree(token: string, owner?: string): void {\n const e = this.scopes.get(token);\n if (!e) return;\n const by = e.owner !== undefined ? ` by ${e.owner}` : \"\";\n const wants = owner !== undefined ? ` (wanted by ${owner})` : \"\";\n throw new Error(`scope '@${token}' is already registered${by}${wants}`);\n }\n}\n\n/** A scope entry's declarations, keyed by its own normalisation. */\nfunction declsOf(e: Entry): [string, ScopeDeclaration][] {\n if (e.kind === \"foreign\") return [...e.decls.entries()];\n return e.bag.declarations().map((d) => [e.bag.normalise(d.name), d]);\n}\n\n/** A scope entry's name normalisation. */\nfunction normOf(e: Entry): (name: string) => string {\n return e.kind === \"foreign\" ? e.norm : (n) => e.bag.normalise(n);\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: Pick<ScopeDeclaration, \"type\" | \"default\" | \"values\" | \"stages\">): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle\n // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the\n // union does not have. Falling off the switch would seed `undefined`, which is not a\n // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the\n // guard and this one did not, which is the drift you only find by removing a duplicate.\n default: return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The state logger, which both product families had written twice each.\n// ---------------------------------------------------------------------------\nexport type {\n StateSnapshot, StateChange, LogMount, StateLoggerAdapter, StateLoggerOptions, StateLogger,\n} from \"./state-logger.js\";\nexport { createStateLogger, diffState } from \"./state-logger.js\";\n","// GENERATED - vendored from expr/family/engine-scopes.json by scripts/sync-conformance.mjs.\n// Do not edit here; edit the shared list and re-run the script.\n\n/** Every engine's game-wide scope token across the family. A compiler accepts every token here\n * that is not its own engine's, unchecked: the other engine owns those names and types. */\nexport interface EngineScope {\n token: string;\n engine: string;\n means: string;\n}\n\nexport const ENGINE_SCOPES: readonly EngineScope[] = [\n { token: \"patter\", engine: \"Patterplay\", means: \"Patter's shared globals\" },\n { token: \"story\", engine: \"Storylet Engine\", means: \"the Storylet Engine's shared @story properties\" },\n];\n","// ---------------------------------------------------------------------------\n// @patterkit/dialect - the Patter configuration of @wildwinter/expr.\n//\n// Provides the Patter `Dialect` (scopes + built-in functions) and a helper to\n// build an `ExpressionSchema` from Patter property declarations (for static\n// validation). Shared by the compiler (parse + validate) and the runtime\n// (evaluate), so it depends only on @wildwinter/expr at runtime (plus a\n// type-only import from @wildwinter/scoperegistry for the foreign-scope spec).\n//\n// Scope tokens: just two - `patter` (global, the default; bare `@name`) and\n// `scene` (scene-local). The default global token is `@patter` (decision in\n// design/scope-registry.md §10.1; renamed from the earlier provisional `@shared`).\n// SHARING is an orthogonal per-property axis (PropertyDecl.shared), NOT a scope\n// token: a flow-private global is `@patter` declared `shared:false`; a shared\n// scene prop is `@scene` declared `shared:true`. (This mirrors Storylet Studio's\n// `@world`/`@site` + shared-flag paradigm - the two tools share one model.)\n// FUTURE (design/scope-registry.md): this static scope list becomes a *registry*\n// (engine-owned + host/foreign tokens).\n// ---------------------------------------------------------------------------\n\nimport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport type { EngineScope } from \"./engine-scopes.js\";\nimport type {\n Dialect, EvalHelpers, ExprNode, ScalarValue,\n ExpressionSchema, PropertyType as ExprPropertyType,\n} from \"@wildwinter/expr\";\nimport { EvalError } from \"@wildwinter/expr\";\nimport type { ScopeRegistrySpec } from \"@wildwinter/scoperegistry\";\nimport type { ProjectFile, PropertyDecl, HostScopeRegistry } from \"@patterkit/model\";\n\ninterface PatterHost {\n /** Next float in [0, 1) from the seeded PRNG (for `random`). */\n nextRandom?: () => number;\n /** Times the current flow has entered a node (for `visits` / `seen`). */\n visits?: (id: string) => number;\n /** Times any flow has entered a node, world-wide (for `patter_visits` / `patter_seen`). */\n patterVisits?: (id: string) => number;\n}\n\nfunction host(h: EvalHelpers): PatterHost {\n return (h.ctx.host ?? {}) as PatterHost;\n}\n\n/** The Patter dialect: scopes patter/scene + built-in functions. */\nexport const patterDialect: Dialect = {\n defaultScope: \"patter\",\n scopes: [\n { token: \"patter\" }, // global / world state (graceful-false on miss); bare @name\n { token: \"scene\" }, // scene-local (per-flow or shared, per the property's `shared` flag)\n ],\n functions: {\n random: {\n minArgs: 2, maxArgs: 2, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n if (args.length !== 2) throw new EvalError(\"random(a, b) requires exactly 2 arguments\");\n const next = host(h).nextRandom;\n if (!next) throw new EvalError(\"random() called without a PRNG in context\");\n const a = h.evaluate(args[0]!), b = h.evaluate(args[1]!);\n if (typeof a !== \"number\" || typeof b !== \"number\") throw new EvalError(\"random(a, b) arguments must be numbers\");\n if (!Number.isInteger(a) || !Number.isInteger(b)) throw new EvalError(\"random(a, b) arguments must be integers\");\n const lo = Math.min(a, b), hi = Math.max(a, b);\n return Math.floor(next() * (hi - lo + 1)) + lo;\n },\n },\n check_flags: {\n minArgs: 1, returnType: \"boolean\", flagDeltaArgs: true,\n validate: flagsCall(\"check_flags\"),\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const flags = readFlags(args[0], h, \"check_flags\");\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") throw new EvalError(\"check_flags() flag args must be +flagName or -flagName\");\n if (arg.sign === \"+\" ? !flags.includes(arg.name) : flags.includes(arg.name)) return false;\n }\n return true;\n },\n },\n set_flags: {\n minArgs: 1, returnType: \"flags\", flagDeltaArgs: true,\n validate: flagsCall(\"set_flags\"),\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const result = [...readFlags(args[0], h, \"set_flags\")];\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") throw new EvalError(\"set_flags() flag args must be +flagName or -flagName\");\n if (arg.sign === \"+\") { if (!result.includes(arg.name)) result.push(arg.name); }\n else { const idx = result.indexOf(arg.name); if (idx >= 0) result.splice(idx, 1); }\n }\n return result;\n },\n },\n // Visit counts (spec §7): how many times a scene / block / node has been\n // *entered*. `visits` / `seen` are this flow's count (flow-local); the\n // `patter_` variants are the world-wide (@patter / shared) count. The arg is a\n // node id - the same id a jump targets.\n visits: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n validate: idArg(\"visits\"),\n eval: (args, h) => host(h).visits?.(nodeId(args, h, \"visits\")) ?? 0,\n },\n seen: {\n minArgs: 1, maxArgs: 1, returnType: \"boolean\",\n validate: idArg(\"seen\"),\n eval: (args, h) => (host(h).visits?.(nodeId(args, h, \"seen\")) ?? 0) > 0,\n },\n patter_visits: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n validate: idArg(\"patter_visits\"),\n eval: (args, h) => host(h).patterVisits?.(nodeId(args, h, \"patter_visits\")) ?? 0,\n },\n patter_seen: {\n minArgs: 1, maxArgs: 1, returnType: \"boolean\",\n validate: idArg(\"patter_seen\"),\n eval: (args, h) => (host(h).patterVisits?.(nodeId(args, h, \"patter_seen\")) ?? 0) > 0,\n },\n },\n};\n\n/**\n * Other engines' game-wide scopes a Patter expression may name, from the family's\n * shared list (expr/family/engine-scopes.json): `@story.act` reads the Storylet\n * Engine's shared @story in a game that runs both. On by default, with no project\n * setting, so combining the engines needs no wiring. They are opaque here (the\n * other engine owns their names and types), never self-backed, and the engine\n * reports at run time when the game has not registered one.\n */\nexport const EXTERNAL_SCOPES: readonly string[] = ENGINE_SCOPES\n .map((s) => s.token)\n .filter((t) => t !== \"patter\" && t !== \"scene\");\n\n/**\n * A foreign-scope spec with the family's other engines added as OPAQUE scopes\n * (no declarations), unless the spec already declares them: a project that\n * imported the Storylet Engine's spec keeps its checked declarations. For\n * compiling and validating only; a bundle's own scopeRegistry never carries\n * these, because the runtime self-backs what it lists.\n */\nexport function withEngineScopes(spec?: ScopeRegistrySpec): ScopeRegistrySpec {\n const present = new Set((spec?.scopes ?? []).map((s) => s.token));\n const extra = EXTERNAL_SCOPES.filter((t) => !present.has(t)).map((token) => ({ token }));\n return { version: spec?.version ?? 1, scopes: [...(spec?.scopes ?? []), ...extra] };\n}\n\n/**\n * A Patter dialect extended with FOREIGN scope tokens imported from another\n * owner's `scopeRegistrySpec` (e.g. a storylet's `@world` / `@player` / `@system`).\n * The parser needs every referenced scope token registered, so authoring tools\n * that allow cross-engine references must compile/validate with this dialect.\n * Foreign scopes use the default missing policy (graceful-false). With no spec\n * (or an empty one) this returns the base `patterDialect` unchanged.\n */\nexport function dialectWithForeignScopes(given?: ScopeRegistrySpec): Dialect {\n // The family's other engines are always in: a Patter line can name `@story`.\n const spec = withEngineScopes(given);\n if (spec.scopes.length === 0) return patterDialect;\n const known = new Set(patterDialect.scopes.map((s) => s.token));\n const extra = spec.scopes\n .filter((s) => !known.has(s.token))\n .map((s) => ({ token: s.token }));\n if (extra.length === 0) return patterDialect;\n return { ...patterDialect, scopes: [...patterDialect.scopes, ...extra] };\n}\n\n/**\n * Split a property ref (\"@name\" / \"@scope.name\") into scope + name - THE one\n * ref grammar, shared by the compiler's validators and the runtime so they\n * cannot drift. `isScope` says which tokens are scopes in the caller's context\n * (dialect tokens, plus any foreign tokens); anything else - including a bare\n * `@name` and a dotted name whose head is not a scope - is a `patter` property.\n */\nexport function splitRef(ref: string, isScope: (token: string) => boolean): { scope: string; name: string } {\n const parts = ref.replace(/^@/, \"\").split(\".\");\n if (parts.length === 2 && isScope(parts[0]!)) {\n return { scope: parts[0]!, name: parts[1]!.toLowerCase() };\n }\n return { scope: \"patter\", name: parts.join(\".\").toLowerCase() };\n}\n\n/** Evaluate a visit-function's single argument to the node id (a string). */\nfunction nodeId(args: ExprNode[], h: EvalHelpers, fn: string): string {\n const v = h.evaluate(args[0]!);\n if (typeof v !== \"string\") throw new EvalError(`${fn}(id) requires a string node id`);\n return v;\n}\n\n/** Validate that a visit function's argument is a string id literal. */\nfunction idArg(fnName: string) {\n return (args: ExprNode[], h: import(\"@wildwinter/expr\").ValidateHelpers): void => {\n const first = args[0];\n if (first && first.kind !== \"string\") {\n h.report({ path: [...h.path, \"args\", 0], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(id): the argument must be a string id literal (a scene / block / node id)` });\n }\n };\n}\n\nfunction readFlags(arg: ExprNode | undefined, h: EvalHelpers, fn: string): string[] {\n if (!arg) throw new EvalError(`${fn}() requires at least one argument (the flags variable)`);\n const v = h.evaluate(arg);\n if (Array.isArray(v)) return v as string[];\n if (v === false || v === null || v === undefined) return []; // empty flags\n throw new EvalError(`${fn}() first argument must be a flags property`);\n}\n\n// Validation for the flags functions (first arg a flags property; deltas declared).\nfunction flagsCall(fnName: string) {\n return (args: ExprNode[], h: import(\"@wildwinter/expr\").ValidateHelpers): void => {\n if (args.length === 0) return;\n const first = args[0]!;\n if (first.kind !== \"scopedvar\") {\n h.report({ path: [...h.path, \"args\", 0], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(): first argument must be a flags property reference (@name or @scope.name)` });\n return;\n }\n const meta = h.schema.properties.get(first.scope)?.get(first.name);\n if (meta && meta.type !== \"flags\") {\n const ref = first.scope === h.defaultScope ? first.name : `${first.scope}.${first.name}`;\n h.report({ path: [...h.path, \"args\", 0], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(): '@${ref}' is not a flags property (got ${meta.type})` });\n return;\n }\n // The +flag/-flag SHAPE check is independent of whether the property is\n // declared; only the flag-NAME check needs the declaration's value list.\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") {\n h.report({ path: [...h.path, \"args\", i], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(): argument ${i + 1} must be +flagName or -flagName` });\n } else if (meta?.type === \"flags\" && meta.enumValues && !meta.enumValues.includes(arg.name)) {\n h.report({ path: [...h.path, \"args\", i], kind: \"unknown-flag-name\", severity: \"error\",\n message: `${fnName}(): unknown flag '${arg.name}'`, reference: arg.name });\n }\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// ExpressionSchema from property declarations (provisional scope mapping).\n// ---------------------------------------------------------------------------\n\n/**\n * Project the model's host-scope registry (`@world`, ...) onto the\n * `scopeRegistrySpec` the compiler / validator consume. Patter's property types\n * are now the same vocabulary as expr's, so this is a structural pass-through\n * that drops authoring-only fields (`purpose`) and omits unset `writable`;\n * returns undefined for an absent registry so callers can pass it straight through.\n */\nexport function hostScopesToSpec(reg?: HostScopeRegistry): ScopeRegistrySpec | undefined {\n if (!reg) return undefined;\n return {\n version: reg.version,\n scopes: reg.scopes.map((s) => ({\n token: s.token,\n ...(s.writable === false ? { writable: false } : {}),\n ...(s.declarations\n ? {\n declarations: s.declarations.map((d) => ({\n name: d.name,\n type: d.type,\n ...(d.values ? { values: d.values } : {}),\n ...(d.stages ? { stages: d.stages } : {}),\n ...(d.default !== undefined ? { default: d.default } : {}),\n ...(d.writable === false ? { writable: false } : {}),\n })),\n }\n : {}),\n })),\n };\n}\n\n/**\n * Build an ExpressionSchema for validating a scene's expressions: global\n * properties (`@patter`) plus that scene's scene-local properties (`@scene`),\n * plus any FOREIGN scopes imported from another owner's `scopeRegistrySpec`.\n * Scope mapping:\n * global (project.properties) -> @patter\n * scene-local (scene.sceneProps)-> @scene\n * foreign (spec) -> its own token (e.g. @world)\n * The `shared` flag is orthogonal - it does not affect validation (both shared\n * and not-shared globals are `@patter`; both scene props are `@scene`). A foreign\n * scope with no declarations is left opaque (omitted) - matching `ScopeRegistry.toSchema`.\n */\nexport function buildSchema(\n project: ProjectFile,\n sceneProps: PropertyDecl[] = [],\n foreign?: ScopeRegistrySpec,\n): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: ExprPropertyType; enumValues?: string[]; stages?: string[] }>>();\n const put = (scope: string, decl: PropertyDecl): void => {\n let m = properties.get(scope);\n if (!m) { m = new Map(); properties.set(scope, m); }\n m.set(decl.name.toLowerCase(), { type: decl.type, enumValues: decl.values, stages: decl.stages });\n };\n for (const decl of project.properties ?? []) put(\"patter\", decl);\n for (const decl of sceneProps) put(\"scene\", decl);\n const known = new Set(patterDialect.scopes.map((s) => s.token));\n for (const scope of foreign?.scopes ?? []) {\n if (known.has(scope.token)) continue; // never let a foreign spec shadow patter/scene\n if (!scope.declarations?.length) continue; // opaque scope\n const m = new Map<string, { type: ExprPropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of scope.declarations) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values, stages: d.stages });\n properties.set(scope.token, m);\n }\n return { properties };\n}\n\n// ---------------------------------------------------------------------------\n// Inline interpolation (spec §16): `{@ref}` slots inside localised strings.\n//\n// Committed surface = a BARE property reference only - `{@name}`, `{@patter.x}`,\n// `{@scene.y}` - resolved to its value and rendered as text. Full expressions /\n// ICU-style formatting are deferred (the `{ ... }` delimiter leaves room). Only\n// `{ ... }` whose trimmed body starts with `@` is a slot, so JSON-ish `{foo}`\n// stays literal. Braces are escaped by doubling: `{{` -> literal `{` and `}}` ->\n// literal `}`, so `{{@name}}` renders the text `{@name}` rather than expanding.\n// ---------------------------------------------------------------------------\n\n/** A `{ ... }` candidate whose trimmed body starts with `@`. */\nexport interface Slot {\n /** The whole matched text including braces, e.g. \"{@gold}\". */\n raw: string;\n /** The trimmed inner body, e.g. \"@gold\" or (malformed) \"@gold + 1\". */\n inner: string;\n /** The bare property ref (\"@gold\") when well-formed; undefined if malformed. */\n ref?: string;\n}\n\nconst BARE_REF = /^@[A-Za-z0-9_.]+$/;\n\ntype Token =\n | { kind: \"text\"; value: string }\n | ({ kind: \"slot\" } & Slot);\n\n/**\n * Tokenise a localised string into literal text and `{@ref}` slots, applying\n * brace-doubling escapes (`{{` -> `{`, `}}` -> `}`). The single source of truth\n * for both `interpolate` (runtime) and `extractSlots` (validator), so they can't\n * disagree on what counts as a slot. A `{ ... }` whose trimmed body does not\n * start with `@` is literal (kept verbatim, braces included).\n */\nfunction* tokenise(text: string): Generator<Token> {\n let buf = \"\";\n let i = 0;\n while (i < text.length) {\n const c = text[i];\n if (c === \"{\" && text[i + 1] === \"{\") { buf += \"{\"; i += 2; continue; }\n if (c === \"}\" && text[i + 1] === \"}\") { buf += \"}\"; i += 2; continue; }\n if (c === \"{\") {\n const close = text.indexOf(\"}\", i + 1);\n if (close !== -1) {\n const raw = text.slice(i, close + 1);\n const inner = text.slice(i + 1, close).trim();\n if (inner.startsWith(\"@\")) {\n if (buf) { yield { kind: \"text\", value: buf }; buf = \"\"; }\n yield { kind: \"slot\", raw, inner, ref: BARE_REF.test(inner) ? inner : undefined };\n i = close + 1;\n continue;\n }\n buf += raw; i = close + 1; continue; // not a slot -> literal braces\n }\n }\n buf += c; i += 1; // ordinary char (incl. an unclosed `{`)\n }\n if (buf) yield { kind: \"text\", value: buf };\n}\n\n/**\n * Find interpolation slots in a localised string. Only `{ ... }` whose trimmed\n * body begins with `@` is a slot; a slot whose body is not a bare ref is returned\n * with `ref` undefined (so the validator can flag it - the committed surface is a\n * bare property reference only). Escaped `{{ }}` braces are not slots.\n */\nexport function extractSlots(text: string): Slot[] {\n const out: Slot[] = [];\n for (const tok of tokenise(text)) {\n if (tok.kind === \"slot\") out.push({ raw: tok.raw, inner: tok.inner, ref: tok.ref });\n }\n return out;\n}\n\n/** Render a resolved slot value as display text. */\nexport function renderSlotValue(v: ScalarValue): string {\n if (Array.isArray(v)) return v.join(\", \");\n if (typeof v === \"boolean\") return v ? \"true\" : \"false\";\n return String(v);\n}\n\n/** ASCII whitespace for caption-collapse - a FIXED set (space, tab, newline, CR, form-feed, vtab) so\n * every Patterplay runtime collapses identically (a regex `\\s` would drift on Unicode across languages). */\nfunction isCaptionWs(c: string): boolean {\n return c === \" \" || c === \"\\t\" || c === \"\\n\" || c === \"\\r\" || c === \"\\f\" || c === \"\\v\";\n}\n\n/** Collapse every run of ASCII whitespace to a single space and trim both ends. Manual (no regex) so it\n * ports byte-for-byte to C# / C++ / GDScript. */\nfunction collapseCaptionWs(s: string): string {\n let out = \"\";\n let pendingSpace = false;\n for (const c of s) {\n if (isCaptionWs(c)) { pendingSpace = true; continue; }\n if (pendingSpace && out.length > 0) out += \" \";\n pendingSpace = false;\n out += c;\n }\n return out;\n}\n\n/**\n * Closed-caption stripping (#214): with captions OFF, remove every `open`…`close` span (delimiters\n * included) from a dialogue line and collapse the surrounding whitespace -\n * `Oh dear. (sigh) What now?` -> `Oh dear. What now?`. A string that contains NO cue is returned\n * unchanged (its original whitespace preserved); only a string we actually edited is whitespace-\n * normalised. `open` and `close` may be the same token (e.g. `*…*`); an unclosed `open` keeps the\n * remainder verbatim. An empty `open` is a no-op. Identical across every Patterplay runtime - part of\n * the conformance contract.\n */\nexport function stripCaptions(text: string, open: string, close: string): string {\n if (open.length === 0 || text.indexOf(open) < 0) return text; // disabled / no cue: fast path, unchanged\n let out = \"\";\n let i = 0;\n let removed = false;\n while (i < text.length) {\n if (text.startsWith(open, i)) {\n const end = text.indexOf(close, i + open.length);\n if (end >= 0) { i = end + close.length; removed = true; continue; } // skip the whole span\n out += text.slice(i); // unclosed cue -> keep the rest literally\n break;\n }\n out += text[i];\n i += 1;\n }\n return removed ? collapseCaptionWs(out) : text;\n}\n\n/**\n * Expand `{@ref}` slots in a string using `resolve` (a property lookup).\n * Well-formed slots become their rendered value (undefined -> empty string);\n * malformed slots and non-slot braces are left verbatim; `{{`/`}}` unescape to\n * literal `{`/`}`.\n */\nexport function interpolate(text: string, resolve: (ref: string) => ScalarValue | undefined): string {\n if (text.indexOf(\"{\") < 0) return text; // fast path: no slot opener -> nothing to interpolate (the common case)\n let out = \"\";\n for (const tok of tokenise(text)) {\n if (tok.kind === \"text\") { out += tok.value; continue; }\n if (!tok.ref) { out += tok.raw; continue; } // malformed slot -> verbatim\n const v = resolve(tok.ref);\n out += v === undefined ? \"\" : renderSlotValue(v);\n }\n return out;\n}\n","// ---------------------------------------------------------------------------\n// @patterkit/model - Patter data-model types (the shape source-of-truth).\n//\n// Faithful to the on-disk schema spec. Covers the SOURCE format - the flow tree\n// (scene/block/group/snippet/beat/jump), the project file, locale files, the\n// authoring file - and, at the bottom, the EXPORT BUNDLE types (schema §10).\n// Save / runtime-state types (schema §9) live with the runtime.\n//\n// All conditions and effect expressions are stored as `src` strings here (no\n// AST in source); see @wildwinter/expr for the expression language.\n// ---------------------------------------------------------------------------\n\nimport type { AstNode } from \"@wildwinter/expr\";\n\nexport type ScalarValue = boolean | number | string | string[];\n\n/** Developer-defined host metadata (spec §17). Opaque to Patter. */\nexport type GameData = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Effects (spec §15) - state mutation at snippet seams. SET-ONLY: an effect is a property\n// mutation and nothing else. Host event emission is NOT an effect - it rides on gameData\n// (snippet- or beat-level), see spec §15 \"Host calls\".\n// ---------------------------------------------------------------------------\n\nexport type Effect =\n // property mutation: assign `target` (a ref \"@name\" / \"@scope.name\") the result of `value`.\n { kind: \"set\"; target: string; value: string };\n\n// ---------------------------------------------------------------------------\n// Jumps (spec §3) - a snippet's optional routing action.\n// ---------------------------------------------------------------------------\n\n/** A scene/block id, or the reserved \"END\". */\nexport type JumpTarget = string;\n\nexport interface Jump {\n to: JumpTarget;\n /** \"jump\" (one-way, default) or \"call\" (jump-and-return via the flow callstack). */\n mode?: \"jump\" | \"call\";\n}\n\n// ---------------------------------------------------------------------------\n// Beats (spec §2) - the atomic content units inside a snippet.\n// ---------------------------------------------------------------------------\n\n// A scene holds any mix of the three kinds (spec §2): spoken dialogue, prose\n// narration, and engine instructions.\n\nexport interface LineBeat {\n id: string;\n kind: \"line\";\n /** Speaker; must be a member of the project cast (validated). */\n character?: string;\n /** Performance direction, language-neutral (never localised). */\n direction?: string;\n gameData?: GameData;\n /** Author-defined freeform tags (#215): a cross-cutting label layer that travels to the runtime.\n * At runtime a beat's tags are the UNION of its own and every ancestor's (scene → block → group(s) →\n * snippet → beat). Each tag is letters/digits/symbols with NO comma and NO whitespace; deduped. */\n tags?: string[];\n}\n\n/**\n * Authorial voice / narration - speaker-less prose (spec §2). Never voiced; its\n * localised text always permits inline `{@name}` interpolation (spec §16). This\n * is the on-screen-text role (e.g. \"A door slams!\") - distinct from a game-event\n * beat, which is a pure engine instruction with no localised content.\n */\nexport interface TextBeat {\n id: string;\n kind: \"text\";\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n}\n\n/**\n * A GAME EVENT (spec §2): a pure engine instruction with no player-facing text - just `gameData` the host\n * reads when the beat plays (comments / docs attach via the authoring file by id). Named \"game event\"\n * rather than \"action\" because a screenplay's \"action\" is prose, which is our text beat. Player-facing\n * words are a line or text beat instead.\n */\nexport interface GameEventBeat {\n id: string;\n kind: \"gameEvent\";\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n}\n\nexport type Beat = LineBeat | TextBeat | GameEventBeat;\n\n// ---------------------------------------------------------------------------\n// Selectable nodes - Group and Snippet (spec §2, §4).\n// ---------------------------------------------------------------------------\n\nexport type Selector =\n | \"run\" | \"branch\" | \"sequence\" | \"choice\";\n\n/** How a `sequence` walks its children. `specificity` = **Best match**: pick the eligible\n * child whose condition most specifically fits the current state (the most atomic constraints\n * actively holding it true); equally-specific ties break by the seeded shuffle. A child with no\n * condition scores zero, so it acts as the filler that wins only when nothing more specific is\n * eligible. Composes with `exhaust`: `repeat` re-scores every visit (re-pickable, the Best-match\n * default), `once` uses each pick up so the group slides down to the filler (graceful degradation). */\nexport type SelectorOrder = \"sequential\" | \"shuffle\" | \"specificity\";\n/** What a `sequence` does after one full pass through its children. */\nexport type SelectorExhaust = \"once\" | \"repeat\" | \"stick\";\n\n/**\n * `sequence` selector config (spec §4). One stateful picker with two orthogonal\n * axes subsumes Ink's stopping / cycle / once / shuffle and their combinations.\n * Defaults: `order: \"sequential\"`, `exhaust: \"once\"`. `shuffle` draws without\n * replacement and never repeats a line back-to-back (built in).\n */\nexport interface SequenceOptions {\n order?: SelectorOrder;\n exhaust?: SelectorExhaust;\n}\n\nexport interface Snippet {\n id: string;\n type: \"snippet\";\n /** Eligibility (and the conditional-jump test). Expression src. */\n condition?: string;\n /** Zero or more beats; zero beats + a jump = a pure jump (spec §3). */\n beats?: Beat[];\n onEnter?: Effect[];\n onExit?: Effect[];\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n /** Optional routing action; fires after `beats`. */\n jump?: Jump;\n // Choice-option field (only meaningful when this snippet is a bare `choice` child,\n // the runtime-tolerance shape, spec §5): the option's prompt is its first content line.\n /** Omit this option entirely when its condition fails (default: show greyed). */\n secretUntilEligible?: boolean;\n /** Repeatable choice option (spec §5). `true`: always offered while its condition passes (Ink `+`).\n * Default `false`: once-only - after the player follows it once it is gone from `getChoices`\n * entirely (not delivered, not flagged unavailable - just absent; Ink `*`). */\n sticky?: boolean;\n /** The choice's fallback option (spec §5). Never delivered as a normal option; auto-followed the\n * moment it is the ONLY eligible option left (its own condition still applies). At most one per\n * choice. Default `false`. */\n fallback?: boolean;\n}\n\n/** An option's prompt beat (spec §5): a single line | text beat - the choice text. */\nexport type PromptBeat = LineBeat | TextBeat;\n\nexport interface Group {\n id: string;\n type: \"group\";\n condition?: string;\n /** How the group's children are walked. Default (omitted) = `\"run\"`: play them in order. */\n selector?: Selector;\n /**\n * For the memoried `sequence` selector (spec §7): is the selector's cursor\n * SHARED across all flows (one cursor world-wide - e.g. two NPCs never draw the\n * same shuffled line) or kept per-flow? Default `false` (per-flow). Orthogonal to\n * a property's `shared` flag; same name, same idea.\n */\n shared?: boolean;\n /** `sequence` config (order × exhaust, spec §4). */\n options?: SequenceOptions;\n children: Array<Group | Snippet>;\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n // Option-position fields (spec §5): valid ONLY when this group is a direct child\n // of a `choice` group - where it is an OPTION whose `children` are the option's\n // content run. Validator-enforced; never carried by a normal group.\n /** The choice text (spec §5): a single line | text beat, always present on an\n * authored option. IS the host's choice text - no derivation, no look-ahead. */\n prompt?: PromptBeat;\n /** Keep this option out of `getChoices()` while ineligible (secrecy). Default false. */\n secretUntilEligible?: boolean;\n /** Repeatable choice option (spec §5). `true`: always offered while its condition passes (Ink `+`).\n * Default `false`: once-only - after the player follows it once it is gone from `getChoices`\n * entirely (not delivered, not flagged unavailable - just absent; Ink `*`). */\n sticky?: boolean;\n /** The choice's fallback option (spec §5). Never delivered as a normal option; auto-followed the\n * moment it is the ONLY eligible option left (its own condition still applies). At most one per\n * choice. Default `false`. */\n fallback?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Addressable nodes - Block and Scene (spec §2).\n// ---------------------------------------------------------------------------\n\nexport interface Block {\n id: string;\n type: \"block\";\n /** Mandatory, author-editable (jump targets must show a readable destination). */\n name: string;\n /**\n * The author-editable, host-facing ADDRESS (spec §6) - a readable slug the runtime targets\n * (\"play this block\"), distinct from the opaque immutable `id` (the internal join key). Absent =\n * derived from `name` (`effectiveGameId`); present = author-pinned (survives renames). Unique\n * within its scene (scene-scoped addressing). Hyphen-slug form (see core `gameIdify`).\n */\n gameId?: string;\n children: Array<Group | Snippet>;\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n}\n\nexport interface Scene {\n id: string;\n type: \"scene\";\n name: string;\n /**\n * The author-editable, host-facing ADDRESS (spec §6) - a readable slug the runtime targets\n * (\"play this scene\"), distinct from the opaque immutable `id`. Absent = derived from `name`\n * (`effectiveGameId`); present = author-pinned. Unique project-wide. Hyphen-slug form (core `gameIdify`).\n */\n gameId?: string;\n /** Host metadata - e.g. `location` lives here, not as a core field. */\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n /** Entry behaviour / setup (spec §15). */\n onEntry?: Effect[];\n /**\n * Scene-scoped property declarations - `@scene` (spec §7). Each may be marked\n * `shared` (one value across all flows in the scene) or not (per-flow, the\n * default); the reference is `@scene.name` either way.\n */\n sceneProps?: PropertyDecl[];\n /** One or more blocks; the first is the default entry. */\n blocks: Block[];\n}\n\n/** A flow file (.patterflow) - one scene. */\nexport interface FlowFile {\n schema: string; // \"patter/flow@0\"\n scene: Scene;\n}\n\nexport type FlowNode = Scene | Block | Group | Snippet;\n\n/**\n * Visit every selectable node (group / snippet) under a children list,\n * depth-first in authored order. Generic over the source AND compiled trees\n * (both share the group/snippet shape) - THE one tree walk; validators,\n * indexers, and exporters all route through it so no hand-rolled walker can\n * forget a level (or a string-bearing field on one).\n */\nexport function walkNodes<N extends { type: string }>(\n nodes: ReadonlyArray<N>,\n visit: (node: N) => void,\n): void {\n for (const node of nodes) {\n visit(node);\n // Groups carry children; snippets do not. Accessed structurally so the one\n // walker serves both the source and compiled trees.\n const children = (node as { children?: ReadonlyArray<N> }).children;\n if (children) walkNodes(children, visit);\n }\n}\n\n/**\n * A beat with NO content worth keeping: an empty line / text bubble left behind - e.g. a snippet seeded\n * only so a jump could hang off it. Such a beat would render at runtime as a blank line that (lacking any\n * localised string) falls back to emitting its raw id, so the editor drops it on save. `hasText` = the\n * beat has a non-empty display string. A game-event beat is never contentless (it's a pure instruction),\n * and a beat carrying `gameData` or `tags` is meaningful even with no text. A jump-only snippet is then\n * just `{ jump }` with zero beats - which is valid.\n */\nexport function isContentlessBeat(beat: Beat, hasText: boolean): boolean {\n if (beat.kind === \"gameEvent\") return false;\n if (hasText) return false;\n if (beat.gameData && Object.keys(beat.gameData).length > 0) return false;\n if (beat.tags && beat.tags.length > 0) return false;\n return beat.kind === \"text\" || (!beat.character && !beat.direction);\n}\n\n// ---------------------------------------------------------------------------\n// Game IDs (spec §6) - the author-editable, host-facing ADDRESS for an\n// addressable node (scene / block): a hyphen-slug the runtime targets, distinct\n// from the opaque immutable `id` and the computed readable `handle`. These pure\n// helpers live in the shape layer so the runtime can compute effective addresses\n// without depending on @patterkit/core (which re-exports them).\n// ---------------------------------------------------------------------------\n\n/** Slugify a name into a hyphen-form game id: lowercase, drop apostrophes, runs of\n * other punctuation -> a single hyphen, collapse repeats, no leading / trailing hyphen. */\nexport function gameIdify(text: string): string {\n return text\n .toLowerCase()\n .replace(/['’]/g, \"\")\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/** A valid authored game id: lowercase alphanumerics + hyphens, no leading / trailing hyphen. */\nexport function isValidGameId(gameId: string): boolean {\n return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);\n}\n\n// ---------------------------------------------------------------------------\n// Property names (design: `@wildwinter/app-shell` src/property-names.ts, which\n// holds the same rules as the shell's defaults and argues them).\n//\n// Not house style: the rule is what `@wildwinter/expr` can parse. Its lexer takes\n// an identifier as /[a-zA-Z_][a-zA-Z0-9_]*/ and folds it to lower case, so\n// `@patter.isNight` reaches a property called `isnight`, `@patter.9lives` and\n// `@patter.not` are parse errors, and `@patter.is-night` is not an error at all:\n// it compiles to `@patter.is` MINUS the string \"night\". That last one is why the\n// rule is worth enforcing rather than trusting - it is the only violation that\n// silently means something else.\n//\n// These live here, and not behind an import of the UI kit, because the compiler,\n// the CLI and the JS runtime embedded in game engines all resolve state by them.\n// `packages/model/test/property-name-parity.test.ts` holds them to the shell's.\n// ---------------------------------------------------------------------------\n\n/** The words `@wildwinter/expr` lexes as keywords, so no property may be called one.\n * `packages/model/test/property-name-grammar.test.ts` probes the real parser rather\n * than trusting this copy. */\n// A legal property NAME is a fact about the expression language, not about this\n// model: `not` is reserved because the tokeniser reads it as an operator. Both\n// families kept their own copy of the rule AND of the keyword list, a list\n// neither owned. @wildwinter/expr derives the list from its own tokeniser, so a\n// keyword added there cannot leave a stale copy here.\n//\n// Re-exported so nothing that imports them has to move.\nexport {\n propertyNameify, isValidPropertyName, isCaseOnlyPropertyName, RESERVED_PROPERTY_NAMES,\n} from \"@wildwinter/expr\";\n/** The effective address: the explicit `gameId` if pinned, else derived from `name`. */\nexport function effectiveGameId(entity: { gameId?: string; name: string }): string {\n const g = entity.gameId?.trim();\n return g ? g : gameIdify(entity.name);\n}\n\n// ---------------------------------------------------------------------------\n// Project file (.patterproj) - spec §14, schema §6.\n// ---------------------------------------------------------------------------\n\nexport type PropertyType = \"boolean\" | \"number\" | \"string\" | \"flags\" | \"enum\" | \"quality\";\n\nexport interface PropertyDecl {\n name: string;\n type: PropertyType;\n default?: ScalarValue;\n /**\n * The orthogonal *sharing* axis (spec §7): is this property's value shared across\n * all flows (one world value) or kept per-flow? It does NOT change the reference\n * syntax - sharing is set here, on the declaration, not by a different scope token.\n * The default depends on the scope it is declared in: a **global** property\n * (project `properties` -> `@patter`) defaults to **shared**; a **scene-local**\n * property (scene `sceneProps` -> `@scene`) defaults to **not shared** (per-flow).\n */\n shared?: boolean;\n /**\n * Persistence axis, for **scene-local (`@scene`) properties** (spec §7). Default\n * `false`: the value PERSISTS across scene re-entries (like every other property).\n * `true`: the value is **reseeded to its default on every scene entry** - \"fresh\n * each playthrough\" (Ink's `temp`). Orthogonal to `shared`. Ignored on global\n * (`@patter`) properties, which always persist for the life of the piece.\n */\n temporary?: boolean;\n /** For enum / flags. */\n values?: string[];\n /** For quality: the ORDERED ladder of stage names (the shared engine's quality type, expr 0.4.0).\n * Ordering operators compare by position in this list and `advance()` steps along it, so the order\n * is the meaning - unlike `values`, whose order is presentation only. Default seeds to the first\n * stage when no `default` is declared. */\n stages?: string[];\n /** Free-text author note documenting what this property is for (authoring only; shown as a hint). */\n purpose?: string;\n}\n\n/**\n * A property of a host / world scope (`@world`, `@game`, ...). The same shape the\n * `@wildwinter/scoperegistry` `scopeRegistrySpec` uses, declared structurally here\n * so the model stays free of a runtime dependency. `default` seeds the standalone\n * runtime's self-backed bag (see `HostScopeSpec`); `writable: false` makes the\n * property read-only to the story (validated at compile time).\n */\nexport interface HostScopeDecl {\n name: string;\n type: PropertyType;\n values?: string[];\n /** For quality: the ordered stage ladder (see `PropertyDecl.stages`). */\n stages?: string[];\n default?: ScalarValue;\n writable?: boolean;\n /** Free-text author note documenting the property (authoring only; shown as a hint). */\n purpose?: string;\n}\n\n/** One scope (`token`) in a project's host-scope registry. */\nexport interface HostScopeSpec {\n /** The scope token after `@` (e.g. `\"world\"`). Must not collide with Patter's own\n * `patter` / `scene` / `flow`. */\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: HostScopeDecl[];\n}\n\n/**\n * A project's host-scope registry: the `scopeRegistrySpec` it OWNS (spec\n * design/scope-registry.md §6). Makes `@world` (and any host scope) first-class\n * in a standalone project: the compiler validates references into it, the runtime\n * self-backs it from declaration defaults when no host resolver claims the token,\n * and coverage drives its values. Structurally identical to scoperegistry's\n * `ScopeRegistrySpec` so it threads straight through the compiler.\n */\nexport interface HostScopeRegistry {\n version: number;\n scopes: HostScopeSpec[];\n}\n\n/**\n * A coverage input driver (#159, design/scope-registry.md §7): during a coverage run the harness feeds a\n * host-scope property (`@world.x`) values from `values` so branches gated on external state get exercised.\n * @world is the game-engine seam: @patter / @scene are story-owned and covered for free, so are never driven.\n */\nexport interface CoverageDriver {\n /** The host-scope ref to drive, e.g. `\"@world.phase\"`. */\n ref: string;\n /** `\"initial\"`: set once when each playthrough starts. `\"recurring\"`: re-rolled at choice points so a\n * single run can pass through several world states. */\n kind: \"initial\" | \"recurring\";\n /** For a recurring driver: how often to re-roll at a choice point (default `\"sometimes\"`). */\n cadence?: \"rarely\" | \"sometimes\" | \"often\";\n /** The pool the harness picks from (uniform). Empty = the driver is inert. */\n values: ScalarValue[];\n}\n\n/** A character's grammatical gender, for localisation. Translators need it to inflect the speaker's own\n * lines in gendered languages (adjectives, participles, pronouns), which the source text alone often\n * cannot tell them. Absent means \"not specified\". Authoring-only: it never reaches the runtime bundle,\n * but it IS carried into the localisation handoff formats as translator context (spec §14).\n *\n * Free text, not a closed set: real languages need more than three genders (common/utrum, animate/\n * inanimate, and so on) and translators name them differently. `COMMON_GENDERS` seeds the editor's\n * auto-suggest so the everyday values stay spelled consistently; anything else is still valid. */\nexport type GrammaticalGender = string;\n\n/** The everyday grammatical genders the Cast editor offers as auto-suggest defaults. Not exhaustive and\n * not enforced - a project may use any string (see `GrammaticalGender`). */\nexport const COMMON_GENDERS = [\"male\", \"female\", \"neuter\"] as const;\n\nexport interface CastMember {\n /** Canonical speaker name (matched by a beat's `character`); language-neutral key. */\n name: string;\n /** Localised player-facing name - a localisation id (project-level strings). */\n displayName?: string;\n /** Grammatical gender for translators (see `GrammaticalGender`); absent = not specified. */\n gender?: GrammaticalGender;\n /** Free-text production notes about the character (casting, voice, intent) - authoring only. */\n notes?: string;\n /** The voice actor cast for this character, if known - surfaced in the VO script export (spec §16).\n * Authoring-only (not shipped in the runtime bundle). */\n actor?: string;\n gameData?: GameData;\n}\n\n/** The cast as it reaches a compiled bundle: the player-facing fields only. `notes`, `actor` and\n * `gender` are authoring / production / translation context, and the compiler drops them, so a shipped\n * game never carries a real person's name or a writer's private notes. The compiler copies the shipping\n * fields across explicitly (an allow-list), which is what actually keeps a new authoring field out of\n * the bundle; this type states the resulting contract for anyone reading a bundle. */\nexport type BundleCastMember = Omit<CastMember, \"notes\" | \"actor\" | \"gender\">;\n\n/** A node TYPE that can carry author-defined gameData fields (the gameData schema, project-level).\n * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`. */\nexport type GameDataNodeKind = \"scene\" | \"block\" | \"snippet\" | \"line\" | \"text\" | \"gameEvent\";\n\n/** The value type of a gameData field - the property-type vocabulary plus a multiline-text variant.\n * Drives the inspector's editor widget (text / textarea / number / toggle / enum dropdown). */\nexport type GameDataFieldType = \"text\" | \"multiline\" | \"number\" | \"boolean\" | \"enum\";\n\n/** One author-defined custom field on a node type (host-integration metadata, NOT expression state). */\nexport interface GameDataField {\n /** Field key - also the key a node stores its value under in `gameData` (values are name-keyed). */\n name: string;\n type: GameDataFieldType;\n /** The value used when a node sets nothing. Storage is SPARSE - nodes hold only their overrides, and\n * a reader falls back to this default (so changing it here propagates to every node that didn't set it). */\n default?: ScalarValue;\n /** Allowed values when `type` is \"enum\". */\n values?: string[];\n /** Free-text description of what the field is for - shown as a rollover hint in the inspector. */\n purpose?: string;\n}\n\n/** Author-defined gameData fields, grouped by the node type they attach to (project-level schema). */\nexport type GameDataFields = Partial<Record<GameDataNodeKind, GameDataField[]>>;\n\n// ---------------------------------------------------------------------------\n// Status vocabularies (spec §13) - ordered not-done -> done, project-tailorable.\n// Tracked PER BEAT in the authoring file (`writing` / `recording`).\n// ---------------------------------------------------------------------------\n\nexport interface WritingStatusDecl {\n name: string;\n /**\n * Threshold marker: this status AND every later one classify as \"ready to\n * record\". Declared on exactly one status in the list.\n */\n readyToRecord?: boolean;\n /** Threshold marker: this status and every later one classify as \"ready to ship\". */\n readyToShip?: boolean;\n /** Theme character-palette slot (0-11) for the per-line status badge / inspector swatch (Patterpad #196).\n * A slot (not a hex) so it adapts to light/dark + the colour themes. Absent = no colour (neutral). */\n colour?: number;\n}\n\n/**\n * The default writing-status ladder (used when a project declares none). The\n * FIRST status is the level-0 / absence status: a beat with no recorded status\n * counts as `stub`. Nothing is inferred from content - tracking is opt-in, and a\n * project that ignores statuses reads as all-stub (so its burndown / script\n * export carry no weight, by design - spec §13).\n */\nexport const DEFAULT_WRITING_STATUSES: WritingStatusDecl[] = [\n { name: \"stub\", colour: 0 }, // red\n { name: \"draft 1\", colour: 1 }, // rust\n { name: \"draft 2\", colour: 2 }, // ochre\n { name: \"edited\", readyToRecord: true, colour: 4 }, // green\n { name: \"final\", readyToShip: true, colour: 9 }, // purple (violet)\n];\n\n/** A recording-status rung (#206): like a writing status but with no readiness markers - the recording\n * ladder is ordered missing -> final and carries a picked palette colour for the inspector chip / report. */\nexport interface RecordingStatusDecl {\n name: string;\n /** Theme character-palette slot (0-11) for the inspector chip / report bar. Absent = neutral. */\n colour?: number;\n}\n\n/** The default recording-status ladder (used when a project declares none). The FIRST rung is the\n * level-0 / absence status: a beat with no recorded status counts as `missing`. */\nexport const DEFAULT_RECORDING_STATUSES: RecordingStatusDecl[] = [\n { name: \"missing\", colour: 0 }, // red\n { name: \"scratch\", colour: 2 }, // ochre\n { name: \"recorded\", colour: 4 }, // green\n { name: \"final\", colour: 9 }, // purple\n];\n\n/** The reserved recording status a line takes when it is flagged **needs re-record** (#227): a recorded\n * take exists but is unusable (bad quality, wrong take), so it must be redone. It is NOT a ladder rung -\n * it is a regression flag that MASKS the derived / manual rung for the recording script, the production\n * report, and status browse, so a line \"recorded\" on disk still shows up as work to do. Reserved so it\n * can't collide with an author-declared rung; carries its own alert colour. */\nexport const RERECORD_STATUS = \"rerecord\";\nexport const RERECORD_STATUS_DECL: RecordingStatusDecl = { name: RERECORD_STATUS, colour: 1 }; // orange alert\n\n/** One recording rung mapped to the folder its audio files live in (Audio Folders mode). */\nexport interface RecordingFolder {\n name: string;\n /** Project-relative folder `<audioRoot>/<slug(name)>`, or undefined for the baseline \"not recorded\" rung. */\n folder?: string;\n}\n\n/**\n * Audio Folders (#206): map the recording ladder to its derived folders under a single audio root.\n * Each rung's folder is `<audioRoot>/<slug(name)>`; the FIRST (lowest) rung is the \"not recorded\"\n * baseline and gets NO folder. The single source of truth shared by the indexer, scratch-save, and the\n * manifest so they never disagree. Returns bare rungs (no folders) when `audioRoot` is empty.\n */\nexport function deriveRecordingFolders(\n audioRoot: string | undefined,\n statuses: RecordingStatusDecl[],\n): RecordingFolder[] {\n const root = audioRoot?.trim();\n return statuses.map((s, i) => {\n if (i === 0 || !root) return { name: s.name }; // baseline rung, or no root configured yet\n return { name: s.name, folder: `${root}/${gameIdify(s.name)}` };\n });\n}\n\n/**\n * A class of documentation note (spec §18) - the project-defined vocabulary plus\n * where each class is DELIVERED. The editor (Patterpad) always shows every class;\n * `deliver` governs EXPORTS only. A note inherits down the tree, so a class set\n * on a scene/block flows to its lines (outermost-first).\n */\nexport interface DocumentationClass {\n name: string;\n /**\n * Export channels this class flows to: a list of channel names, or `\"*\"` for\n * all. Omitted/empty = editor-only (e.g. \"writing\"). Built-in channels: `vo`\n * (voice-recording scripts), `loc` (localisation handoff), `editor` (the editable\n * script handed to an outside editor); studios add their own (e.g. `sfx`, `art`),\n * carried now and picked up when that export exists.\n */\n deliver?: string[] | \"*\";\n}\n\n/**\n * The default documentation classes (used when a project declares none). An\n * untyped note (no class) is editor-only by construction; these are the named\n * routes. The vocabulary is project-extensible - a studio replaces this list.\n */\nexport const DEFAULT_DOCUMENTATION_CLASSES: DocumentationClass[] = [\n { name: \"everyone\", deliver: \"*\" }, // every export, and (like all) the editor\n { name: \"vo\", deliver: [\"vo\"] }, // voice-recording scripts\n { name: \"loc\", deliver: [\"loc\"] }, // localisation handoff\n { name: \"editor\", deliver: [\"editor\"] }, // the editable script handed to an outside editor\n];\n// (An editor-only note still exists: leave a note UNTYPED - it surfaces in the editor but no export.)\n\n/** The version-control system a project is kept under (spec §12). Drives the lock-aware vs merge-based\n * write path + the emitted VCS config; chosen at create and switchable in Project Settings. */\nexport type VcsKind = \"git\" | \"perforce\" | \"plastic\" | \"svn\" | \"none\";\n\n/** Spell-check setup for a project (Patterpad #177). */\nexport interface ProjectDictionary {\n /** The active dictionary id - a built-in language (\"en-US\"/\"en-GB\") or an app-level imported Hunspell\n * pair. Absent = derive from the source locale. The imported pair itself lives per-machine (userData),\n * so only the id travels with the project. */\n language?: string;\n /** The project's custom word list - names, places, invented terms - always accepted. Travels with the\n * project (shared via VCS), unlike the per-machine imported dictionaries. */\n words?: string[];\n /** Words the author chose to IGNORE (right-click ▸ Ignore on a flagged word). Distinct from `words`:\n * these aren't vocabulary to add, just tokens to stop flagging in this project (a dialect spelling, a\n * code). Both suppress the squiggle; they differ in intent + where they surface. Travels with the project. */\n ignore?: string[];\n /** Spell-check on/off for this project (default on). */\n enabled?: boolean;\n}\n\n/**\n * Estimating (writing-burndown, spec §13): when a scene is still all guesswork - every status-tracked\n * beat at or below `thresholdStatus` (an unset beat counts as the lowest rung) - the production report\n * REPLACES its actual (placeholder) line count with an estimate, and shares that estimate across the\n * characters appearing in its placeholder lines. Off by default; when off, no estimate appears anywhere.\n * See design/proposals/estimating.md.\n */\nexport interface EstimatingConfig {\n /** Master on/off. Off (or absent) = the report shows pure actuals, no estimate anywhere. */\n enabled: boolean;\n /** Writing-ladder rung NAME: a scene is estimated only when EVERY status-tracked beat is at or below\n * this rung. Absent = the lowest rung. */\n thresholdStatus?: string;\n /** The per-scene estimate (written lines) used when no tag override matches. */\n defaultLines: number;\n /** Tag -> lines overrides. A scene carrying a mapped tag uses that number; if it carries several, the\n * LARGEST wins. */\n tagEstimates?: { tag: string; lines: number }[];\n}\n\nexport interface ProjectFile {\n schema: string; // \"patter/project@0\"\n root?: boolean;\n project: { id: string; name: string; roomKey?: string };\n locales: { default: string; all: string[] };\n /** The authored entry point: the scene (and optional block) a flow starts at when none is given,\n * used by `patter play`, Patterpad's Play, and coverage. Omitted = fall back to the first scene. */\n start?: { scene: string; block?: string };\n /** The authored scene order (scene ids) for navigation. Scenes not listed follow in file order;\n * listed ids that no longer exist are ignored. Omitted = plain file order. Presentation only:\n * it never affects play, which always starts from `start` / an explicit address. */\n sceneOrder?: string[];\n /** Version-control system (spec §12); omitted = unset / none. */\n vcs?: VcsKind;\n /** Project-wide VO mode (spec §16) - one boolean, no per-scene override. */\n voiced?: boolean;\n /** Track audio/recording status (#206): whether the recording-status ladder, Audio Folders, scratch, and\n * the inspector's Audio row + the report's recording breakdown are active. Only meaningful for a `voiced`\n * project (it gates on both). Authoring metadata, editor-only; never reaches the bundle. Omitted = OFF -\n * opt-in even for a voiced project (a voiced story may want voice scripts without tracking recording status). */\n trackAudioStatus?: boolean;\n /**\n * Inline text formatting. When true, authors can mark dialogue / narration / direction /\n * choice-prompt text bold, italic, or bold+italic; it is stored INSIDE the localised strings\n * (and the flow's `direction`) as `<b>…</b>`, `<i>…</i>`, `<bi>…</bi>`, with literal `<`, `>`,\n * `&` escaped to `<` / `>` / `&`. The runtime treats the string as opaque - it is the\n * GAME's job to parse the tags. Default ON (omitted === enabled); set `false` to disable it for a\n * game that renders plain strings and would otherwise show the tags literally. */\n formatting?: boolean;\n /** Autosave: periodically persist the edited scene without an explicit Save. Default ON (omitted ===\n * enabled); set `false` to require manual saves. */\n autosave?: boolean;\n /** Auto Rebuild: recompile the `.patterc` bundle automatically after edits (debounced), so the on-disk\n * build stays current without a manual Publish Bundle. Editor-only; never reaches the bundle. Default OFF\n * (omitted === off) - opt-in, since it writes the bundle on every real change (poor fit if you commit the\n * bundle to a lock-based VCS). The rebuild is deduped (skipped when the compiled bundle is unchanged) and\n * a mid-edit invalid project silently keeps the last good build. */\n autoRebuild?: boolean;\n /** Closed captions (#214): the delimiter pair that wraps non-spoken caption cues inside DIALOGUE\n * lines (e.g. `(sigh)` in `Oh dear. (sigh) What now?`). A game can turn captions off at runtime\n * (`setClosedCaptions(false)`), and the runtime then strips every `open…close` span - delimiters and\n * surrounding whitespace - from line text. Baked into the bundle so the runtime knows the pair;\n * omitted = the default `(` / `)`. Captions are ON by default (full text shown). */\n closedCaptions?: CaptionDelimiters;\n audio?: { scratchStore: string };\n layout?: { flow?: string; strings?: string; authoring?: string };\n /** `bundle`: the compiled `.patterc` output path (relative to the project root,\n * or absolute); default `dist/<project-file-stem>.patterc` (spec §11).\n * `localisation`: how strings ship + are resolved (spec §11).\n * - \"embedded\" (default): every locale's strings live INSIDE the `.patterc`; the runtime resolves\n * them and can switch locale live (`setLocale`).\n * - \"ids\": the `.patterc` carries NO strings; the runtime emits the beat ID for each line and the\n * game looks it up in its own loc system (Export Localisation hands over the language files).\n * `sourceDebug` embeds the SOURCE language too, purely so the build can be played for debugging;\n * the bundle flags it so the runtime can warn it is not a shippable build. */\n export?: { targets?: string[]; bundle?: string; localisation?: { mode: \"embedded\" | \"ids\"; sourceDebug?: boolean } };\n properties?: PropertyDecl[];\n /** Host / world scopes the project declares (`@world`, `@game`, ...): makes them first-class so the\n * compiler validates references into them, the runtime self-backs them from defaults when no host\n * resolver is bound, and coverage drives their values. Omitted = no host scopes (`@world.x` is then a\n * compile error). See design/scope-registry.md §6. */\n scopeRegistry?: HostScopeRegistry;\n /** Where the game's shared scopes folder is, relative to this project file's folder\n * (`\"../../shared/game-scopes\"`), for a folder the walk-up would not find. Omitted = walk up from the\n * project to the first `game-scopes/`, stopping at the version-control root; none found = the project\n * works alone. A path that doesn't exist is a project error. See the Patter site's World properties\n * page and patterkit/design/shared-scopes.md. Editor and compiler only; never reaches the bundle. */\n gameScopes?: string;\n /** Coverage input drivers (#159): values to feed host scopes (`@world`) during a coverage run so\n * externally-gated branches get exercised. Authoring-only (never reaches the runtime bundle). */\n coverageDrivers?: CoverageDriver[];\n cast?: CastMember[];\n gameDataFields?: GameDataFields;\n /** Ordered writing-status ladder (not-done -> done); default `DEFAULT_WRITING_STATUSES`. */\n writingStatuses?: WritingStatusDecl[];\n /** Ordered recording-status ladder (not-done -> done); default `DEFAULT_RECORDING_STATUSES`. */\n recordingStatuses?: RecordingStatusDecl[];\n /** Audio Folders mode (#206): the single project-relative root under which each rung's audio lives, in\n * an auto-derived subfolder `<audioRoot>/<slug(statusName)>/` (see `deriveRecordingFolders`). Only\n * meaningful when `audioFolders` is on. Authoring metadata; never reaches the bundle. */\n audioRoot?: string;\n /** Audio Folders mode (#206): when true, a dialogue line's recording status is DERIVED from which\n * rung's derived folder (under `audioRoot`) holds its `<beatId>.wav|mp3` (top-down the ladder, implicit\n * \"missing\"), instead of being set manually. Authoring metadata; never reaches the bundle. Default off. */\n audioFolders?: boolean;\n /** Scratch recording (Patterpad #224): the recording-status rung whose folder is the source/dest for\n * in-app \"record scratch\" takes. When set (and `audioFolders` on), Patterpad offers to record a quick\n * scratch take into this rung's folder for any line at or below this rung. Authoring metadata, editor-\n * only; never reaches the bundle. Unset = scratch recording off. */\n scratchStatus?: string;\n /** Spell-check setup (Patterpad #177): the active dictionary language + the project's custom word list +\n * an on/off flag. Source-language-only, authoring metadata - it never reaches the runtime bundle. */\n dictionary?: ProjectDictionary;\n /**\n * Estimating (spec §13 writing burndown): replace a still-guesswork scene's actual (placeholder)\n * line count with an estimate in the production report. Off by default. See `EstimatingConfig` and\n * design/proposals/estimating.md.\n */\n estimating?: EstimatingConfig;\n /** Documentation-note classes + their export routing (spec §18); default `DEFAULT_DOCUMENTATION_CLASSES`. */\n documentationClasses?: DocumentationClass[];\n}\n\n// ---------------------------------------------------------------------------\n// Locale file (.patterloc) - schema §4. String text only.\n// ---------------------------------------------------------------------------\n\nexport interface LocaleFile {\n schema: string; // \"patter/strings@0\"\n /** Scene id this file's strings belong to (or a project-level marker). */\n scene: string;\n locale: string;\n default?: boolean;\n /** beatId -> text. */\n strings: Record<string, string>;\n}\n\n/** The `scene` marker for a project-level loc shard (`loc/<locale>/_project.patterloc`): strings that\n * aren't tied to a scene beat - currently cast display names, later project title / UI strings. */\nexport const PROJECT_LOCALE_SCENE = \"@project\";\n\n/** The project-level loc-string key for a cast member's player-facing name, e.g. `cast:BARKEEP`.\n * Namespaced so it can't collide with opaque beat ids. The default-locale value is seeded from the\n * CastMember's `displayName`; the runtime resolves a character's shown name through this key. */\nexport function castStringKey(name: string): string {\n return `cast:${name}`;\n}\n\n// ---------------------------------------------------------------------------\n// Authoring file (.patterx) - schema §5. All edit/production metadata.\n// ---------------------------------------------------------------------------\n\n/** Typed documentation annotation line (spec §18). */\nexport interface DocLine {\n /** The documentation CLASS (a `DocumentationClass.name`) - routes export\n * visibility. Omitted = editor-only (not delivered to any export). */\n type?: string;\n text: string;\n}\n\n/** One message in a threaded editor comment: who wrote it, when (ISO timestamp), and the text. */\nexport interface CommentMessage {\n author: string;\n ts: string;\n body: string;\n /** A TOMBSTONE: the words are gone, the turn in the conversation is not.\n *\n * Removing a reply outright would renumber the argument around it, so what is\n * left records who spoke and when, and that they withdrew it. The `body` is\n * EMPTIED rather than kept and hidden, because \"deleted\" has to mean gone from\n * a file that lives in version control.\n *\n * This is the one message allowed an empty body: `saveSceneComments` prunes\n * empty messages so a cancelled composer leaves nothing behind, and without\n * this flag a tombstone would be pruned on the way to disk. */\n deleted?: boolean;\n}\n\n/** A sub-text range a comment is pinned to, within its anchor node's say text (#148). Offsets are\n * character positions over the rendered (plain) source text - inline formatting is marks, not part of\n * the count. `quote` is the text the range covered when made: the editor re-anchors by FINDING it (the\n * offsets are just a hint), and a thread whose quote no longer exists is shown demoted, not lost. */\nexport interface CommentRange {\n from: number;\n to: number;\n quote: string;\n}\n\n/** Threaded editor comment (collaboration), anchored to a stable beat/node id. `messages[0]` is the\n * opener; replies follow in order (each carries its own author + timestamp, Word/Docs style).\n * `range` pins it to a span of the node's text (absent = the whole beat). `resolved` archives the\n * thread - hidden in the editor unless \"show resolved comments\" is on. */\nexport interface Comment {\n id: string;\n /** Anchored to a stable beat/node id. */\n anchor: string;\n /** A sub-text span within the anchor's text; absent = a whole-beat comment. */\n range?: CommentRange;\n resolved?: boolean;\n messages: CommentMessage[];\n}\n\n/** A \"suggest a rewrite\" proposal for a single say/prose beat or option prompt (review flow,\n * design/proposals/suggest-rewrite.md). Whole-beat anchored: `baseline` is the say text WHEN suggested\n * (the \"before\" + the drift detector - if it no longer matches the live text, the line changed since and\n * the suggestion is shown stale), `proposed` is the replacement. Accept overwrites the beat's say text;\n * both accept and reject set `resolved` (archived) + `outcome` (a light audit trail). Stored in the\n * authoring shard, never in the flow text, so downstream tools ignore it.\n *\n * Beyond the text, a suggestion may also propose a new speaker, a new direction, or cutting the beat\n * (the editable-script handoff, design/proposals/editable-script-handoff.md). Each such part carries its\n * own baseline, so each is stale-checked on its own; accept applies whichever parts are present. A\n * suggestion that only changes the speaker or direction, or only cuts, keeps `proposed` equal to\n * `baseline`. */\nexport interface Suggestion {\n id: string;\n /** The say/prose beat's stable id. */\n anchor: string;\n /** The say text at the moment this was suggested (the diff \"before\" + staleness check). */\n baseline: string;\n /** The proposed replacement say text. */\n proposed: string;\n /** The primary author: with several (`authors`), the one with the most changes. */\n author: string;\n /** Everyone whose changes this suggestion carries, when there is more than one (an editor's file with\n * several people's tracked changes on one line). `author` stays the primary, so a reader that only\n * knows `author` keeps working. */\n authors?: string[];\n ts: string;\n /** A proposed new speaker (a known cast member's name), and the speaker when suggested. */\n proposedCharacter?: string;\n baselineCharacter?: string;\n /** A proposed new direction (the parenthetical), and the direction when suggested (\"\" for none). */\n proposedDirection?: string;\n baselineDirection?: string;\n /** A proposal to cut the beat (an editor emptied its line). Accept marks it cut. */\n proposedCut?: boolean;\n /** Where the suggestion came from, when it arrived in an editable-script handoff: the handoff and the\n * line's marker in it. Drives the review's \"from handoff\" filter, and lets a re-import of the same\n * handoff replace its still-open suggestions instead of stacking duplicates. */\n handoff?: SuggestionSource;\n /** Accepted or rejected -> archived (hidden unless \"show resolved suggestions\" is on). */\n resolved?: boolean;\n outcome?: \"accepted\" | \"rejected\";\n}\n\n/** The handoff a suggestion arrived in (see `Suggestion.handoff`). */\nexport interface SuggestionSource {\n /** The handoff's id (\"H-7Q2K\"). */\n id: string;\n /** The line's marker code in that handoff (\"K7Q2M\"). */\n marker: string;\n}\n\nexport interface EditRecord {\n modifiedAt?: string;\n by?: string;\n /** Per-locale localisation date -> staleness when source modifiedAt is later. */\n localisedAt?: Record<string, string>;\n}\n\nexport interface AuthoringFile {\n schema: string; // \"patter/authoring@0\"\n comments?: Comment[];\n /** \"Suggest a rewrite\" review proposals (design/proposals/suggest-rewrite.md). */\n suggestions?: Suggestion[];\n /** Typed documentation, keyed by node/beat id (spec §18). */\n documentation?: Record<string, DocLine[]>;\n /**\n * Writing status, keyed by BEAT id - a value from the project's writing-status\n * enumeration (spec §13; `writingStatuses`, default `DEFAULT_WRITING_STATUSES`).\n * Tracked on the source language only (translation staleness is `localisedAt`).\n */\n writing?: Record<string, string>;\n /**\n * Recording status, keyed by BEAT id - a value from the project's recording-status\n * enumeration (spec §13/§16; `recordingStatuses`). Single-locale by design: games\n * recording VO in more than one language are rare (and absent at indie level); if\n * ever needed, a per-locale extension comes later.\n */\n recording?: Record<string, string>;\n /**\n * Audio-relationship metadata, keyed by BEAT id. Native (recording) language\n * only - like recording status, VO is single-language by design.\n */\n audio?: Record<string, unknown>;\n /** Author trail + edit/localisation dates, keyed by beat/node id. */\n edits?: Record<string, EditRecord>;\n /**\n * **Cut** content (spec §13): scene or beat ids removed from the production\n * but kept in source. Orthogonal to status (cut is not a degree of doneness);\n * reports exclude cut content from counts / estimates / coverage and surface\n * it as a separate \"cut: N\" figure so a removal is visible, not vanished.\n */\n cut?: Record<string, boolean>;\n /**\n * **Needs re-record** (#227): dialogue-line ids whose recorded take is unusable and must be redone\n * (bad quality, wrong take, misread). Orthogonal to the recording ladder - a flagged line keeps its\n * audio on disk, but its recording status is MASKED to the reserved `rerecord` status for the recording\n * script / report / status browse (see `RERECORD_STATUS`), so a \"recorded\" line still reads as work.\n * Authoring-only; never compiled into a bundle.\n */\n rerecord?: Record<string, boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Editable-script handoff record (design/proposals/editable-script-handoff.md §5.2).\n// One per export, at `handoffs/<id>.json` in the project: what was sent, to whom,\n// when, and each editable line's text AS SENT, which every check on reimport\n// compares against. Authoring-only; never compiled into a bundle.\n// ---------------------------------------------------------------------------\n\nexport const HANDOFF_SCHEMA = \"patter/handoff@0\";\n\nexport interface HandoffFile {\n schema: typeof HANDOFF_SCHEMA;\n /** \"H-7Q2K\": shown in the document's page header, so a returned file finds its record. */\n id: string;\n createdAt: string;\n createdBy: string;\n /** The file format sent. */\n format: \"docx\";\n /** Who it went to, as typed at export (optional). */\n recipient?: string;\n /** The scene ids exported, in document order. */\n range: { scenes: string[] };\n options: {\n /** Which notes rode along: a documentation channel name (\"editor\"), or \"all\". */\n notes: string;\n /** Each line's writing status shown as a tag. */\n status: boolean;\n /** A cast page before the script. */\n cast: boolean;\n };\n /** Every editable line, keyed by its marker code (\"K7Q2M\"). */\n lines: Record<string, HandoffLine>;\n /** The whole document in order: context rows tied to the node they describe, and each editable line\n * by marker. Lets the importer align what came back with what was sent, and anchor a comment made on\n * a context row to its node. */\n skeleton: HandoffRow[];\n /** One entry per reimport, appended. */\n imports?: HandoffImport[];\n /** Set when the handoff is finished with; a closed handoff no longer appears in review filters. */\n closed?: boolean;\n}\n\n/** An editable line as sent. */\nexport interface HandoffLine {\n /** The beat id (a line, a narration beat, or an option prompt). */\n id: string;\n kind: \"line\" | \"narration\" | \"option\";\n /** The speaker and direction as sent (spoken lines only). */\n character?: string;\n direction?: string;\n /** The text as sent: the baseline for every comparison on reimport. */\n baseline: string;\n}\n\n/** One row of the sent document, in order. */\nexport type HandoffRow =\n | { kind: \"box\"; marker: string }\n | { kind: \"scene\" | \"block\" | \"condition\" | \"group\" | \"else\" | \"jump\" | \"gameEvent\" | \"note\"; node: string; text: string };\n\n/** A record of one reimport. */\nexport interface HandoffImport {\n at: string;\n by: string;\n /** SHA-256 of the returned file, so a second import of the same file is recognisable. */\n fileHash: string;\n counts: { changed: number; unchanged: number; stale: number; comments: number; problems: number };\n}\n\n// ---------------------------------------------------------------------------\n// Export bundle (schema §10) - the compiled artefact the runtimes load.\n// Conditions/effects are pre-derived `{ src, ast }` envelopes (not src strings);\n// authoring is stripped; the project-wide voiced flag is carried; locales assembled.\n// ---------------------------------------------------------------------------\n\n/** A compiled expression envelope: canonical source + pre-derived tagged-tuple AST. */\nexport interface Expression {\n src: string;\n ast: AstNode;\n}\n\nexport type CompiledEffect =\n { kind: \"set\"; target: string; value: Expression };\n\nexport interface CompiledSnippet {\n id: string;\n type: \"snippet\";\n condition?: Expression;\n beats?: Beat[]; // beats carry no expressions\n onEnter?: CompiledEffect[];\n onExit?: CompiledEffect[];\n gameData?: GameData;\n tags?: string[]; // author tags (#215), accumulated down the tree at runtime\n jump?: Jump;\n secretUntilEligible?: boolean;\n /** Option-position: repeatable (spec §5). Default false = once-only. */\n sticky?: boolean;\n /** Option-position: the choice's fallback, auto-followed when last (spec §5). */\n fallback?: boolean;\n}\n\nexport interface CompiledGroup {\n id: string;\n type: \"group\";\n condition?: Expression;\n /** Default (omitted) = `\"run\"`. */\n selector?: Selector;\n /** Selector cursor shared across flows (default false = per-flow). */\n shared?: boolean;\n options?: SequenceOptions;\n children: Array<CompiledGroup | CompiledSnippet>;\n gameData?: GameData;\n tags?: string[]; // author tags (#215)\n /** Option-position fields (spec §5) - only when a direct child of a `choice`. */\n prompt?: PromptBeat;\n secretUntilEligible?: boolean;\n /** Repeatable (spec §5). Default false = once-only. */\n sticky?: boolean;\n /** The choice's fallback, auto-followed when last (spec §5). */\n fallback?: boolean;\n}\n\nexport interface CompiledBlock {\n id: string;\n type: \"block\";\n name: string;\n /** Host-facing address (spec §6); the runtime resolves it to `id`. Absent = derived from `name`. */\n gameId?: string;\n children: Array<CompiledGroup | CompiledSnippet>;\n gameData?: GameData;\n tags?: string[]; // author tags (#215)\n}\n\nexport interface CompiledScene {\n id: string;\n type: \"scene\";\n name: string;\n /** Host-facing address (spec §6); the runtime resolves it to `id`. Absent = derived from `name`. */\n gameId?: string;\n gameData?: GameData;\n tags?: string[]; // author tags (#215)\n onEntry?: CompiledEffect[];\n sceneProps?: PropertyDecl[];\n blocks: CompiledBlock[];\n}\n\nexport interface Bundle {\n schema: string; // \"patter/bundle@0\"\n /** `hash` fingerprints the WHOLE bundle (binds saves, gates staleness); `structureHash` is the same\n * fingerprint with the string tables left out, so same structureHash + a different hash = a\n * text-only edit, safe to hot-swap in place (live bundle refresh). */\n content: { project: string; version?: string; hash?: string; structureHash?: string };\n voiced: boolean; // project-wide VO mode (spec §16)\n locales: { default: string; included: string[] };\n /** Player-facing cast only: the compiler strips notes / actor / gender (see `BundleCastMember`). */\n cast?: BundleCastMember[];\n properties?: PropertyDecl[];\n /** Host / world scope declarations, baked from the project so the runtime can self-back a declared\n * scope (`@world`, ...) when no host resolver claims its token. Absent = no host scopes. */\n scopeRegistry?: HostScopeRegistry;\n /** Other engines' game-wide scopes the content names (`story`), sorted: the family's shared vocabulary,\n * opaque to the compiler and never self-backed. The engine reports when the game has not registered\n * one. Absent when none. */\n externalScopes?: string[];\n gameDataFields?: GameDataFields;\n scenes: Record<string, CompiledScene>;\n /** locale -> (beatId -> text). In \"embedded\" localisation this carries every included locale; in \"ids\"\n * it is EMPTY (the runtime emits beat IDs), unless `localisation.sourceDebug` embedded the source locale\n * for debug playback. `content.hash` is computed over the FULL strings regardless, so the staleness gate\n * is unaffected. */\n strings: Record<string, Record<string, string>>;\n /** How strings ship + resolve (spec §11). Absent = \"embedded\" (back-compat default): the runtime resolves\n * `strings` per locale. \"ids\": the runtime emits beat IDs and the game localises them itself; `sourceDebug`\n * means the source locale is embedded purely for debug playback and the runtime should flag the build as\n * not shippable. */\n localisation?: { mode: \"embedded\" | \"ids\"; sourceDebug?: boolean };\n /** Closed-caption delimiters baked from the project (#214). Absent = the default `(` / `)`; the\n * runtime strips spans between them from line text when a game disables captions. */\n closedCaptions?: CaptionDelimiters;\n}\n\n/** Closed-caption configuration (#214). `open`/`close` wrap a caption cue inside a dialogue line (both\n * non-empty; they MAY be the same token, e.g. `*…*`). `character` names a cast member whose lines are a\n * pure caption: when captions are off, ALL of that character's dialogue (and its speaker label) is\n * omitted - delimiters or not - leaving a silent line that still fires (so audio plays). Absent / empty\n * `character` resolves to the default `SFX` (you \"disable\" it simply by never using that speaker). */\nexport interface CaptionDelimiters {\n open: string;\n close: string;\n character?: string;\n}\n\n/** The default caption delimiters when a project pins none: square brackets, the closed-captioning\n * convention for non-speech cues. Round brackets are deliberately NOT the default: `(` at the start of a\n * line opens a performance direction in the editor, so it would shadow a caption cue there. */\nexport const DEFAULT_CAPTION_DELIMITERS: CaptionDelimiters = { open: \"[\", close: \"]\" };\n\n/** The default caption character: a cast member named `SFX` whose lines are pure captions (omitted when\n * captions are off). Applies even to a project that pins no `closedCaptions`. */\nexport const DEFAULT_CAPTION_CHARACTER = \"SFX\";\n\n// ---------------------------------------------------------------------------\n// Save format: `patter/save@0` - the FAMILY's contract, not any one runtime's.\n//\n// Every Patterplay runtime (JS, Unity, Unreal, Godot) writes exactly this shape and reads it, so a\n// save crosses engines: a game that saves from a web build loads in Godot, a Patterpad Play-window\n// save loads in Unity. The JS reference is canonical (its output IS this document), and the shape\n// lives HERE rather than in the runtime so that the runtime implements a type it does not get to\n// invent - the rule the Storylet Engine holds its four runtimes to, and the reason its saves crossed\n// first time while ours crossed nowhere: three ports, three key namings, and nothing that said which\n// was right (from-storylets/save-shape-across-engines, 2026-09-03; design/patter-schema.md 9).\n//\n// Keys are camelCase literals, values are bare scalars, and semantic parity is what the conformance\n// corpus pins - byte parity is not required, key-path parity is.\n//\n// Version 3 (the one-registry model): property values are the game's ScopeRegistry's, not the\n// engine's. A save holds only what is NOT a property (cursors, PRNGs, visits, selectors), plus, when\n// the engine made its own registry (a standalone game), that registry's values under `registry`. A\n// game that passed a registry saves it once itself. Version 2 saves still load on every runtime:\n// their property sections move into the registry under the keys below.\n//\n// Registry keys (identical on every runtime, since they are in the save): `patter` for the shared\n// globals; `patter/scene/<sceneId>` for a scene's shared `@scene` props; `patter/flow/<flowId>/patter`\n// and `patter/flow/<flowId>/scene/<sceneId>` for a flow's own. Ids escape `%` as `%25` and `/` as `%2F`.\n// A self-backed `@world` (no resolver bound) is a stored property too, under `world`.\n// ---------------------------------------------------------------------------\n\nexport const SAVE_SCHEMA = \"patter/save@0\";\n/** The `save.version` inside the envelope. Bumped only when a reader would MISREAD an older save. */\nexport const SAVE_VERSION = 3;\n\n/** The tagged envelope a host stores: `serializeState` writes it, `deserializeState` refuses anything else. */\nexport interface SaveEnvelope {\n schema: typeof SAVE_SCHEMA;\n save: SaveGame;\n}\n\n/** A registry's values: registry key -> property name -> value (`{ patter: { gold: 7 } }`). */\nexport type EngineSave = Record<string, Record<string, ScalarValue>>;\n\n/** Serialised `sequence` selector visit state for one group (spec §4 / §7). Every key is OPTIONAL and\n * present only once the selector has used it: `seq` after the first sequential pick, `bag` once a\n * shuffle has drawn, `last` once there is a no-immediate-repeat memory. A port that stores \"started\"\n * flags derives them from key presence rather than writing them. */\nexport interface SelectorSnapshot {\n seq?: number;\n bag?: string[];\n last?: string;\n}\n\n/** One entry on a flow's continuation stack: a position within a container's children. */\nexport interface StackFrame {\n sceneId: string;\n /** A block id or a run-group id (both are sequential containers). */\n containerId: string;\n index: number;\n /** SNAPSHOT-ONLY (never on a live frame): the id of the child at `index` when the save was taken, so a\n * restore against an EDITED bundle re-finds the position by id. Absent when the frame sat at its\n * container's end (no next child), or in an older save. */\n nextId?: string;\n}\n\n/** The prompt of a saved choice option: the choice text as a structured line/text beat. */\nexport interface SavedChoicePrompt {\n kind: \"line\" | \"text\";\n text: string;\n character?: string;\n characterName?: string;\n direction?: string;\n}\n\n/** One option of a pending choice, saved VERBATIM: re-deriving on load would re-evaluate conditions. */\nexport interface SavedChoiceOption {\n id: string;\n prompt?: SavedChoicePrompt;\n eligible: boolean;\n gameData?: GameData;\n}\n\n/** A pending choice as saved: the option set the player was shown, replayed on load (schema 9.3). */\nexport interface SavedChoice {\n groupId: string;\n options: SavedChoiceOption[];\n}\n\n/** The flow's execution position: where it is, and what it is waiting on. */\nexport interface FlowCursor {\n flowEnded: boolean;\n currentSceneId: string | null;\n /** The continuation stack (call frames + the active block run), root first. */\n stack: StackFrame[];\n activeSnippetId: string | null;\n beatIndex: number;\n /** Present (non-null) iff the flow is stopped at a choice. */\n pendingChoice: SavedChoice | null;\n /** The chosen option owning a prompt still to be replayed (a save taken between choose + advance).\n * Optional / null in older saves -> no pending prompt. */\n pendingPromptOwnerId?: string | null;\n /** The prompt still to be replayed, exactly as the choice SHOWED it (text and speaker fields, the same\n * shape as a saved option's `prompt`), so the replay after a load says what the player saw. Written only\n * alongside a `pendingPromptOwnerId` (the choose -> advance window, with `replayPromptOnChoose`), and\n * absent otherwise. A save without it (written before Patterplay 0.18.0) resolves the owner's prompt beat\n * when it is delivered, as before. */\n pendingPrompt?: SavedChoicePrompt;\n /** This flow's (non-shared) selector cursors, keyed by group id. */\n selectors: Record<string, SelectorSnapshot>;\n}\n\n/** The serialised cursor + PRNG + visits of a single flow. Its properties are the registry's. */\nexport interface FlowSnapshot {\n /** This flow's built-in PRNG position (mulberry32 state, uint32; older saves may carry it signed). */\n rngState: number;\n /** This flow's per-node entry counts (node id -> times entered by this flow). */\n visits: Record<string, number>;\n cursor: FlowCursor;\n}\n\n/** A full resumable save-game (version 3): everything that is not a property, plus the registry's\n * values when the engine made its own registry. */\nexport interface SaveGame {\n version: 3;\n /** The engine's own registry's values, keyed by registry key: present only when the engine made the\n * registry itself (a standalone game). A game that passed a registry saves it once, beside this. */\n registry?: EngineSave;\n /** World-wide per-node entry counts (node id -> times entered by any flow). */\n sharedVisits: Record<string, number>;\n /** Shared selector cursors (node id -> snapshot) for `shared` memoried selectors. */\n sharedSelectors: Record<string, SelectorSnapshot>;\n /** Each live flow's snapshot, keyed by flow id. */\n flows: Record<string, FlowSnapshot>;\n}\n\n/** A version 2 flow snapshot, which carried the flow's property values itself. Still read. */\nexport interface FlowSnapshotV2 extends FlowSnapshot {\n /** The NOT-shared `@patter` globals, under token \"patter\". */\n scopes: EngineSave;\n /** Per-scene NOT-shared `@scene` bags (scene id -> name -> value). */\n sceneBags: Record<string, Record<string, ScalarValue>>;\n}\n\n/** A version 2 save, from before the registry held the properties. Every runtime still reads it: its\n * property sections move into the registry (under the keys in the header above) as it loads. */\nexport interface SaveGameV2 {\n version: 2;\n /** Shared `@patter` globals (owned scope \"patter\"). */\n shared: EngineSave;\n sharedVisits: Record<string, number>;\n sharedSelectors: Record<string, SelectorSnapshot>;\n /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value). */\n stageBags: Record<string, Record<string, ScalarValue>>;\n flows: Record<string, FlowSnapshotV2>;\n}\n","// ---------------------------------------------------------------------------\n// Author tags (#215): a cross-cutting label layer baked into the bundle.\n//\n// A node's *accumulated* tags are the union of its own and every ancestor's,\n// ordered outermost-first (scene → block → group(s) → snippet → beat) and\n// deduped. That accumulation is purely structural, it depends only on where a\n// node sits in the tree, not on play state, so it's precomputed ONCE at engine\n// load into a flat `id -> string[]` index and read back as O(1) lookups for both\n// the delivered step `tags` and the `tagsFor*` accessors.\n// ---------------------------------------------------------------------------\n\nimport type { Bundle, CompiledGroup, CompiledSnippet } from \"@patterkit/model\";\n\nfunction dedupe(tags: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const t of tags) if (!seen.has(t)) { seen.add(t); out.push(t); }\n return out;\n}\n\n/**\n * Map every node id (scene / block / group / snippet / beat, prompt beats included) to its accumulated\n * tags. Node ids are globally unique within a project (the validator enforces\n * it), so one flat map suffices. Nodes with no tags anywhere up the chain map to\n * an empty array.\n */\nexport function buildTagIndex(bundle: Bundle): Map<string, string[]> {\n const index = new Map<string, string[]>();\n\n const visit = (node: CompiledGroup | CompiledSnippet, inherited: string[]): void => {\n const acc = dedupe([...inherited, ...(node.tags ?? [])]);\n index.set(node.id, acc);\n // An option's prompt beat is a beat like any other: its own tags plus the option's. Left out, a\n // replayed prompt and the outline's prompt lost every tag (all four runtimes, until 2026-10).\n if (node.type === \"group\" && node.prompt) index.set(node.prompt.id, dedupe([...acc, ...(node.prompt.tags ?? [])]));\n if (node.type === \"group\") {\n for (const child of node.children) visit(child, acc);\n } else {\n for (const beat of node.beats ?? []) index.set(beat.id, dedupe([...acc, ...(beat.tags ?? [])]));\n }\n };\n\n for (const scene of Object.values(bundle.scenes)) {\n const sceneAcc = dedupe(scene.tags ?? []);\n index.set(scene.id, sceneAcc);\n for (const block of scene.blocks) {\n const blockAcc = dedupe([...sceneAcc, ...(block.tags ?? [])]);\n index.set(block.id, blockAcc);\n for (const child of block.children) visit(child, blockAcc);\n }\n }\n\n return index;\n}\n","// ---------------------------------------------------------------------------\n// @patterkit/runtime - the reference runtime.\n//\n// An `Engine` is the world + flow manager: it owns the compiled Bundle, the\n// shared state (shared `@patter` globals + shared `@scene` props + host foreign\n// scopes), and a set of named **flows**. All *play* happens on a `Flow` handle\n// (`engine.openFlow(id, ...)`): a flow has its own execution cursor, its own PRNG,\n// and its own copy of the NOT-shared state (per-flow `@patter` globals + per-flow\n// `@scene` props). Multiple flows run concurrently and independently - a flow is\n// addressed explicitly (`alice.advance()`), so there is no ambient \"current flow\".\n//\n// Scopes are just two tokens - `@patter` (global; bare `@name`) and `@scene`\n// (scene-local) - with an orthogonal per-property `shared` flag (default: shared\n// for `@patter`, per-flow for `@scene`). So each token spans two storage areas:\n// the shared half lives on the engine, the per-flow half on the flow; a read/write\n// routes by the property's `shared` flag. (Mirrors Storylet Studio's @world/@site.)\n//\n// A flow plays the select-one-child model (spec §4): a block branches one\n// eligible child; a group runs its selector (branch / sequence / choice -\n// `sequence` covers order x exhaust); a `choice` group stops for the host; a snippet runs onEnter, delivers\n// beats, runs onExit, follows its jump. Falling off the end ends the flow.\n// Cross-flow jumps are not a thing - the host switches flows.\n//\n// Every property bag lives in ONE ScopeRegistry per game (the one-registry model): the\n// game hands the engine its registry (`options.registry`) or the engine makes its own\n// and acts as its own game. `@patter` is registered under `patter`; the per-flow and\n// per-scene bags under keys starting `patter/` (see `keys` below), which no expression\n// can name. `engine.saveGame()` / `loadGame()` snapshot + restore what is NOT a property:\n// cursors, PRNGs, visits, and selectors. The registry's values ride in `saveGame()` only\n// when the engine made the registry itself; otherwise the game saves the registry once.\n// ---------------------------------------------------------------------------\n\nimport { evaluate, deserialiseAst, makePrng, toUint32 } from \"@wildwinter/expr\";\nimport type { ScalarValue, EvalContext, ExprNode } from \"@wildwinter/expr\";\nimport { matchedSpecificity as scoreSpecificity, type EvalTruthy } from \"@wildwinter/expr-specificity\";\nimport { ScopeRegistry } from \"@wildwinter/scoperegistry\";\nimport { defaultFor, PropertyBag } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, PropertyRow, ScopeDeclaration, ScopeResolver } from \"@wildwinter/scoperegistry\";\nimport { patterDialect, interpolate, splitRef, stripCaptions } from \"@patterkit/dialect\";\nimport { walkNodes, effectiveGameId, castStringKey, DEFAULT_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from \"@patterkit/model\";\nimport { buildTagIndex } from \"./tags.js\";\nimport { SAVE_VERSION } from \"@patterkit/model\";\nimport type {\n EngineSave, SelectorSnapshot, StackFrame, SavedChoice, SavedChoicePrompt, FlowSnapshot, SaveGame, SaveGameV2,\n Bundle, CompiledScene, CompiledBlock, CompiledGroup, CompiledSnippet,\n CompiledEffect, Beat, LineBeat, TextBeat, GameData, Expression, PropertyDecl, PropertyType, Jump, HostScopeDecl,\n} from \"@patterkit/model\";\n\ntype SelectableNode = CompiledGroup | CompiledSnippet;\n\n// Compiled expressions are immutable, so each one's AST is deserialised once -\n// per evaluation was the engine's hottest path (every condition / effect / slot).\nconst astCache = new WeakMap<Expression, ExprNode>();\n\n// The save shape is the FAMILY's contract and lives in @patterkit/model (`SaveGame` and friends, with\n// the reasoning). Re-exported here so `import type { SaveGame } from \"@patterkit/runtime\"` keeps\n// working; this engine's saveGame() output IS that document, and every port writes the same.\nexport type {\n EngineSave, SelectorSnapshot, StackFrame, SavedChoice, SavedChoiceOption, SavedChoicePrompt,\n FlowCursor, FlowSnapshot, SaveGame, SaveEnvelope, SaveGameV2, FlowSnapshotV2,\n} from \"@patterkit/model\";\n\n/** The owner label on everything this engine registers: named in a clash error and carried on\n * the registry's examiner rows, so one inspector can group a combined game by engine. */\nconst OWNER = \"Patter\";\n\n/** The registry keys this engine stores its instance bags under. An id is escaped (`%` and `/`)\n * so a flow named `npc/bob` cannot collide with another flow's scene. Every runtime writes the\n * same keys: they are in the save. */\nconst esc = (id: string): string => id.replace(/%/g, \"%25\").replace(/\\//g, \"%2F\");\n/** Internal: marks a hotSwap replacement of a standalone engine, which shares its predecessor's registry\n * but is still its own game (it self-backs host scopes and saves the registry's values). */\nconst OWNS_REGISTRY = Symbol(\"ownsRegistry\");\ntype InternalOptions = EngineOptions & { [OWNS_REGISTRY]?: boolean };\n\nconst keys = {\n /** A scene's SHARED `@scene` props (one bag per scene, every flow's). */\n stage: (sceneId: string): string => `patter/scene/${esc(sceneId)}`,\n /** Everything one flow registers starts with this. */\n flow: (flowId: string): string => `patter/flow/${esc(flowId)}/`,\n /** A flow's NOT-shared `@patter` globals. */\n flowGlobals: (flowId: string): string => `patter/flow/${esc(flowId)}/patter`,\n /** A flow's NOT-shared `@scene` props for one scene. */\n flowScene: (flowId: string, sceneId: string): string => `patter/flow/${esc(flowId)}/scene/${esc(sceneId)}`,\n};\n\n/** What `Flow.advance()` surfaces to the host at each stop. */\nexport type StepResult =\n | { type: \"line\"; id: string; text: string; character?: string; characterName?: string; direction?: string; gameData?: GameData; tags?: string[] }\n | { type: \"text\"; id: string; text: string; gameData?: GameData; tags?: string[] }\n | { type: \"gameEvent\"; id: string; gameData?: GameData; tags?: string[] }\n | { type: \"choice\"; groupId: string; options: ChoiceOption[] }\n | { type: \"end\" };\n\n// --- Static structure introspection (editor / dev tooling) -------------------\n// A read-only view of the AUTHORED tree (scenes -> blocks -> groups/snippets -> beats), for dev\n// tools that build against the writer's structure (e.g. an Unreal Sequencer of subsequences per\n// beat). Static: no flow, no play state. Per-beat data mirrors what a StepResult would carry\n// (source text, author gameData, accumulated tags), read at the default locale.\n\n/** One beat's static data - the same shape a delivered step carries, resolved at the source locale. */\nexport interface BeatInfo {\n id: string;\n kind: \"line\" | \"text\" | \"gameEvent\";\n /** Speaker token (line only). */\n character?: string;\n /** Resolved display name for `character` (source locale), if the cast declares one. */\n characterName?: string;\n /** Performance direction (line only). */\n direction?: string;\n /** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */\n text?: string;\n /** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */\n gameData?: GameData;\n /** Accumulated author tags (scene -> block -> group(s) -> snippet -> beat). Omitted when empty. */\n tags?: string[];\n}\n\n/** A node in the outline tree: a group (with its selector + children) or a snippet (with its beats). */\nexport interface OutlineNode {\n type: \"group\" | \"snippet\";\n id: string;\n tags?: string[];\n // group only\n selector?: string;\n /** A choice/option group's prompt beat, if any. */\n prompt?: BeatInfo;\n children?: OutlineNode[];\n // snippet only\n beats?: BeatInfo[];\n jumpTo?: string;\n jumpMode?: \"jump\" | \"call\";\n}\n\n/** A block in the outline tree. */\nexport interface OutlineBlock {\n id: string;\n gameId?: string;\n name: string;\n /** The block's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */\n gameData?: GameData;\n tags?: string[];\n children: OutlineNode[];\n}\n\n/** A scene in the outline tree. */\nexport interface OutlineScene {\n id: string;\n gameId?: string;\n name: string;\n /** The scene's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */\n gameData?: GameData;\n tags?: string[];\n blocks: OutlineBlock[];\n}\n\n/** One beat in document order, with the scene/block/snippet it lives in (the flat view). */\nexport interface FlatBeat {\n sceneId: string;\n blockId: string;\n snippetId: string;\n beat: BeatInfo;\n}\n\n/** What {@link Flow.advanceToStop} returns: the beats walked, and the choice / end that stopped it. */\nexport interface AdvanceToStopResult {\n /** The line / text / game-event beats played on the way to the stop (never a choice / end). */\n played: Array<Extract<StepResult, { type: \"line\" | \"text\" | \"gameEvent\" }>>;\n stop: Extract<StepResult, { type: \"choice\" | \"end\" }>;\n}\n\n/** The choice text of an option (spec §5): its `prompt` beat, resolved + interpolated. */\nexport interface ChoicePrompt {\n kind: \"line\" | \"text\";\n /** Display text (interpolated; may be empty - the host can render from gameData / an icon). */\n text: string;\n /** Speaker / direction - present only for a `line` prompt (the PC's spoken choice). */\n character?: string;\n /** The speaker's resolved player-facing name (locale-aware; absent when the character has none). */\n characterName?: string;\n direction?: string;\n}\n\n/** A single option of a pending `choice` group. */\nexport interface ChoiceOption {\n /** The option's id (an Option group, or a degenerate option snippet) - pass to `choose()`. */\n id: string;\n /**\n * The option's `prompt` (spec §5) - the choice text as a structured line/text beat. For the\n * degenerate bare-snippet tolerance, derived from the snippet's first content line. Undefined\n * only when even that is absent; internal ids are never leaked as display text.\n */\n prompt?: ChoicePrompt;\n /** False when the option's condition fails; still returned (greyed) unless hidden. */\n eligible: boolean;\n gameData?: GameData;\n}\n\n/**\n * The host's **World Properties** resolver: a `{ get, set? }` the game provides so the story can read\n * (and, if you allow it, write) its `@world.*` values at runtime. Property metadata (types, read-only)\n * comes from the compiled bundle's declared world properties; the values themselves live in the host and\n * are never stored or saved by this engine.\n */\nexport type WorldResolver = ScopeResolver;\n\n/** What the engine DECIDED, as opposed to what it produced. A step tells you the line that\n * played; a trace event tells you why that line and not its siblings - which children were\n * eligible, where the selector's cursor stood, which choice options were live.\n *\n * The reasoning is IN the entry, not implied by it. `select` carries every child it looked at\n * with its own verdict, the way a deal in the Storylet Engine carries every card it considered.\n * A log that recorded only the winner would answer \"what happened\" and not \"why\", and \"why\"\n * is the question a dialogue author cannot otherwise answer: the line you expected is missing\n * and nothing anywhere says which condition dropped it.\n *\n * Patter's vocabulary, not the Storylet Engine's: there are no cards or hands here. The\n * structure is shared (an ordered retained log, `seq`, a flow-level and an engine-level tap);\n * the events are this engine's own. */\nexport type TraceEvent =\n | {\n type: \"select\";\n /** The group whose children were being chosen among. */\n group: string;\n /** `branch` | `sequence` | `run` | `choice`, and for `sequence` its order/exhaust policy. */\n selector: string;\n order?: string;\n exhaust?: string;\n /** Every child considered, in authored order, each with why it was or was not takeable. */\n children: { id: string; eligible: boolean }[];\n /** The child selected, or null when nothing was takeable. */\n picked: string | null;\n }\n | {\n type: \"choice\";\n group: string;\n /** Every option the group offered, with the ones a condition dropped marked. */\n options: { id: string; eligible: boolean }[];\n }\n /** A choice the player answered. `option` is what they took. */\n | { type: \"chose\"; group: string; option: string }\n /** A choice with no takeable option and no eligible fallback, which falls through\n * silently. Also reported through `onDryChoice`, which stays: a log is an audit read\n * afterwards, that callback is live feedback a host acts on. */\n | { type: \"dry\"; group: string }\n | { type: \"jump\"; to: string; mode: \"jump\" | \"call\" }\n /** One landed effect; `prev` is the value it replaced, so a reader can say \"0 -> 1\". */\n | { type: \"write\"; target: string; value: ScalarValue; prev?: ScalarValue }\n /** Content the engine could not evaluate and played through (see {@link PlayError}): a condition\n * counted as false, an effect skipped, or a Best-match part scored as false. */\n | { type: \"diagnostic\"; kind: PlayError[\"kind\"]; node: string; source?: string; message: string };\n\n/**\n * A content error the engine played through. Content can fail at run time in ways the compiler cannot\n * see: a division by zero, a host value of the wrong type, a story write to a read-only `@world` value.\n * The story never stops for one. A condition that fails counts as false; an effect that fails is skipped\n * and the rest of its list still runs; a part of a Best-match condition that fails scores as false. Each\n * is reported, through `EngineOptions.onError` and as a `diagnostic` entry in the decision log. The same\n * rule holds on all four runtimes, including the two whose languages cannot throw.\n */\nexport interface PlayError {\n /** The flow it happened in. */\n flow: string;\n /** What failed: a condition, an effect, or a part of a condition being scored for Best match. */\n kind: \"condition\" | \"effect\" | \"best-match\";\n /** The snippet, group, or option whose condition failed, or that owns the effect (a scene, for its onEntry). */\n node: string;\n /** The expression's source text, when the bundle carries it. */\n source?: string;\n message: string;\n}\n\nexport type TraceHandler = (event: TraceEvent) => void;\n/** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */\nexport type EngineTraceHandler = (flow: string, event: TraceEvent) => void;\n\n/** A retained entry: the event plus its place in flow time. `seq` is monotonic across the\n * flow and survives clearLog, so two reads of the log agree about order. */\nexport type LogEntry = TraceEvent & { seq: number; scene?: string };\n\n/** One entry on the ENGINE's log: the same event plus the flow it happened in. A run is\n * several flows over shared state, so \"what happened in this run\" is only answerable in one\n * ordered stream, and only if each line says who. */\nexport type EngineLogEntry = LogEntry & { flow: string };\n\nexport interface EngineOptions {\n /**\n * Custom float-in-[0,1) source for `random()` / shuffle, shared by all flows.\n * Overrides the built-in seeded PRNG - but its position is NOT captured by\n * `saveGame()`. For resumable runs, use the built-in per-flow seed instead.\n */\n rng?: () => number;\n /** Default seed for each flow's built-in (serialisable) PRNG; override per flow in `openFlow`. */\n seed?: number;\n /** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.\n * Ignored by an \"ids\" bundle, which emits beat IDs for the game to localise itself. */\n locale?: string;\n /** The host's resolver for **World Properties** (`@world.*`): the values the game owns and the story\n * reads. Omit it and the runtime self-backs `@world` from the declared defaults, as a property the\n * registry stores and saves. Shared by all flows. A game running several engines registers `@world`\n * in its registry itself instead, and no engine is given a resolver. */\n world?: WorldResolver;\n /** The game's registry: ONE per game, holding every engine's properties except those the game keeps\n * itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,\n * its per-flow and per-scene bags under keys starting `patter/`, and `@world` if `world` is passed),\n * reads every other scope from it, and `saveGame()` leaves the property values to the game. Host\n * scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should\n * store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as\n * its own game: it self-backs `@world`, and `saveGame()` carries the registry's values too. */\n registry?: ScopeRegistry;\n /**\n * Replay a chosen option's `prompt` as its first played beat (spec §5). Default `false`:\n * the prompt is a label only and `choose()` plays just the option's content. `true`: the\n * prompt beat is delivered first (the choice \"spoken back\"). A host decision, not authored.\n */\n replayPromptOnChoose?: boolean;\n /** Closed captions (#214): show non-spoken caption cues inside dialogue lines (the `[sigh]` in\n * `Oh dear. [sigh] What now?`). Default `true` (full text). `false` strips every cue + its delimiters\n * and collapses the whitespace - for a player who hears the audio and doesn't want the captions.\n * Toggle live with `engine.setClosedCaptions(...)`. */\n closedCaptions?: boolean;\n /** Diagnostics hook (opt-in, dev tooling only): fired with the choice's group id whenever a choice runs\n * DRY - no takeable option and no eligible fallback - so it falls through silently. The behaviour is\n * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices\n * that ran dry. Leave it unset in shipped games (zero cost). */\n onDryChoice?: (groupId: string) => void;\n /** Called with each content error the engine played through (see {@link PlayError}): a condition\n * that failed and counted as false, an effect that failed and was skipped. Unset, each is written\n * to `console.warn`, so a content bug is never silent. */\n onError?: (error: PlayError) => void;\n /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through\n * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a\n * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful\n * with the log off - it is live feedback, not an audit read afterwards. */\n log?: boolean;\n}\n\n// PropertyView is gone. It was the shared PropertyRow plus a `path`, and `path` moved onto\n// that row on 2026-09-02 - so the name was a synonym, and a synonym for a shared type is\n// how the two families drifted in the first place: the same row called PropertyView here,\n// ScopePropertyRow there, PropertyRow in the kernel. listProperties() returns PropertyRow.\n\n/** Options for opening a flow. */\nexport interface OpenFlowOptions {\n /** Scene to start at - its host-facing gameId (address) OR its internal id; defaults to the\n * bundle's first scene. */\n scene?: string;\n /** Block within the scene to start at - its gameId (scene-scoped address) OR its internal id. */\n block?: string;\n /** Seed for this flow's PRNG (defaults to the engine's `seed`). */\n seed?: number;\n}\n\ninterface ChoiceState {\n /** The choice group's id (saved alongside the verbatim option set - SavedChoice). */\n groupId: string;\n options: ChoiceOption[];\n byId: Map<string, SelectableNode>;\n}\n\ninterface SelectorState {\n seq?: number; // sequential cursor (visits taken)\n bag?: string[]; // shuffle: child ids still undrawn this pass (undefined = not started)\n last?: string; // last child id picked (no-immediate-repeat across reshuffles)\n}\n\n/** What an open checkpoint records: how to undo each change, newest last. Each piece of state records\n * itself when it changes (a value, a count, a cursor), never a copy of everything, so a checkpoint costs\n * what the steps inside it do, however much history the game has built up. */\nexport interface Journal {\n undo: Array<() => void>;\n /** Flows already snapshotted (or opened inside the checkpoint, so closed on rollback). */\n flows: Set<Flow>;\n /** Flows opened inside the checkpoint. */\n opened: Set<Flow>;\n /** Selector cursors already copied: shared ones by group id, a flow's own in `flowSelectors`. */\n selectors: Set<string>;\n flowSelectors: Map<Flow, Set<string>>;\n}\n\n/** An open checkpoint, from {@link Engine.checkpoint}. Opaque: hand it back to `rollback` or `commit`. */\nexport class Checkpoint {\n /** @internal */\n constructor(readonly journal: Journal) {}\n}\n\n/** Shared, read-mostly context the engine hands to every flow it owns. */\ninterface FlowHost {\n /** True when the run asked for a log; flows skip building entries otherwise. */\n logEnabled: boolean;\n /** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */\n emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;\n bundle: Bundle;\n /** IDs-only build (`localisation.mode === \"ids\"`, no source-debug): the engine emits each beat's ID as\n * its text and omits character display names, leaving localisation to the game (use `flow.interpolate`\n * to apply `{@ref}` property replacement to a string the game looked up itself). */\n emitIds: boolean;\n strings: Record<string, string>;\n /** The DEFAULT locale's string table - fallback for a key the active locale is missing (notably the\n * cast display-name keys, seeded there from `displayName`). */\n defaultStrings: Record<string, string>;\n /** Cast canonical name -> authoring `displayName` (the unlocalised fallback when no loc string exists). */\n castDisplay: Map<string, string>;\n nodeIndex: Map<string, SelectableNode>;\n blockIndex: Map<string, { sceneId: string }>;\n blockById: Map<string, CompiledBlock>;\n /** Host-facing addresses (spec §6), shared with the engine: scene gameId -> internal id (project-wide),\n * and per-scene block gameId -> internal id. A flow needs them to resolve `goto` by address. */\n sceneGameIdToId: Map<string, string>;\n blockGameIdToId: Map<string, Map<string, string>>;\n /** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */\n tagIndex: Map<string, string[]>;\n /** The game's one registry: `@patter` (the SHARED globals), host scopes, every instance bag. */\n registry: ScopeRegistry;\n /** True when the engine made the registry (a standalone game): `saveGame()` then carries its values. */\n readonly ownsRegistry: boolean;\n /** The SHARED `@patter` globals' bag, registered under `patter`. */\n patterBag: PropertyBag;\n /** Host scopes this engine self-backed and registered (the game bound none, nobody else had). */\n hostScopes: string[];\n /** Decls for the shared `@patter` globals - (re)seed on `engine.reset()`. */\n patterSharedDecls: ScopeDeclaration[];\n /** Decls for the per-flow `@patter` globals - seed each flow's local registry. */\n patterLocalDecls: ScopeDeclaration[];\n /** Lowercase names of the SHARED globals (route a `@patter` ref to engine vs flow). */\n patterSharedNames: Set<string>;\n /** Per-scene set of SHARED `@scene` prop names (route a `@scene` ref to stage vs flow). */\n sceneSharedNames: Map<string, Set<string>>;\n /** World-wide per-node entry counts (node id -> times entered by any flow). */\n sharedVisits: Map<string, number>;\n /** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */\n sharedSelectors: Map<string, SelectorState>;\n /** Per-scene SHARED scene props, each registered under `keys.stage(sceneId)`. Made the first time any\n * flow needs the scene, so a bag loaded before then waits in the registry and is claimed here. */\n stageBags: Map<string, PropertyBag>;\n /** The open checkpoint's undo journal, or null when none is open (see {@link Engine.checkpoint}). */\n journal: Journal | null;\n customRng?: () => number;\n /** Play a chosen option's prompt as its first beat (spec §5); default false. */\n replayPromptOnChoose?: boolean;\n /** Closed captions (#214). `captionsOn`: show caption cues in dialogue lines (default true); when\n * false the engine strips `captionOpen`…`captionClose` spans from line text. Mutable via\n * `setClosedCaptions` (one toggle, all flows - like setLocale). */\n captionsOn: boolean;\n captionOpen: string;\n captionClose: string;\n /** A cast member whose lines are pure captions: when captions are off ALL of its dialogue + speaker is\n * omitted (a silent line), delimiters or not. Default `SFX`. Empty = no caption character. */\n captionCharacter: string;\n /** Diagnostics hook (opt-in, dev only): fired when a choice runs DRY - nothing takeable and no eligible\n * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage\n * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */\n onDryChoice?: (groupId: string) => void;\n /** Where a content error the engine played through is reported (see PlayError). */\n onError: (error: PlayError) => void;\n /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's\n * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */\n refSplitCache: Map<string, { scope: string; name: string }>;\n refSplitRevision: number;\n}\n\n// ---------------------------------------------------------------------------\n// Engine - the world + flow manager\n// ---------------------------------------------------------------------------\n\nexport class Engine {\n private readonly host: FlowHost;\n private readonly defaultSeed: number;\n private readonly flowsById = new Map<string, Flow>();\n /** Every locale's string table (the inline `bundle.strings`), kept so the active locale can be swapped\n * live (setLocale) without rebuilding the engine. Reassigned wholesale by `replaceStrings`\n * (live bundle refresh, tier 1), hence not readonly. */\n private allStrings: Record<string, Record<string, string>>;\n /** The currently active locale (string lookups + character names resolve in it). */\n private currentLocale: string;\n /** True for a source-only DEBUG build (`localisation: { mode: \"ids\", sourceDebug: true }`) - the strings\n * are the source language, embedded only so the build can be played; not a shippable localised build. */\n private readonly sourceDebug: boolean;\n /** Host-facing addresses (spec §6): scene gameId -> internal id (project-wide), and per-scene\n * block gameId -> internal id. The effective gameId falls back to the name slug when unpinned. */\n private readonly sceneGameIdToId = new Map<string, string>();\n private readonly blockGameIdToId = new Map<string, Map<string, string>>();\n\n /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement\n * engine keeps the same world resolver, custom RNG, and diagnostic hooks. */\n private readonly creationOptions: EngineOptions;\n /** The run's ordered stream: every flow's events, each naming its flow. Empty and\n * unwritten unless `options.log` asked for it. */\n private readonly engineLog: EngineLogEntry[] = [];\n /** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */\n private engineSeq = 0;\n private readonly engineTraceHandlers = new Set<EngineTraceHandler>();\n\n constructor(bundle: Bundle, options: EngineOptions = {}) {\n this.creationOptions = options;\n const locale = options.locale ?? bundle.locales.default;\n const allStrings = bundle.strings;\n this.allStrings = allStrings;\n this.currentLocale = locale;\n const strings = allStrings[locale] ?? {};\n const defaultStrings = allStrings[bundle.locales.default] ?? {};\n // Localisation mode (spec §11). \"ids\" + no source-debug = the engine emits beat IDs (the game localises\n // itself). A source-debug build still resolves its embedded source strings, but is flagged for a warning.\n const loc = bundle.localisation;\n const emitIds = loc?.mode === \"ids\" && !loc.sourceDebug;\n this.sourceDebug = loc?.mode === \"ids\" && !!loc.sourceDebug;\n if (this.sourceDebug && typeof console !== \"undefined\") {\n console.warn(\"[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.\");\n }\n // Cast name -> displayName: the unlocalised fallback for a character's shown name when neither the\n // active nor the default locale carries a `cast:<name>` string.\n const castDisplay = new Map<string, string>();\n for (const c of bundle.cast ?? []) if (c.displayName) castDisplay.set(c.name, c.displayName);\n this.defaultSeed = (options.seed ?? 0x9e3779b9) >>> 0;\n\n const nodeIndex = new Map<string, SelectableNode>();\n const blockIndex = new Map<string, { sceneId: string }>();\n const blockById = new Map<string, CompiledBlock>();\n for (const [sceneId, scene] of Object.entries(bundle.scenes)) {\n this.sceneGameIdToId.set(effectiveGameId(scene), sceneId);\n const blockAddrs = new Map<string, string>();\n for (const block of scene.blocks) {\n blockIndex.set(block.id, { sceneId });\n blockById.set(block.id, block);\n blockAddrs.set(effectiveGameId(block), block.id);\n walkNodes<SelectableNode>(block.children, (n) => nodeIndex.set(n.id, n));\n }\n this.blockGameIdToId.set(sceneId, blockAddrs);\n }\n\n // Globals (`@patter`) split by the `shared` flag (default shared): shared ones\n // live in the engine's owned scope, per-flow ones seed each flow's registry.\n const props = bundle.properties ?? [];\n const patterSharedDecls = props.filter((p) => p.shared ?? true).map(toDecl);\n const patterLocalDecls = props.filter((p) => !(p.shared ?? true)).map(toDecl);\n const patterSharedNames = new Set(patterSharedDecls.map((d) => d.name.toLowerCase()));\n\n // \"@patter.\" so a row addresses itself the way getProperty takes it. `@gold` also\n // resolves - splitRef defaults an unqualified name to the patter scope - but the\n // qualified form is the address, and the shorthand is a shorthand.\n const registry = options.registry ?? new ScopeRegistry();\n const ownsRegistry = !options.registry || (options as InternalOptions)[OWNS_REGISTRY] === true;\n const patterBag = new PropertyBag(patterSharedDecls, { pathPrefix: \"@patter.\" });\n const hostScopes: string[] = [];\n const registered: string[] = [];\n try {\n registry.mountOwned(\"patter\", patterBag, { owner: OWNER }); // claims values the game loaded first\n registered.push(\"patter\");\n // The host's World Properties resolver binds `@world` as an external scope: the game keeps the values,\n // the registry never saves them. Its declarations (types, read-only) come from the compiled bundle.\n const hostBound = new Set<string>();\n if (options.world) {\n const worldSpec = bundle.scopeRegistry?.scopes.find((s) => s.token === \"world\");\n const decls = (worldSpec?.declarations ?? []).map(toForeignDecl);\n registry.defineForeign(\"world\", options.world, decls, { writable: worldSpec?.writable ?? true, owner: OWNER });\n registered.push(\"world\");\n hostBound.add(\"world\");\n }\n // A declared host scope nobody bound. A standalone engine is its own game, so it self-backs the scope:\n // a property bag seeded from the declarations, stored and SAVED by the registry like any other, since\n // only a resolver the game binds is external. Given the GAME's registry, the engine registers nothing\n // here: those tokens are the game's to register, or another engine's (a bundle compiled against the\n // Storylet Engine's spec declares `@story`), and self-backing one would clash with its real owner\n // depending only on which engine was built first.\n for (const spec of ownsRegistry ? bundle.scopeRegistry?.scopes ?? [] : []) {\n if (hostBound.has(spec.token) || registry.has(spec.token)) continue;\n const decls = (spec.declarations ?? []).map((d) => selfBackedDecl(d, spec.writable));\n registry.defineOwned(spec.token, decls, { owner: OWNER });\n registered.push(spec.token);\n hostScopes.push(spec.token);\n }\n } catch (e) {\n for (const k of registered) registry.remove(k, { keep: true }); // a clash leaves the game's registry as it was\n throw e;\n }\n\n // Scene props (`@scene`) split by `shared` (default per-flow): record, per\n // scene, which names are shared so a `@scene` ref routes to stage vs flow.\n const sceneSharedNames = new Map<string, Set<string>>();\n for (const [sceneId, scene] of Object.entries(bundle.scenes)) {\n const names = new Set((scene.sceneProps ?? []).filter((p) => p.shared ?? false).map((p) => p.name.toLowerCase()));\n sceneSharedNames.set(sceneId, names);\n }\n\n this.host = {\n logEnabled: options.log ?? false,\n emitEngine: (flow, event, scene) => this.emitEngine(flow, event, scene),\n bundle, emitIds, strings, defaultStrings, castDisplay, nodeIndex, blockIndex, blockById,\n sceneGameIdToId: this.sceneGameIdToId, blockGameIdToId: this.blockGameIdToId, // same instances the engine resolves with\n tagIndex: buildTagIndex(bundle), registry, ownsRegistry, patterBag, hostScopes,\n patterSharedDecls, patterLocalDecls, patterSharedNames, sceneSharedNames,\n sharedVisits: new Map(),\n sharedSelectors: new Map(),\n stageBags: new Map(),\n journal: null,\n customRng: options.rng,\n onDryChoice: options.onDryChoice,\n onError: options.onError ?? ((e) => console.warn(`Patterplay: ${e.kind} on ${e.node} in flow '${e.flow}' failed, played through: ${e.message}`)),\n replayPromptOnChoose: options.replayPromptOnChoose ?? false,\n captionsOn: options.closedCaptions ?? true, // captions shown by default (full text)\n captionOpen: (bundle.closedCaptions ?? DEFAULT_CAPTION_DELIMITERS).open,\n captionClose: (bundle.closedCaptions ?? DEFAULT_CAPTION_DELIMITERS).close,\n captionCharacter: bundle.closedCaptions?.character || DEFAULT_CAPTION_CHARACTER, // absent/empty -> SFX\n refSplitCache: new Map(),\n refSplitRevision: registry.revision,\n };\n }\n\n /** The active locale (string + character-name lookups resolve in it). */\n get locale(): string { return this.currentLocale; }\n\n /** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),\n * not a shippable localised build. An IDs-only ship build is `false`. */\n get isSourceDebug(): boolean { return this.sourceDebug; }\n\n /**\n * Switch the active locale LIVE - a real game's \"language\" setting can change mid-session. Subsequent\n * string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new\n * locale; everything else - flow position, `@patter`/`@scene` state, visit counts, the PRNG - is\n * untouched (already-emitted text isn't retro-translated; that's the host's call). A locale with no\n * table resolves every string via the `<Untranslated: {id}>` source fallback. All open flows share the\n * engine's string table, so the swap reaches every flow at once.\n */\n setLocale(locale: string): void {\n this.currentLocale = locale;\n this.host.strings = this.allStrings[locale] ?? {};\n }\n\n /**\n * Live bundle refresh, tier 1 (strings only): swap every locale's string table in place from a\n * freshly compiled bundle whose STRUCTURE is unchanged (same `content.structureHash`). Like\n * setLocale, nothing restarts and no flow is touched: the next delivered beat reads the new text,\n * `{@ref}` slots re-interpolate, and beats the host already received keep the words it saw. The\n * swap reaches every open flow at once and is not part of save state. Structural edits need the\n * full save/load hot swap instead (a structure change here simply won't show).\n */\n replaceStrings(bundle: Bundle): void {\n this.allStrings = bundle.strings;\n this.host.strings = this.allStrings[this.currentLocale] ?? {};\n this.host.defaultStrings = this.allStrings[this.host.bundle.locales.default] ?? {};\n }\n\n /**\n * Live bundle refresh, tier 2 (full swap): rebuild on an edited bundle with the whole run carried\n * over. Snapshot (`saveGame`), construct a fresh engine on `bundle` with THIS engine's original\n * options (same world resolver, RNG, hooks), restore (`loadGame`), and carry over the presentation\n * state that deliberately isn't save state (active locale, closed-captions toggle). The\n * content-drift policy (§9.8) resolves edits under the cursor: stack frames re-find their next\n * child by id, drifted options drop, a vanished snippet is skipped.\n *\n * Returns the REPLACEMENT engine, on the same registry. This one hands its bags over (each is\n * removed from the registry with its values kept, and the replacement claims them as it\n * registers), its flows are closed, and it should be discarded. Hosts re-bind their flow handles\n * via `next.getFlow(id)`. If the restore throws (defensive - §9.8 makes this unreachable for\n * ordinary edits), the swap falls back to a fresh engine with each saved flow restarted from the\n * top of the scene it was in; the shared properties carry over.\n */\n hotSwap(bundle: Bundle): Engine {\n this.refuseInCheckpoint(\"hotSwap\");\n const snapshot = this.saveGame();\n const carryOver = (next: Engine): Engine => {\n next.setLocale(this.currentLocale);\n next.setClosedCaptions(this.host.captionsOn);\n return next;\n };\n // The replacement registers on the SAME registry, and a standalone engine's replacement is still its\n // own game (so its saveGame keeps carrying the registry's values).\n const make = (): Engine => new Engine(bundle, {\n ...this.creationOptions, registry: this.host.registry, [OWNS_REGISTRY]: this.host.ownsRegistry,\n } as InternalOptions);\n this.release(true);\n const next = make();\n try {\n next.loadGame(snapshot);\n return carryOver(next);\n } catch {\n // A partial load may have mutated `next`: hand its bags back, fall back on a THIRD engine and\n // restart each flow at the top of the scene it was in (dropped when that scene is gone too).\n next.release(true);\n const fresh = make();\n for (const [id, f] of Object.entries(snapshot.flows)) {\n const sceneId = f.cursor.currentSceneId;\n try { fresh.openFlow(id, sceneId !== null ? { scene: sceneId } : {}); } catch { /* scene deleted: drop the flow */ }\n }\n return carryOver(fresh);\n }\n }\n\n /** Whether closed captions are currently shown (full dialogue text). */\n get closedCaptions(): boolean { return this.host.captionsOn; }\n\n /**\n * Turn closed captions on/off LIVE (#214). When OFF, subsequent dialogue lines have their caption\n * cues (`[sigh]` etc., between the project's delimiters) and the surrounding whitespace stripped;\n * narration, choice prompts, and everything else are untouched. Like setLocale this is a presentation\n * toggle - it reaches every open flow at once and isn't part of save state; already-emitted text is\n * not retro-edited. An IDs-only game applies the same rule itself via `flow.stripCaptions`.\n */\n setClosedCaptions(on: boolean): void {\n this.host.captionsOn = on;\n }\n\n /**\n * Open (and start) a named flow. Each flow has its own cursor, PRNG, and per-flow\n * half of the scopes (not-shared `@patter`/`@scene`); all flows share the shared\n * half.\n *\n * Re-opening an existing id REPLACES it with a fresh flow, and CLOSES the old one\n * ({@link Flow.close}) so a host still holding it cannot keep driving the shared world. Replacing is\n * therefore a reset: that name's cursor, visit counts, selector cursors (so any shuffle / once-each\n * position) and per-flow properties all start over.\n *\n * Contrast {@link runFlow}, which REUSES a flow of the same name instead of replacing it - that is the\n * call to reach for when you want a speaker's variation state to carry on.\n *\n * THROWS on an address that does not resolve (an unknown scene, or a block that is not in the named\n * scene), before anything changes: nothing is opened, and a flow already open under `id` carries on\n * exactly as it was.\n */\n openFlow(id: string, opts: OpenFlowOptions = {}): Flow {\n this.assertExternalScopes();\n const { sceneId, blockId } = this.resolveOpenAddress(opts.scene, opts.block);\n const prior = this.flowsById.get(id);\n const journal = this.host.journal;\n if (journal && prior && !prior.isClosed) throw new Error(`openFlow would replace the open flow '${id}', which can't be undone while a checkpoint is open`);\n prior?.close(); // finish the flow this name used to mean\n const flow = new Flow(id, this.host, opts.seed ?? this.defaultSeed);\n if (journal) {\n // Opened inside the checkpoint: rolling back closes it and puts back whatever the name meant.\n journal.flows.add(flow);\n journal.opened.add(flow);\n journal.undo.push(() => {\n flow.close();\n if (prior) this.flowsById.set(id, prior); else this.flowsById.delete(id);\n });\n }\n this.flowsById.set(id, flow);\n flow.start(sceneId, blockId);\n return flow;\n }\n\n /**\n * \"Play this address and give me everything it produced\" - the one-call form of the bark / one-shot\n * pattern. The NAMED flow is reused if it already exists (moved with {@link Flow.goto}) and opened at\n * the address if not, then run to its next stop, returning every beat it played.\n *\n * Calling it again with the SAME NAME does NOT replace the flow - it reuses it, and that is the whole\n * point. A flow owns its selector cursors, visit counts and per-flow properties, so reusing one lets a\n * **shuffle keep its bag** and an **\"once each\" list keep its place**: successive calls give the next\n * variation instead of replaying the first forever. (A fresh flow each time would reset all of it,\n * unless every such group happened to be authored `shared`.) Use one name per independent speaker;\n * different names never share per-flow state.\n *\n * This is exactly where it differs from {@link openFlow}, which REPLACES a flow of the same name and\n * so resets that variation state. Never mix the two on one name unless you mean to start over.\n *\n * Returns the played beats in order - `[]` means the address had nothing left to give (an exhausted\n * variation list, say), which is the signal to fall back to other content. It THROWS on an address\n * that does not resolve: unlike `goto` (a navigation primitive, where probing is legitimate), naming a\n * location here asserts it exists, and keeping `[]` unambiguous is worth more than a soft failure.\n *\n * A run that stops at a CHOICE returns the beats up to it and leaves the choice pending on the flow -\n * fetch it with `engine.getFlow(name)?.getChoices()`.\n */\n runFlow(flow: string, scene: string, block?: string): AdvanceToStopResult[\"played\"] {\n const existing = this.flowsById.get(flow);\n if (!existing) return this.openFlow(flow, { scene, block }).advanceToStop().played; // openFlow throws on a bad address\n if (!existing.goto(scene, block)) {\n throw new Error(`runFlow: address not found: ${scene}${block === undefined ? \"\" : ` / ${block}`}`);\n }\n return existing.advanceToStop().played;\n }\n\n /** Content that names another engine's scope (`@story.act`) runs only where that engine is on this\n * registry: without it every read would answer false and every write fail, so the flow is refused as\n * it opens (or the save as it loads), before anything changes. By then a game has built all of its\n * engines, whatever order it built them in. The same message on every runtime. */\n private assertExternalScopes(): void {\n for (const token of this.host.bundle.externalScopes ?? []) {\n if (!this.host.registry.has(token)) {\n throw new Error(`this content names @${token}, which no engine on this registry registered: `\n + `give every engine the game's one registry`);\n }\n }\n }\n\n /**\n * `openFlow`'s address as internal ids, or a throw when it does not resolve. With a scene, the block is\n * scene-scoped exactly as {@link Flow.goto} resolves it (a gameId address in that scene, or the internal\n * id of a block in that scene), so a block from another scene does not resolve. With no scene, the block\n * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.\n */\n private resolveOpenAddress(scene?: string, block?: string): { sceneId?: string; blockId?: string } {\n if (scene !== undefined) {\n const sceneId = resolveScene(this.host, scene);\n if (sceneId === undefined) throw new Error(`unknown scene: ${scene}`);\n if (block === undefined) return { sceneId };\n const blockId = resolveBlock(this.host, sceneId, block);\n if (blockId === undefined) throw new Error(`unknown block: ${block}`);\n return { sceneId, blockId };\n }\n if (block !== undefined) {\n if (!this.host.blockIndex.has(block)) throw new Error(`unknown block: ${block}`);\n return { blockId: block };\n }\n if (Object.keys(this.host.bundle.scenes).length === 0) throw new Error(\"no scenes in bundle\");\n return {};\n }\n\n /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an\n * unknown one passes through, for the caller to find nothing. */\n private resolveSceneRef(ref?: string): string | undefined {\n if (ref == null) return undefined;\n return resolveScene(this.host, ref) ?? ref;\n }\n\n /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its\n * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,\n * since a block's gameId is only unique within its scene. */\n private resolveBlockRef(sceneId: string | undefined, ref?: string): string | undefined {\n if (ref == null) return undefined;\n if (sceneId == null) return ref;\n return resolveBlock(this.host, sceneId, ref);\n }\n\n /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.\n * The inverse of the resolve helpers - for a host that wants to display / log the address. */\n sceneAddress(sceneId: string): string | undefined {\n const scene = this.host.bundle.scenes[sceneId];\n return scene ? effectiveGameId(scene) : undefined;\n }\n blockAddress(blockId: string): string | undefined {\n const block = this.host.blockById.get(blockId);\n return block ? effectiveGameId(block) : undefined;\n }\n\n /**\n * Author tags (#215) accumulated for a beat by id: its own tags unioned with every ancestor's\n * (scene → block → group(s) → snippet → beat), deduped, outermost-first. The same value the beat's\n * delivered step carries. Empty array for an unknown id or a beat with no tags anywhere up the chain.\n */\n tagsForBeat(beatId: string): string[] {\n return this.host.tagIndex.get(beatId) ?? [];\n }\n /** A scene's own tags (by internal id or gameId address). Empty when none / unknown. */\n tagsForScene(sceneRef: string): string[] {\n const id = this.resolveSceneRef(sceneRef);\n return (id != null ? this.host.tagIndex.get(id) : undefined) ?? [];\n }\n /** A block's accumulated tags (scene + block), by scene + block ref (id or gameId). Empty when none / unknown. */\n tagsForBlock(sceneRef: string, blockRef: string): string[] {\n const sceneId = this.resolveSceneRef(sceneRef);\n const id = this.resolveBlockRef(sceneId, blockRef);\n return (id != null ? this.host.tagIndex.get(id) : undefined) ?? [];\n }\n\n /**\n * A scene's own author gameData (by internal id or gameId address): the RAW sparse overrides, exactly\n * as a beat's step carries its own, not merged with the project's declared field defaults (resolve\n * those with `effectiveGameData(gameDataFields(bundle, \"scene\"), ...)`). Not inherited by its blocks.\n * A fresh object each call; empty when the scene sets none or the ref is unknown.\n */\n gameDataForScene(sceneRef: string): GameData {\n const id = this.resolveSceneRef(sceneRef);\n const scene = id != null ? this.host.bundle.scenes[id] : undefined;\n return { ...scene?.gameData };\n }\n /** A block's own author gameData, by scene + block ref (id or gameId). Raw overrides, like\n * {@link gameDataForScene}; the scene's gameData is not folded in. Empty when none / unknown. */\n gameDataForBlock(sceneRef: string, blockRef: string): GameData {\n const id = this.resolveBlockRef(this.resolveSceneRef(sceneRef), blockRef);\n const block = id != null ? this.host.blockById.get(id) : undefined;\n return { ...block?.gameData };\n }\n\n /**\n * Every cast member the PROJECT declares, in authored order - the same list `describeBundle` counts.\n * A superset of any scene's cast: the validator holds a beat's `character` to a declared member, so\n * {@link castForScene} and {@link castForBlock} only ever return names that appear here.\n */\n getCast(): string[] {\n // `cast` is absent from a bundle whose project declares none (the compiler omits the key), and a\n // nameless member is junk from a hand-edited bundle: both give an empty answer, not a throw.\n const names: string[] = [];\n for (const c of this.host.bundle.cast ?? []) if (c?.name) names.push(c.name);\n return names;\n }\n\n /**\n * A scene's cast: the `character` token of every speaker with a line anywhere in it, deduped, in\n * first-appearance order. Static, like {@link getOutline}: it walks the authored structure, so a\n * speaker behind a condition, inside any group, or voicing a choice prompt counts - this is who CAN\n * speak in the scene, not who a given playthrough heard. Empty for an unknown ref, or a scene with no\n * dialogue. Tokens, not display names: resolve those through the delivered step (`characterName`),\n * which is what follows `setLocale`.\n */\n castForScene(sceneRef: string): string[] {\n const id = this.resolveSceneRef(sceneRef);\n const scene = id != null ? this.host.bundle.scenes[id] : undefined;\n if (!scene) return [];\n const out = new Set<string>();\n for (const block of scene.blocks) collectCast(block.children, out);\n return [...out];\n }\n\n /** One block's cast, by scene + block ref (id or gameId). {@link castForScene} scoped to a block. */\n castForBlock(sceneRef: string, blockRef: string): string[] {\n const sceneId = this.resolveSceneRef(sceneRef);\n const id = this.resolveBlockRef(sceneId, blockRef);\n const block = id != null ? this.host.blockById.get(id) : undefined;\n if (!block) return [];\n const out = new Set<string>();\n collectCast(block.children, out);\n return [...out];\n }\n\n /**\n * The authored structure as a nested tree: scenes -> blocks -> children (groups + snippets, groups\n * preserved) -> a snippet's beats. Static (no flow / play state); per-beat data is read at the source\n * locale. For dev tooling that builds against the writer's structure (see also {@link getBeatSequence}).\n */\n getOutline(): OutlineScene[] {\n return Object.values(this.host.bundle.scenes).map((scene) => ({\n id: scene.id,\n ...(effectiveGameId(scene) ? { gameId: effectiveGameId(scene) } : {}),\n name: scene.name,\n ...gameDataField(scene.gameData),\n ...this.tagsField(scene.id),\n blocks: scene.blocks.map((block) => ({\n id: block.id,\n ...(effectiveGameId(block) ? { gameId: effectiveGameId(block) } : {}),\n name: block.name,\n ...gameDataField(block.gameData),\n ...this.tagsField(block.id),\n children: block.children.map((n) => this.outlineNode(n)),\n })),\n }));\n }\n\n /**\n * Every beat in document order, flattened (through groups), each with the scene / block / snippet it\n * belongs to and its static data. The linear view of {@link getOutline} - hand it to a tool that lays\n * one item per beat (e.g. an Unreal Sequencer of subsequences).\n */\n getBeatSequence(): FlatBeat[] {\n const out: FlatBeat[] = [];\n for (const scene of Object.values(this.host.bundle.scenes)) {\n for (const block of scene.blocks) {\n walkNodes<SelectableNode>(block.children, (n) => {\n if (n.type !== \"snippet\") return;\n for (const beat of n.beats ?? []) {\n out.push({ sceneId: scene.id, blockId: block.id, snippetId: n.id, beat: this.beatInfo(beat) });\n }\n });\n }\n }\n return out;\n }\n\n /** A node's outline entry: a group (selector + prompt + children) or a snippet (beats + jump). */\n private outlineNode(n: SelectableNode): OutlineNode {\n if (n.type === \"group\") {\n return {\n type: \"group\",\n id: n.id,\n ...this.tagsField(n.id),\n ...(n.selector ? { selector: n.selector } : {}),\n ...(n.prompt ? { prompt: this.beatInfo(n.prompt) } : {}),\n children: n.children.map((c) => this.outlineNode(c)),\n };\n }\n return {\n type: \"snippet\",\n id: n.id,\n ...this.tagsField(n.id),\n beats: (n.beats ?? []).map((b) => this.beatInfo(b)),\n ...(n.jump ? { jumpTo: n.jump.to, ...(n.jump.mode ? { jumpMode: n.jump.mode } : {}) } : {}),\n };\n }\n\n /** One beat's static data (source locale), the same shape a delivered step carries. */\n private beatInfo(beat: Beat): BeatInfo {\n const tags = this.host.tagIndex.get(beat.id);\n const info: BeatInfo = { id: beat.id, kind: beat.kind };\n if (beat.kind === \"line\") {\n if (beat.character !== undefined) {\n info.character = beat.character;\n const name = this.host.defaultStrings[castStringKey(beat.character)] ?? this.host.castDisplay.get(beat.character);\n if (name !== undefined) info.characterName = name;\n }\n if (beat.direction !== undefined) info.direction = beat.direction;\n }\n if (beat.kind === \"line\" || beat.kind === \"text\") {\n const source = this.host.defaultStrings[beat.id]; // source-locale text, un-interpolated\n if (source !== undefined) info.text = source;\n }\n if (beat.gameData && Object.keys(beat.gameData).length) info.gameData = beat.gameData;\n if (tags && tags.length) info.tags = tags;\n return info;\n }\n\n /** A `{ tags }` fragment for an id, present only when the id has accumulated tags (keeps output tidy). */\n private tagsField(id: string): { tags?: string[] } {\n const tags = this.host.tagIndex.get(id);\n return tags && tags.length ? { tags } : {};\n }\n\n /** Retrieve an open flow by id (undefined if none / closed). */\n getFlow(id: string): Flow | undefined {\n return this.flowsById.get(id);\n }\n\n /** All currently-open flows. */\n flows(): Flow[] {\n return [...this.flowsById.values()];\n }\n\n /**\n * The SHARED kernel bags with the path each answers to in a log: the `@patter` globals,\n * and one per scene for the shared `@scene` props. Parity with the Storylet Engine's\n * listBags of the same name - it is what a state logger mounts.\n *\n * A stage bag's log path is `@scene:<sceneId>.`, not the bag's own `@scene.`: a property\n * is ADDRESSED relative to a flow's current scene, but a log spans scenes and has to say\n * which one. That is why a mount may override the bag's prefix.\n *\n * loadGame() replaces every bag, so re-enumerate after a load.\n */\n listBags(): LogMount[] {\n const mounts: LogMount[] = [{ bag: this.host.patterBag }];\n for (const [sceneId, bag] of this.host.stageBags) mounts.push({ bag, pathPrefix: `@scene:${sceneId}.` });\n return mounts;\n }\n\n /**\n * Open a checkpoint: from here until `rollback` or `commit`, the engine keeps what it needs to undo\n * every change the story makes. `rollback` puts the game back exactly as it was at the checkpoint:\n * property values (every scope, including a host's `@world`, which is written back through the\n * game), visit counts, shuffle and sequence positions, `@scene` bags made since, every flow's cursor\n * and PRNG, and any flow opened since (closed and forgotten). `commit` keeps everything and stops\n * recording.\n *\n * The use is asking \"would this say anything?\" without consequences: move a flow to an address,\n * advance it, and roll back if it had nothing to give, so its scene's `onEntry` effects, its visits,\n * and its shuffle draws never happened.\n *\n * One checkpoint at a time. Inside one, the calls that replace or drop flows or their bags wholesale\n * (`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` on the name of an open flow, and a flow's own\n * `start`, `reset`, and `restore`) throw. Not undone:\n * trace and log events already emitted, and draws from a custom `rng` the game supplied.\n */\n checkpoint(): Checkpoint {\n if (this.host.journal) throw new Error(\"a checkpoint is already open: roll it back or commit it first\");\n const journal: Journal = { undo: [], flows: new Set(), opened: new Set(), selectors: new Set(), flowSelectors: new Map() };\n this.host.journal = journal;\n return new Checkpoint(journal);\n }\n\n /** Undo everything since `checkpoint` (see there), and close it. */\n rollback(checkpoint: Checkpoint): void {\n this.endCheckpoint(checkpoint);\n const undo = checkpoint.journal.undo;\n for (let i = undo.length - 1; i >= 0; i--) undo[i]!();\n Engine.forget(checkpoint.journal);\n }\n\n /** Keep everything since `checkpoint`, and close it. */\n commit(checkpoint: Checkpoint): void {\n this.endCheckpoint(checkpoint);\n Engine.forget(checkpoint.journal);\n }\n\n /** True while a checkpoint is open. */\n get inCheckpoint(): boolean { return this.host.journal !== null; }\n\n private endCheckpoint(checkpoint: Checkpoint): void {\n if (this.host.journal !== checkpoint.journal) throw new Error(\"that checkpoint is not the open one\");\n this.host.journal = null;\n }\n\n /** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */\n private static forget(journal: Journal): void {\n journal.undo = [];\n journal.flows.clear();\n journal.opened.clear();\n journal.selectors.clear();\n journal.flowSelectors.clear();\n }\n\n private refuseInCheckpoint(what: string): void {\n if (this.host.journal) throw new Error(`${what} can't be undone, so it isn't allowed while a checkpoint is open`);\n }\n\n /** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still\n * holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */\n closeFlow(id: string): void {\n this.refuseInCheckpoint(\"closeFlow\");\n this.flowsById.get(id)?.close();\n this.flowsById.delete(id);\n }\n\n /**\n * Reset the whole game to its initial state: drop every flow, re-seed the shared\n * `@patter` globals to their declared defaults, and clear all shared state (shared\n * `@scene` bags, world visit counts). World properties are host-owned and untouched.\n * After reset, open fresh flows with `openFlow`.\n */\n reset(): void {\n this.refuseInCheckpoint(\"reset\");\n for (const flow of this.flowsById.values()) flow.close(); // finish them, don't just forget them\n this.flowsById.clear();\n this.host.patterBag.reseed(this.host.patterSharedDecls);\n this.host.sharedVisits.clear();\n this.host.sharedSelectors.clear();\n for (const s of this.host.stageBags.keys()) this.host.registry.remove(keys.stage(s));\n this.host.stageBags.clear();\n // Values loaded for bags nobody has claimed yet are the old game's too: a flow opened after the\n // reset must not pick them up. Other engines' parked values are theirs, and stay.\n this.host.registry.discardParked(\"patter/\");\n }\n\n /** Remove every bag this engine registered, keeping the values parked when `keep` (a live reload\n * handing its state to a replacement), and close its flows. The engine is inert afterwards. */\n private release(keep: boolean): void {\n for (const flow of this.flowsById.values()) { flow.releaseBags(keep); flow.close(); }\n this.flowsById.clear();\n const reg = this.host.registry;\n for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep });\n this.host.stageBags.clear();\n for (const t of [\"patter\", ...this.host.hostScopes]) if (reg.has(t)) reg.remove(t, { keep });\n if (this.creationOptions.world && reg.has(\"world\")) reg.remove(\"world\");\n }\n\n /** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */\n getProperty(ref: string): ScalarValue | undefined {\n const { scope, name } = this.splitShared(ref);\n return this.host.registry.get(scope, name);\n }\n\n /** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level).\n *\n * This is the GAME's surface, so it writes with host authority: a host-scope declaration's\n * `writable: false` is the story's promise not to write the value, never a lock on the game that\n * owns it. The story's own writes come through `Flow.runEffects`, which never claims it. (Ruled\n * across the family 2026-09-05, after a Storylets venue clock and this project's coverage driver\n * were each refused the one property they existed to move.) */\n setProperty(ref: string, value: ScalarValue): void {\n const { scope, name } = this.splitShared(ref);\n const prev = this.host.journal ? this.host.registry.get(scope, name) : undefined;\n this.host.registry.set(scope, name, value, { host: true });\n if (this.host.journal && prev !== undefined) this.host.journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));\n }\n\n /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current\n * value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */\n /** The run's decisions, in order, each naming the flow it happened in. Empty unless the\n * run was opened with `log: true`. A flow's own log stays flow-local; this is the only\n * place a story spanning several flows reads as one sequence. */\n log(): readonly EngineLogEntry[] {\n return this.engineLog;\n }\n\n /** Drop the retained entries. `seq` does NOT restart: two reads of a log either side of a\n * clear still agree about what came first. */\n clearLog(): void {\n this.engineLog.length = 0;\n }\n\n /** Live tap on the run's decisions, for tooling that wants them as they happen rather than\n * retained. Returns its own unsubscribe. */\n onTrace(handler: EngineTraceHandler): () => void {\n this.engineTraceHandlers.add(handler);\n return () => this.engineTraceHandlers.delete(handler);\n }\n\n private emitEngine(flow: string, event: TraceEvent, scene?: string): void {\n for (const h of this.engineTraceHandlers) h(flow, event);\n if (!this.host.logEnabled) return;\n this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...(scene ? { scene } : {}) });\n }\n\n listProperties(): PropertyRow[] {\n return this.host.patterSharedDecls.map((d) => ({\n name: d.name,\n // The qualified address, matching what the bag composes for every other scope.\n // `@gold` still resolves on input; it is the shorthand, not the address.\n path: `@patter.${d.name}`,\n type: d.type as PropertyType,\n values: d.values,\n stages: d.stages,\n value: this.getProperty(`@${d.name}`),\n default: defaultFor(d),\n // Part of the shared row. Always true here today: `toDecl` never sets it, because\n // Patter has no read-only shared property. The Storylet Engine does declare them\n // and its panels disable the editor accordingly, so the field is carried rather\n // than dropped - and the day a read-only @patter property exists, the row says so.\n writable: d.writable ?? true,\n }));\n }\n\n // @scene is scene-namespaced and needs a flow's current scene - silently\n // routing it into the shared bag (as a junk \"scene.x\" key) was a trap.\n private splitShared(ref: string): { scope: string; name: string } {\n const split = splitHostRef(this.host, ref);\n if (split.scope === \"scene\") {\n throw new Error(`'${ref}': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);\n }\n return split;\n }\n\n /** The shared `@patter` values alone, as `{ patter: {...} }`.\n * @deprecated The registry holds every property now: a game saves `registry.save()` once, and a\n * standalone engine's `saveGame()` carries its registry's values. Removed at the next breaking release. */\n save(): EngineSave {\n return { patter: this.host.patterBag.save() };\n }\n\n /** Lay a `{ patter: {...} }` section over the shared `@patter` values; anything else is ignored.\n * @deprecated See `save`. Removed at the next breaking release. */\n load(blob: EngineSave): void {\n if (blob.patter) this.host.patterBag.load(blob.patter);\n }\n\n /** Snapshot the whole game's NON-property state: visit counts, shared selector cursors, and every\n * live flow's cursor and PRNG. The property values are the registry's: a standalone engine (one that\n * made its own registry) carries them here under `registry`; a game that passed a registry saves it\n * once itself, beside each engine's `saveGame()`. */\n saveGame(): SaveGame {\n const flows: Record<string, FlowSnapshot> = {};\n for (const [id, flow] of this.flowsById) flows[id] = flow.snapshot();\n return {\n version: SAVE_VERSION,\n ...(this.host.ownsRegistry ? { registry: this.host.registry.save() } : {}),\n sharedVisits: Object.fromEntries(this.host.sharedVisits),\n sharedSelectors: serialiseSelectors(this.host.sharedSelectors),\n flows,\n };\n }\n\n /**\n * Restore a `saveGame()`: visit counts, shared selector cursors, and every flow. Property values come\n * from the registry. A save that carries them (a standalone engine's, or a version 2 save from before\n * the registry held them) has them moved into the registry here; otherwise the game loads its\n * registry itself, before or after this call. Either order works: this engine's bags are handed back\n * to the registry (values kept) and the restored flows claim them as they register.\n */\n loadGame(save: SaveGame | SaveGameV2): void {\n this.refuseInCheckpoint(\"loadGame\");\n const version: unknown = (save as { version?: unknown }).version;\n if (version !== 2 && version !== SAVE_VERSION) throw new Error(`unsupported save version: ${String(version)}`);\n // Everything a load reads is checked BEFORE anything changes: a save missing its flows used to close\n // every flow and load the registry, then fail, leaving the engine half-loaded.\n const flows: unknown = (save as { flows?: unknown }).flows;\n if (typeof flows !== \"object\" || flows === null || Array.isArray(flows)) throw new Error(\"malformed save: no flows\");\n this.assertExternalScopes();\n const reg = this.host.registry;\n // Flows the save does not have are over: their bags go. The rest are handed back with their values,\n // which is what a game that loaded its registry first has just laid the save's values over.\n for (const [id, flow] of this.flowsById) { flow.releaseBags(id in save.flows); flow.close(); }\n this.flowsById.clear();\n for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep: true });\n this.host.stageBags.clear();\n\n const values = save.version === 2 ? sectionsFromV2(save) : save.registry;\n if (values) {\n // The engine's own registry takes the save wholesale. A game's registry may hold values the game\n // loaded for other engines, still waiting to be claimed: add to those, never replace them.\n if (this.host.ownsRegistry) reg.load(values);\n else reg.load(values, { keepParked: true });\n }\n this.host.sharedVisits.clear();\n for (const [id, n] of Object.entries(save.sharedVisits ?? {})) this.host.sharedVisits.set(id, n);\n this.host.sharedSelectors.clear();\n for (const [id, st] of deserialiseSelectors(save.sharedSelectors)) this.host.sharedSelectors.set(id, st);\n for (const [id, snap] of Object.entries(save.flows)) {\n const flow = new Flow(id, this.host, this.defaultSeed);\n flow.restore(snap);\n this.flowsById.set(id, flow);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Flow - one playable flow (cursor + the per-flow half of @patter/@scene + PRNG)\n// ---------------------------------------------------------------------------\n\nexport class Flow {\n readonly id: string;\n private readonly host: FlowHost;\n private local: PropertyBag; // the NOT-shared `@patter` globals (this flow's copy), keys.flowGlobals\n /** The registry keys this flow has registered (its globals and each scene bag). */\n private readonly registered = new Set<string>();\n private rngState: number;\n\n // Execution cursor. The `stack` is the continuation stack: each frame is a\n // position within a block's children (the top frame is the active block run;\n // lower frames are pending call-returns). A snippet's beats deliver from\n // `activeSnippet`/`beatIndex`.\n private started = false;\n private flowEnded = false;\n /** Closed by the engine (see `close()`). Terminal, and distinct from `flowEnded`: an ENDED flow is\n * merely out of content and `goto` revives it; a CLOSED one is finished for good. */\n private closed = false;\n private currentSceneId: string | null = null;\n private stack: StackFrame[] = [];\n private activeSnippet: CompiledSnippet | null = null;\n private beatIndex = 0;\n private pendingChoice: ChoiceState | null = null;\n /** When `replayPromptOnChoose`, the chosen option's AUTHORED prompt to deliver before its content: its\n * beat (id, gameData, tags) and the prompt exactly as the choice showed it (text and speaker fields).\n * `shown` is missing only after loading a save written before saves carried it: the beat is then\n * resolved when it is delivered, as it always was. */\n private pendingPrompt: { beat: LineBeat | TextBeat; shown?: ChoicePrompt } | null = null;\n /** The chosen option that owns `pendingPrompt`, so a save taken between choose() and the next\n * advance() can find the prompt beat again on load (it isn't otherwise reachable by id). */\n private pendingPromptOwnerId: string | null = null;\n private selectors = new Map<string, SelectorState>();\n /** Per-node entry counts for this flow (node id -> times entered). */\n private visitCounts = new Map<string, number>();\n\n // Per-flow halves of the two scopes. The NOT-shared `@patter` globals live in\n // `local`; the NOT-shared `@scene` props live in `sceneBags` (namespaced per\n // scene; they PERSIST across re-entries, spec §7). The SHARED halves live on the\n // host (`host.patterBag` / `host.stageBags`). All of them are registered in the\n // game's registry. Each resolver presents one merged scope, routing each property\n // to its half by the declared `shared` flag: that split is why `@patter` and\n // `@scene` are composed here rather than aliased to a single registry key.\n /** This flow's per-scene LOCAL scene props; see FlowHost.stageBags. */\n private sceneBags = new Map<string, PropertyBag>();\n\n private readonly patterResolver: ScopeResolver = {\n get: (n) => (this.host.patterSharedNames.has(n) ? this.host.patterBag.get(n) : this.local.get(n)),\n set: (n, v) => {\n if (this.host.patterSharedNames.has(n)) this.host.registry.set(\"patter\", n, v);\n else this.local.set(n, v);\n },\n };\n\n private readonly sceneResolver: ScopeResolver = {\n get: (n) => {\n const s = this.currentSceneId;\n if (s === null) return undefined;\n return this.sceneBagFor(s, n)?.get(n);\n },\n set: (n, v) => {\n const s = this.currentSceneId;\n if (s === null) return;\n const bag = this.sceneBagFor(s, n);\n // NOT silent, which is the bag's rule rather than an accident: an engine write\n // notifies subscribers and is audited, where a HOST write (an inspector poking a\n // value) is silent but still audited. A scene-prop write during play is the\n // engine's own, so it notifies.\n if (bag) bag.set(n, v);\n },\n };\n\n // The eval context is built once and REFRESHED only when the registry's set of\n // scopes moves (its `revision`): every constituent resolves live state at call\n // time (bags mutate in place per scoperegistry's contract; patter/scene route\n // through this flow's resolvers, which read the current `local`/`sceneBags`/\n // `currentSceneId`; the host callbacks read current flow fields), but another\n // engine registering `@story` after this flow opened must still be readable.\n // Rebuilding it per evaluation was the engine's hottest allocation.\n private readonly evalCtx: EvalContext;\n private ctxRevision = -1;\n private registryQualities: EvalContext[\"qualities\"];\n private readonly flowLog: LogEntry[] = [];\n /** Monotonic across the flow's life; survives clearLog so two reads agree on order. */\n private flowSeq = 0;\n\n constructor(id: string, host: FlowHost, seed: number) {\n this.id = id;\n this.host = host;\n this.rngState = toUint32(seed);\n this.local = this.newLocal(); // registered by start() / restore()\n\n this.evalCtx = {\n scopes: {},\n host: {\n nextRandom: this.rng,\n visits: (id: string) => this.visitCounts.get(id) ?? 0,\n patterVisits: (id: string) => this.host.sharedVisits.get(id) ?? 0,\n },\n // The quality channel (expr 0.4.0): hands the evaluator a property's stage ladder, which is what\n // makes ordering compare by position and advance() step. Wired by hand because this context takes\n // only the registry's SCOPES (the patter/scene resolvers here are the flow's own merged views),\n // and because @scene declarations belong to whichever scene the flow is in RIGHT NOW.\n qualities: (scope, name) => this.stagesFor(scope, name),\n };\n }\n\n /** The eval context, its scopes refreshed if the registry's set of scopes has moved since. */\n private context(): EvalContext {\n const reg = this.host.registry;\n if (reg.revision !== this.ctxRevision) {\n const base = reg.toEvalContext();\n const scopes = this.evalCtx.scopes;\n for (const k of Object.keys(scopes)) delete scopes[k];\n Object.assign(scopes, base.scopes); // every registered scope: other engines' too\n scopes[\"patter\"] = this.patterResolver; // override with the merged shared+per-flow views\n scopes[\"scene\"] = this.sceneResolver;\n this.registryQualities = base.qualities;\n this.ctxRevision = reg.revision;\n }\n return this.evalCtx;\n }\n\n /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare\n * lowercase, as the compiler emits references (the selfBackedResolver lesson). */\n private stagesFor(scope: string, name: string): readonly string[] | undefined {\n const key = name.toLowerCase();\n const fromDecls = (decls: ReadonlyArray<{ name: string; type: string; stages?: string[] }> | undefined) =>\n decls?.find((d) => d.name.toLowerCase() === key && d.type === \"quality\")?.stages;\n if (scope === \"patter\") {\n return fromDecls(this.host.patterSharedDecls) ?? fromDecls(this.host.patterLocalDecls);\n }\n if (scope === \"scene\") {\n const scene = this.currentSceneId != null ? this.host.bundle.scenes[this.currentSceneId] : undefined;\n return fromDecls(scene?.sceneProps);\n }\n // Any other scope's ladder is the registry's (another engine's `@story`, the game's `@world`), with\n // the bundle's own host-scope declarations behind it for a game that registered `@world` undeclared.\n return this.registryQualities?.(scope, name)\n ?? fromDecls(this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations);\n }\n\n // -- Host API -------------------------------------------------------------\n\n /** Begin this flow at a scene (and optionally a specific block within it). */\n start(sceneId?: string, blockId?: string): void {\n // Starting resets this flow's property bags, which a rollback can't put back: only a flow opened\n // inside the checkpoint may start in one.\n if (this.host.journal && !this.host.journal.opened.has(this)) throw new Error(\"a flow can't be started or reset while a checkpoint is open\");\n // A start is a reset: this flow's bags go, and so does anything a load left waiting for them.\n this.releaseBags(false);\n this.host.registry.discardParked(keys.flow(this.id));\n this.mountLocal();\n this.selectors.clear();\n this.visitCounts.clear();\n this.stack = [];\n this.currentSceneId = null;\n this.flowEnded = false;\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.pendingChoice = null;\n this.started = true;\n\n if (blockId) {\n const loc = this.host.blockIndex.get(blockId);\n if (!loc) throw new Error(`unknown block: ${blockId}`);\n this.enterSceneSetup(loc.sceneId);\n this.stack = [{ sceneId: loc.sceneId, containerId: blockId, index: 0 }];\n this.enter(blockId);\n } else {\n const id = sceneId ?? Object.keys(this.host.bundle.scenes)[0];\n const scene = id ? this.host.bundle.scenes[id] : undefined;\n if (!scene) throw new Error(id ? `unknown scene: ${id}` : \"no scenes in bundle\");\n this.enterSceneSetup(id!);\n const first = scene.blocks[0];\n if (first) { this.stack = [{ sceneId: id!, containerId: first.id, index: 0 }]; this.enter(first.id); }\n }\n this.settle();\n }\n\n /**\n * Forget everything in this flow and begin again - its per-flow state (not-shared\n * `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and\n * visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is\n * untouched. A clearer-named alias of `start()`.\n */\n reset(sceneId?: string, blockId?: string): void {\n this.start(sceneId, blockId);\n }\n\n /**\n * Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's\n * `onEntry` effects run, entering counts as a visit, and the callstack is REPLACED - any pending\n * `call` returns are discarded, just as a goto inside a call does.\n *\n * `scene` and `block` are host-facing gameIds (spec §6) or internal ids; `block` is scene-scoped, so\n * it is looked up within `scene`. `\"END\"` ends the flow. To move within the current scene, pass the\n * current scene's address again (`flow.currentScene` -> `engine.sceneAddress`).\n *\n * This is HOST navigation, not authoring, and it takes effect IMMEDIATELY: any beats left in the\n * snippet being delivered are abandoned, and a pending choice is dropped. The format stops an AUTHOR\n * writing a divert into the middle of a snippet; a host teleport is out-of-band, like `reset()` or\n * `loadGame()`. A flow that never started starts here; one that already ended resumes here.\n *\n * Returns false - leaving the cursor exactly where it was - if the address does not resolve. Per-flow\n * state (properties, visit counts, selector cursors) is untouched either way: this MOVES, never resets.\n */\n goto(scene: string, block?: string): boolean {\n if (this.closed) return false; // closed is terminal: unlike \"ended\", a goto cannot revive it\n this.touch();\n if (scene === \"END\") {\n this.started = true; this.pendingChoice = null; this.pendingPrompt = null; this.pendingPromptOwnerId = null;\n this.activeSnippet = null; this.beatIndex = 0;\n this.flowEnded = true; this.stack = [];\n return true;\n }\n // Resolve BOTH addresses before touching any state, so a bad one is a no-op rather than a half-move.\n const sceneId = resolveScene(this.host, scene);\n if (sceneId === undefined) return false;\n let blockId: string | undefined;\n if (block !== undefined) {\n blockId = resolveBlock(this.host, sceneId, block);\n if (blockId === undefined) return false; // a block address is scene-scoped: unknown HERE is unknown\n }\n // Never started: start() does the same landing plus the one-time per-flow setup.\n if (!this.started) { this.start(sceneId, blockId); return true; }\n\n this.pendingChoice = null; this.pendingPrompt = null; this.pendingPromptOwnerId = null;\n this.activeSnippet = null; this.beatIndex = 0; // abandon the rest of the snippet being delivered\n this.flowEnded = false; // an ended flow resumes at the target\n this.enterTarget(blockId ?? sceneId, \"jump\"); // \"jump\" = replace the stack, exactly like an authored goto\n this.settle();\n return true;\n }\n\n /**\n * Finish this flow for good. Engine-managed: `engine.closeFlow(id)`, `engine.reset()`, and re-opening\n * a name with `engine.openFlow` all call it on the flow being dropped.\n *\n * A dropped flow used to stay fully live: unregistered and invisible to `engine.flows()`, but a host\n * still holding the object could keep advancing it, and every scene `onEntry`, shared property, world\n * visit count and shared selector cursor it touched still landed on the engine. Closing makes that\n * stale reference inert - `advance()` reports the end and `goto()` refuses - so a forgotten reference\n * cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.\n */\n close(): void {\n this.releaseBags(false);\n this.closed = true;\n this.flowEnded = true;\n this.stack = [];\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.pendingChoice = null;\n this.pendingPrompt = null;\n this.pendingPromptOwnerId = null;\n }\n\n /** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can\n * put them back: a handful of fields and a copy of the call stack (a few frames), never its history.\n * Its visits, selector cursors, and property values are recorded change by change as they happen. */\n private touch(): void {\n const journal = this.host.journal;\n if (!journal || journal.flows.has(this)) return;\n journal.flows.add(this);\n const { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPrompt, pendingPromptOwnerId } = this;\n const stack = this.stack.map((f) => ({ ...f })); // frames advance in place, so copy them\n journal.undo.push(() => {\n Object.assign(this, { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPrompt, pendingPromptOwnerId, stack });\n });\n }\n\n /** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */\n get isClosed(): boolean { return this.closed; }\n\n /** The scene the cursor is currently in - set on entry and whenever a jump crosses scenes. Read\n * right after `advance()` to know which scene the just-played beat lives in (tooling that mirrors\n * the playhead, e.g. an editor following a cross-scene jump). `null` before the flow has started. */\n get currentScene(): string | null { return this.currentSceneId; }\n\n /** Run until the next line, game event, choice, or the end of the flow. */\n advance(): StepResult {\n if (this.closed) return { type: \"end\" }; // a stale reference to a closed flow drives nothing\n if (!this.started) throw new Error(\"flow has not been started\");\n this.touch();\n // A replayed prompt (replayPromptOnChoose) is delivered first, before the option's content.\n if (this.pendingPrompt) { const p = this.pendingPrompt; this.pendingPrompt = null; this.pendingPromptOwnerId = null; return p.shown ? this.promptResult(p.beat, p.shown) : this.beatResult(p.beat); }\n this.settle();\n if (this.flowEnded) return { type: \"end\" };\n if (this.pendingChoice) return { type: \"choice\", groupId: this.pendingChoice.groupId, options: this.pendingChoice.options };\n if (!this.activeSnippet) { this.flowEnded = true; return { type: \"end\" }; }\n return this.beatResult(this.activeSnippet.beats![this.beatIndex++]!);\n }\n\n /**\n * Advance repeatedly, collecting every played beat, until a choice or the end - the \"play to the\n * next stop\" a host's play UI / tooling wants. The terminal `choice` / `end` is returned as `stop`;\n * `played` holds the line / text / game-event results walked on the way to it. Termination is guaranteed\n * (each `advance()` makes progress or `settle()` throws on a contentless jump cycle).\n */\n advanceToStop(): AdvanceToStopResult {\n const played: AdvanceToStopResult[\"played\"] = [];\n for (;;) {\n const r = this.advance();\n if (r.type === \"choice\" || r.type === \"end\") return { played, stop: r };\n played.push(r); // narrowed to line / text / game-event by the guard above\n }\n }\n\n /**\n * Drive the cursor to the next *deliverable* stop: a beat ready on the active\n * snippet, a pending choice, or the end. Runs onExit/jump seams and walks the\n * block run (sequentially, skipping ineligible children); a finished block pops\n * to its caller (call-return) or ends the flow.\n */\n private settle(): void {\n let transitions = 0;\n for (;;) {\n // Static validation cannot rule out jump cycles (conditions gate them),\n // so a content bug like two pure jumps jumping at each other must be an\n // error, not a hang.\n if (++transitions > 10_000) {\n throw new Error(\"flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content\");\n }\n if (this.flowEnded || this.pendingChoice) return;\n\n if (this.activeSnippet) {\n if (this.beatIndex < (this.activeSnippet.beats?.length ?? 0)) return; // a beat is ready\n this.runEffects(this.activeSnippet.onExit, this.activeSnippet.id);\n const jump = this.activeSnippet.jump;\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.resolveJump(jump);\n continue;\n }\n\n const frame = this.stack[this.stack.length - 1];\n if (!frame) { this.flowEnded = true; return; }\n if (frame.sceneId !== this.currentSceneId) this.currentSceneId = frame.sceneId; // resumed scene (no reseed)\n const children = this.childrenOf(frame.containerId);\n if (!children) { this.stack.pop(); continue; } // drifted container -> skip the frame\n // A `run` container walks its children in order, skipping the ones whose condition\n // does not hold. That skip IS the decision an author asks about - \"why did my line\n // not appear\" - so the trace records the ones it walked past on the way, not only\n // the one it entered. selectChild covers branch/sequence; this covers the default,\n // which is what a block is.\n const from = frame.index;\n while (frame.index < children.length && !this.eligible(children[frame.index]!)) frame.index++;\n if (this.host.logEnabled && frame.index !== from) {\n this.emit({\n type: \"select\", group: frame.containerId, selector: \"run\",\n children: children.slice(from, frame.index + 1)\n .map((c, i) => ({ id: c.id, eligible: from + i === frame.index })),\n picked: children[frame.index]?.id ?? null,\n });\n }\n if (frame.index >= children.length) { this.stack.pop(); continue; } // run exhausted -> resume caller\n this.enterChild(children[frame.index++]!); // advance past it: that's the gather/return point\n }\n }\n\n /**\n * THIS flow's own kernel bags: its not-shared `@patter` half and its per-scene `@scene`\n * props, each prefixed with the flow id so one path space holds every flow. The shared\n * halves are the Engine's listBags.\n */\n listBags(): LogMount[] {\n const mounts: LogMount[] = [{ bag: this.local, pathPrefix: `${this.id}/@patter.` }];\n for (const [sceneId, bag] of this.sceneBags) mounts.push({ bag, pathPrefix: `${this.id}/@scene:${sceneId}.` });\n return mounts;\n }\n\n /** This flow's decisions, in order. Empty unless the run was opened with `log: true`.\n * The engine's log carries the same events tagged with the flow; this one is what a\n * single conversation reads as. */\n log(): readonly LogEntry[] {\n return this.flowLog;\n }\n\n /** Drop the retained entries. `seq` keeps counting, so order survives a clear. */\n clearLog(): void {\n this.flowLog.length = 0;\n }\n\n /** Record one decision, on this flow's log and the engine's. Cheap to call with logging\n * off: the entry is never built. */\n private emit(event: TraceEvent): void {\n const scene = this.currentSceneId ?? undefined;\n this.host.emitEngine(this.id, event, scene);\n if (!this.host.logEnabled) return;\n this.flowLog.push({ ...event, seq: this.flowSeq++, ...(scene ? { scene } : {}) });\n }\n\n /** The options of a pending choice (empty when not at a choice point). */\n getChoices(): ChoiceOption[] {\n return this.pendingChoice?.options ?? [];\n }\n\n /** Pick an eligible option by id; the next `advance()` runs it. */\n choose(id: string): void {\n const choice = this.pendingChoice;\n if (!choice) throw new Error(\"no choice is pending\");\n const option = choice.options.find((o) => o.id === id);\n if (!option) throw new Error(`unknown choice option: ${id}`);\n if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);\n this.touch();\n const node = choice.byId.get(id)!;\n this.emit({ type: \"chose\", group: choice.groupId, option: id });\n this.pendingChoice = null;\n // Optionally speak the chosen option's prompt back as its first beat (spec §5): only an AUTHORED\n // prompt, and exactly as the choice showed it. A prompt borrowed from the option's own first content\n // line is not replayed, since that line is about to play as content anyway.\n const authored = this.host.replayPromptOnChoose ? this.authoredPromptOf(node) : undefined;\n this.pendingPrompt = authored && option.prompt ? { beat: authored, shown: { ...option.prompt } } : null;\n this.pendingPromptOwnerId = this.pendingPrompt ? node.id : null;\n // The block frame is already advanced past the choice group (the gather point),\n // so when the chosen option finishes without a jump, the flow continues there.\n this.enterChild(node);\n }\n\n isEnded(): boolean {\n return this.flowEnded;\n }\n\n /** Read a property by ref - `@patter` / `@scene` (each routed by its `shared` flag) or foreign. */\n getProperty(ref: string): ScalarValue | undefined {\n const { scope, name } = this.splitRef(ref);\n if (scope === \"patter\") return this.patterResolver.get(name);\n if (scope === \"scene\") return this.sceneResolver.get(name);\n return this.host.registry.get(scope, name); // host scopes, other engines' scopes\n }\n\n /** Write a property by ref (routed by scope, then by the property's `shared` flag). The GAME's\n * surface: it writes with host authority, so a `writable: false` host declaration does not refuse\n * it (see `Engine.setProperty`). The story's writes go through `writeProperty` instead. */\n setProperty(ref: string, value: ScalarValue): void {\n this.writeProperty(ref, value, true);\n }\n\n /** The write itself. `host` says WHO is writing, which is the only thing `writable: false` cares\n * about: the story is refused, the game is not. */\n private writeProperty(ref: string, value: ScalarValue, host: boolean): void {\n const { scope, name } = this.splitRef(ref);\n const journal = this.host.journal;\n if (scope === \"patter\") {\n // Inside a checkpoint, each write records how to put the old value back, against the bag it\n // actually landed in (a `@scene` write's bag depends on the scene the flow is in at the time).\n if (journal) {\n const shared = this.host.patterSharedNames.has(name);\n const prev = shared ? this.host.patterBag.get(name) : this.local.get(name);\n const bag = this.local;\n if (prev !== undefined) journal.undo.push(shared ? () => this.host.registry.set(\"patter\", name, prev) : () => bag.set(name, prev));\n }\n this.patterResolver.set!(name, value);\n } else if (scope === \"scene\") {\n // The resolver stays graceful for expression evaluation, but a host write\n // with nowhere to land must error, not silently vanish.\n if (this.currentSceneId === null) throw new Error(`'${ref}': the flow has not entered a scene yet`);\n if (journal) {\n const bag = this.sceneBagFor(this.currentSceneId, name);\n const prev = bag?.get(name);\n if (bag && prev !== undefined) journal.undo.push(() => bag.set(name, prev));\n }\n this.sceneResolver.set!(name, value);\n } else {\n const prev = journal ? this.host.registry.get(scope, name) : undefined;\n this.host.registry.set(scope, name, value, host ? { host: true } : undefined); // host / other scopes\n // Recorded after the write: one a read-only host scope refused never happened, so has nothing to undo.\n if (journal && prev !== undefined) journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));\n }\n }\n\n // -- Save / restore (engine-driven) --------------------------------------\n\n /** @internal Snapshot this flow's cursor + PRNG + visits. Its properties are the registry's. */\n snapshot(): FlowSnapshot {\n return {\n rngState: this.rngState,\n visits: Object.fromEntries(this.visitCounts),\n cursor: {\n flowEnded: this.flowEnded,\n currentSceneId: this.currentSceneId,\n // Stamp each frame with the id of the child it would run next (nextId), so a restore against\n // an EDITED bundle re-finds the position by id instead of trusting the raw index (§9.8 /\n // live bundle refresh). A frame saved at its container's end has no next child - no stamp.\n stack: this.stack.map((f) => {\n const next = this.childrenOf(f.containerId)?.[f.index];\n return next ? { ...f, nextId: next.id } : { ...f };\n }),\n activeSnippetId: this.activeSnippet?.id ?? null,\n beatIndex: this.beatIndex,\n pendingChoice: this.pendingChoice\n ? { groupId: this.pendingChoice.groupId, options: this.pendingChoice.options.map((o) => ({ ...o })) }\n : null,\n pendingPromptOwnerId: this.pendingPromptOwnerId,\n // The prompt still to be replayed, as the choice showed it: present only in the choose -> advance window.\n ...(this.pendingPrompt?.shown ? { pendingPrompt: savedPrompt(this.pendingPrompt.shown) } : {}),\n selectors: serialiseSelectors(this.selectors),\n },\n };\n }\n\n /** @internal Restore this flow from a snapshot: its cursor, PRNG, visits, and selector cursors. Its\n * property values are the registry's and stay as they are (a load puts the saved ones there first). */\n restore(snap: FlowSnapshot): void {\n if (this.host.journal) throw new Error(\"a flow can't be restored while a checkpoint is open\");\n this.restoreCursor(snap);\n // Register this flow's bags: each claims the values the registry holds for it (loaded by the game, by\n // loadGame from the save, or handed back by the engine this one replaces), laid over fresh defaults.\n // Released KEEPING their values, so a live flow restored in place keeps its property values (a fresh\n // one, made by a load, is claiming the loaded values either way). The scenes the cursor stands in are\n // registered now; any other scene's bag is claimed on entry.\n this.releaseBags(true);\n this.mountLocal();\n for (const s of new Set([this.currentSceneId, ...this.stack.map((f) => f.sceneId)])) {\n if (s !== null && this.host.bundle.scenes[s]) this.ensureSceneBags(s);\n }\n }\n\n /** The cursor half of `restore`, without touching the bags (a rollback has already put those back). */\n private restoreCursor(snap: FlowSnapshot): void {\n this.rngState = toUint32(snap.rngState);\n this.visitCounts = new Map(Object.entries(snap.visits ?? {}));\n const c = snap.cursor;\n this.started = true;\n this.flowEnded = c.flowEnded;\n this.beatIndex = c.beatIndex;\n this.currentSceneId = c.currentSceneId;\n // Re-bind each frame to the CURRENT bundle: prefer the saved next-child id (survives siblings\n // inserted / removed / reordered before the cursor); fall back to the raw index when the id is\n // absent (an older save) or its node drifted out of the bundle (§9.8 best-effort).\n this.stack = c.stack.map((f) => {\n const { nextId, ...frame } = f;\n if (nextId !== undefined) {\n const at = this.childrenOf(frame.containerId)?.findIndex((ch) => ch.id === nextId) ?? -1;\n if (at >= 0) return { ...frame, index: at };\n }\n return { ...frame };\n });\n\n // Content-drift policy (§9.8): if a saved position points at content deleted\n // since the save, resume best-effort rather than throwing - the missing\n // snippet / choice is dropped and play continues from the surviving stack.\n this.activeSnippet = null;\n if (c.activeSnippetId !== null) {\n const node = this.host.nodeIndex.get(c.activeSnippetId);\n if (node && node.type === \"snippet\") this.activeSnippet = node;\n }\n\n this.selectors = deserialiseSelectors(c.selectors); // this flow's (non-shared) selector cursors\n\n // Replay the saved option set VERBATIM (schema 9.3) - re-deriving would\n // re-evaluate conditions (double-consuming PRNG draws) and could change the\n // choice under the player. Options whose nodes drifted out of the bundle\n // are dropped; a choice with no surviving options dissolves (9.8).\n this.pendingChoice = null;\n if (c.pendingChoice !== null) {\n const byId = new Map<string, SelectableNode>();\n const options: ChoiceOption[] = [];\n for (const o of c.pendingChoice.options) {\n const node = this.host.nodeIndex.get(o.id);\n if (!node) continue;\n byId.set(o.id, node);\n options.push({ ...o });\n }\n if (options.length > 0) this.pendingChoice = { groupId: c.pendingChoice.groupId, options, byId };\n }\n\n // A save taken between choose() and the next advance() left a prompt still to be replayed\n // (replayPromptOnChoose): the chosen option's authored prompt beat, found again by its owner, and the\n // prompt as the choice showed it, carried by the save. A save from before `pendingPrompt` existed has\n // only the owner, and its beat is resolved when delivered, as it was then. Dropped if the option\n // drifted out of the bundle or has no authored prompt (§9.8): the live choose() would replay nothing.\n this.pendingPrompt = null;\n this.pendingPromptOwnerId = c.pendingPromptOwnerId ?? null;\n if (this.pendingPromptOwnerId) {\n const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);\n const beat = owner ? this.authoredPromptOf(owner) : undefined;\n if (beat) this.pendingPrompt = { beat, ...(c.pendingPrompt ? { shown: { ...c.pendingPrompt } } : {}) };\n else this.pendingPromptOwnerId = null;\n }\n }\n\n // -- Scene / block / node entry ------------------------------------------\n\n /** Set the current scene, reset its scene-local props, run onEntry. */\n private enterSceneSetup(sceneId: string): void {\n const scene = this.host.bundle.scenes[sceneId];\n if (!scene) throw new Error(`unknown scene: ${sceneId}`);\n this.currentSceneId = sceneId;\n this.enter(sceneId);\n this.seedScene(scene); // seeds @scene defaults (per-flow on first entry; shared once globally)\n this.runEffects(scene.onEntry, scene.id); // on-entry effects still fire every entry (spec §4)\n }\n\n /**\n * Play one child of the active run. A snippet begins delivering. A group is\n * walked by its selector: the default `run` pushes a nested run (its children\n * play in order, gathering back); `choice` stops for the host; a select-one\n * selector (branch, or a `sequence` in any order x exhaust mode) picks ONE child (recursing\n * to a leaf) - selecting nothing contributes no content and the run continues.\n */\n private enterChild(node: SelectableNode): void {\n this.enter(node.id);\n if (node.type === \"snippet\") { this.beginSnippet(node); return; }\n const selector = node.selector ?? \"run\";\n if (selector === \"run\") {\n this.stack.push({ sceneId: this.currentSceneId!, containerId: node.id, index: 0 });\n return;\n }\n if (selector === \"choice\") { this.setupChoice(node); return; }\n const pick = this.selectChild(node);\n if (pick) this.enterChild(pick);\n }\n\n /** A container's children, whether it's a block or a run-group; undefined if the id is gone. */\n private childrenOf(containerId: string): SelectableNode[] | undefined {\n const block = this.host.blockById.get(containerId);\n if (block) return block.children;\n const node = this.host.nodeIndex.get(containerId);\n if (node && node.type === \"group\") return node.children;\n return undefined; // content drift: the container was deleted since the save\n }\n\n private beginSnippet(snippet: CompiledSnippet): void {\n this.runEffects(snippet.onEnter, snippet.id);\n this.activeSnippet = snippet;\n this.beatIndex = 0;\n }\n\n private setupChoice(group: CompiledGroup): void {\n const options: ChoiceOption[] = [];\n const byId = new Map<string, SelectableNode>();\n const fallbacks: SelectableNode[] = [];\n for (const child of group.children) {\n // An option is an Option group (its content runs + gathers back) or - the\n // degenerate shape - a single snippet. prompt / sticky / fallback / secretUntilEligible\n // live on whichever (spec §5).\n if (child.fallback === true) { fallbacks.push(child); continue; } // never a normal option; auto-followed when last\n // Once-only (default): once the player has followed it, it is GONE from the choice entirely -\n // not delivered, not flagged unavailable, simply absent. A `sticky` option is never consumed,\n // so it stays available as long as its condition passes. Consumption is the existing per-flow\n // visit count, so it persists through save/restore for free.\n if (child.sticky !== true && (this.visitCounts.get(child.id) ?? 0) >= 1) continue;\n const eligible = this.eligible(child);\n const hidden = child.secretUntilEligible === true;\n if (!eligible && hidden) continue; // secret while ineligible; otherwise an ineligible option shows greyed\n options.push({ id: child.id, prompt: this.promptFor(child), eligible, gameData: child.gameData });\n byId.set(child.id, child);\n }\n // A choice is offered only when the player can take something. One whose every remaining option is\n // greyed out left the player stuck in front of it, so it runs dry instead, as a choice with no\n // options does: the fallback follows if there is one, otherwise the flow moves on.\n if (options.some((o) => o.eligible)) {\n // The offered set, including options a condition left ineligible: an author asking\n // \"why is that option greyed?\" is asking about this moment, and a log of the taken\n // option alone cannot answer it. Options hidden by secretUntilEligible are absent\n // here too, exactly as the player sees them.\n this.emit({ type: \"choice\", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });\n this.pendingChoice = { groupId: group.id, options, byId };\n return;\n }\n // No normal option can be taken. Auto-follow the fallback if it is eligible (its own condition still\n // applies); otherwise the choice GATHERS - it contributes nothing and the run continues past it\n // (a dry choice falls through rather than deadlocking; the validator warns about choices that can\n // run dry with no fallback).\n const fallback = fallbacks.find((f) => this.eligible(f));\n if (fallback) { this.enterChild(fallback); return; }\n // Nothing takeable and no eligible fallback: the choice runs dry and the flow walks past it. The\n // behaviour is unchanged; the opt-in diagnostics hook makes this silent fall-through observable.\n this.emit({ type: \"dry\", group: group.id });\n // The callback stays beside the log deliberately. They are not the same thing: this is\n // live feedback a host acts on the moment it happens, the log is read afterwards, and a\n // shipped game runs with the log off and this hook still wired.\n this.host.onDryChoice?.(group.id);\n }\n\n // -- Jumps (jump / call-return) ----------------------------------------\n\n private resolveJump(jump: Jump | undefined): void {\n // No jump: gather - the snippet falls through and the block run continues\n // (settle's frame walk picks the next child, or pops to a caller).\n if (!jump) return;\n this.enterTarget(jump.to, jump.mode === \"call\" ? \"call\" : \"jump\");\n }\n\n /**\n * Route to a target (scene / block / `END`). `call` PUSHES a return frame (the\n * caller's block run, already advanced to its next child, stays below); `jump`\n * is absolute - it REPLACES the whole stack, discarding pending returns. `END`\n * hard-ends the flow regardless of the callstack.\n */\n private enterTarget(to: string, mode: \"call\" | \"jump\"): void {\n this.emit({ type: \"jump\", to, mode });\n if (to === \"END\") { this.flowEnded = true; this.stack = []; return; }\n\n let sceneId: string;\n let containerId: string;\n const scene = this.host.bundle.scenes[to];\n if (scene) {\n this.enterSceneSetup(to);\n const first = scene.blocks[0];\n if (!first) { if (mode === \"jump\") this.stack = []; return; } // empty scene\n sceneId = to; containerId = first.id;\n } else {\n const loc = this.host.blockIndex.get(to);\n if (!loc) throw new Error(`jump target not found: ${to}`);\n if (loc.sceneId !== this.currentSceneId) this.enterSceneSetup(loc.sceneId);\n sceneId = loc.sceneId; containerId = to;\n }\n\n this.enter(containerId); // count the entered block\n const frame: StackFrame = { sceneId, containerId, index: 0 };\n if (mode === \"call\") this.stack.push(frame);\n else this.stack = [frame];\n }\n\n // -- Selectors ------------------------------------------------------------\n\n private selectChild(group: CompiledGroup): SelectableNode | null {\n // Each condition is evaluated ONCE: the verdicts and the eligible list come from the same pass. Twice\n // cost double, and a condition calling random() drew twice, so its verdict and the pick could disagree.\n const verdicts = group.children.map((c) => ({ id: c.id, eligible: this.eligible(c) }));\n const eligible = group.children.filter((_, i) => verdicts[i]!.eligible);\n const order = group.options?.order ?? \"sequential\";\n const exhaust = group.options?.exhaust ?? \"once\";\n // The reasoning goes in the entry, not just the outcome: every child this looked at,\n // with why it was or was not takeable. \"The line I expected is missing\" is only\n // answerable if the log says which sibling was dropped and that it was dropped here.\n const trace = (picked: SelectableNode | null): SelectableNode | null => {\n this.emit({\n type: \"select\", group: group.id, selector: group.selector ?? \"default\",\n ...(group.selector === \"sequence\" ? { order, exhaust } : {}),\n children: verdicts, picked: picked?.id ?? null,\n });\n return picked;\n };\n\n if (eligible.length === 0) return trace(null);\n const st = this.selectorState(group);\n\n switch (group.selector) {\n case \"branch\":\n return trace(eligible[0]!);\n\n case \"sequence\":\n return trace(order === \"shuffle\" ? this.pickShuffle(eligible, exhaust, st)\n : order === \"specificity\" ? this.pickSpecificity(eligible, exhaust, st)\n : this.pickSequential(eligible, exhaust, st));\n\n case \"run\":\n case \"choice\":\n default:\n return null; // run / choice / default are handled in enterChild, not here\n }\n }\n\n /** `sequence` with `order: \"sequential\"` - walk children in authored order. */\n private pickSequential(eligible: SelectableNode[], exhaust: string, st: SelectorState): SelectableNode | null {\n const len = eligible.length;\n const n = st.seq ?? 0;\n st.seq = n + 1;\n if (exhaust === \"repeat\") return eligible[n % len]!; // cycle\n if (n < len) return eligible[n]!; // still in the first pass\n if (exhaust === \"stick\") return eligible[len - 1]!; // hold the last forever (stopping)\n return null; // once: nothing after the pass\n }\n\n /**\n * `sequence` with `order: \"shuffle\"` - draw WITHOUT replacement (a bag), never\n * repeating the immediately-previous pick across a reshuffle (no line twice in a\n * row when >=2 are eligible). `stick` holds out the last authored child as the\n * permanent terminal; `once` stops after one pass; `repeat` reshuffles.\n */\n private pickShuffle(eligible: SelectableNode[], exhaust: string, st: SelectorState): SelectableNode | null {\n const len = eligible.length;\n const stick = exhaust === \"stick\";\n const fill = (): string[] => (stick ? eligible.slice(0, len - 1) : eligible).map((c) => c.id);\n const eligibleIds = new Set(eligible.map((c) => c.id));\n\n if (st.bag === undefined) st.bag = fill();\n // The bag was filled from the children eligible THEN. Draw only from those still eligible now, so a\n // child whose condition has since gone false is never drawn (it used to be drawn, and the group then\n // played nothing). If none of the bag is drawable, the pass is over, exactly as when the bag is empty.\n if (!st.bag.some((id) => eligibleIds.has(id))) {\n if (exhaust === \"once\") return null;\n if (stick) { const last = eligible[len - 1]!; st.last = last.id; return last; }\n st.bag = fill(); // repeat: reshuffle\n if (st.bag.length === 0) return null;\n }\n\n // Draw without replacement, never repeating the immediately-previous pick when another is drawable.\n const pool = st.bag.filter((id) => eligibleIds.has(id));\n const p = st.last !== undefined && pool.length > 1 ? pool.indexOf(st.last) : -1;\n let i = Math.floor(this.rng() * (p >= 0 ? pool.length - 1 : pool.length));\n if (p >= 0 && i >= p) i++;\n const id = pool[i]!;\n st.bag.splice(st.bag.indexOf(id), 1);\n st.last = id;\n return eligible.find((c) => c.id === id)!;\n }\n\n\n /**\n * `sequence` with `order: \"specificity\"` - **Best match**: score every eligible child by how\n * specifically its condition fits the CURRENT state (`matchedSpec`), keep the top-scoring tier,\n * and break ties with the seeded shuffle (no immediate repeat). A child with no condition scores\n * 0, so it is the filler that wins only when nothing more specific is eligible.\n *\n * `exhaust` composes as it does for the other orders: `repeat` re-scores the full eligible set\n * every draw (re-pickable - the character keeps preferring the on-topic line); `once` uses each\n * pick up (a bag of remaining ids), so as specific lines are consumed the group slides down to\n * less-specific ones and finally the filler, then yields null; `stick` degrades like `once` but\n * holds the final pick forever instead of drying up.\n */\n private pickSpecificity(eligible: SelectableNode[], exhaust: string, st: SelectorState): SelectableNode | null {\n let pool = eligible;\n if (exhaust !== \"repeat\") { // once / stick: draw without replacement\n if (st.bag === undefined) st.bag = eligible.map((c) => c.id);\n const remaining = new Set(st.bag);\n pool = eligible.filter((c) => remaining.has(c.id));\n if (pool.length === 0) { // every child used up\n return exhaust === \"stick\" && st.last !== undefined\n ? eligible.find((c) => c.id === st.last) ?? null // hold the last pick if still eligible\n : null;\n }\n }\n\n // Top specificity tier among the drawable pool.\n let best = -1;\n const scored = pool.map((c) => { const s = this.specScore(c); if (s > best) best = s; return { c, s }; });\n const tier = scored.filter((x) => x.s === best).map((x) => x.c);\n\n // Tie-break by the seeded PRNG, never repeating the immediately-previous pick (matches shuffle).\n // A lone top-tier child is returned WITHOUT drawing, so a clear winner consumes no randomness.\n let pick: SelectableNode;\n if (tier.length === 1) {\n pick = tier[0]!;\n } else {\n const p = st.last !== undefined ? tier.findIndex((c) => c.id === st.last) : -1;\n let i = Math.floor(this.rng() * (p >= 0 ? tier.length - 1 : tier.length));\n if (p >= 0 && i >= p) i++;\n pick = tier[i]!;\n }\n\n if (exhaust !== \"repeat\") st.bag = st.bag!.filter((id) => id !== pick.id);\n st.last = pick.id;\n return pick;\n }\n\n /** A child's Best-match score against the current state: 0 when it has no condition (the filler\n * tier), else the specificity of its (already-passing) condition. */\n private specScore(node: SelectableNode): number {\n return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true, node) : 0;\n }\n\n /**\n * The **matched-specificity** metric (parity contract): how many atomic constraints are actively\n * holding this condition TRUE against the live state. Evaluation-aware, not a static clause count -\n * it walks the tree with a De-Morgan polarity flag so `or` and `not` score the branch that is\n * actually carrying the truth. `want` = \"does this subtree need to be true for the whole condition\n * to hold?\" (true at the root). Only `and`/`or`/`not`/`check_flags` are structural; every other\n * node (comparisons, scoped vars, literals, other calls) is an atom, evaluated whole.\n */\n private matchedSpec(node: ExprNode, want: boolean, owner: SelectableNode): number {\n // Delegates to the shared @wildwinter/expr-specificity scorer (same walk,\n // shared with Storylet Studio). We supply Patter's truthiness rule and keep\n // check_flags counting via the package's default counting call. The scorer walks every part,\n // including an `or` branch eligibility never evaluated, so a part can fail here that did not fail\n // there: it scores as false (see PlayError), as on the other three runtimes.\n const evalTruthy: EvalTruthy = (n) => {\n try { return truthy(evaluate(n, this.context(), patterDialect)); }\n catch (err) { this.reportError(\"best-match\", owner.id, owner.condition, err); return false; }\n };\n return scoreSpecificity(node, evalTruthy, { want });\n }\n\n /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */\n private selectorState(group: CompiledGroup): SelectorState {\n const map = group.shared ? this.host.sharedSelectors : this.selectors;\n // A cursor is copied the first time a checkpoint sees it (shared ones once for every flow).\n const journal = this.host.journal;\n let seen: Set<string> | undefined;\n if (journal) {\n seen = group.shared ? journal.selectors : journal.flowSelectors.get(this);\n if (!seen) { seen = new Set(); journal.flowSelectors.set(this, seen); }\n }\n if (journal && seen && !seen.has(group.id)) {\n seen.add(group.id);\n const was = map.get(group.id);\n const copy = was ? { ...was, ...(was.bag ? { bag: [...was.bag] } : {}) } : undefined;\n journal.undo.push(() => { if (copy) map.set(group.id, copy); else map.delete(group.id); });\n }\n let st = map.get(group.id);\n if (!st) { st = {}; map.set(group.id, st); }\n return st;\n }\n\n // -- Effects + expressions ------------------------------------------------\n\n /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */\n private runEffects(effects: CompiledEffect[] | undefined, owner: string): void {\n // SET-ONLY (spec §15): an effect mutates a property. Host events ride on gameData, not effects.\n for (const e of effects ?? []) {\n let value: ScalarValue;\n try {\n value = this.evalExpr(e.value);\n // `prev` read before the write, so a reader can say \"0 -> 1\" without a second pass.\n // Only paid for when the run asked for a log.\n const prev = this.host.logEnabled ? this.getProperty(e.target) : undefined;\n this.writeProperty(e.target, value, false); // the STORY writes: a read-only host property refuses it\n this.emit({ type: \"write\", target: e.target, value, ...(prev !== undefined ? { prev } : {}) });\n } catch (err) {\n // Skipped, reported, and the rest of the list still runs (see PlayError).\n this.reportError(\"effect\", owner, e.value, err);\n }\n }\n }\n\n /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */\n private eligible(node: SelectableNode): boolean {\n if (!node.condition) return true;\n try { return truthy(this.evalExpr(node.condition)); }\n catch (err) { this.reportError(\"condition\", node.id, node.condition, err); return false; }\n }\n\n /** Report a content error the engine is playing through: to the host's onError, and to the log. */\n private reportError(kind: PlayError[\"kind\"], node: string, expr: Expression | undefined, err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n const source = expr?.src;\n this.host.onError({ flow: this.id, kind, node, ...(source ? { source } : {}), message });\n this.emit({ type: \"diagnostic\", kind, node, ...(source ? { source } : {}), message });\n }\n\n private evalExpr(expr: Expression): ScalarValue {\n return evaluate(this.conditionAst(expr), this.context(), patterDialect);\n }\n\n /** The deserialised (in-memory) AST for an expression, cached per Expression. Shared by the\n * evaluator and the Best-match specificity walker so both work off one parse. */\n private conditionAst(expr: Expression): ExprNode {\n let ast = astCache.get(expr);\n if (!ast) { ast = deserialiseAst(expr.ast); astCache.set(expr, ast); }\n return ast;\n }\n\n /** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */\n private enter(id: string): void {\n const own = this.visitCounts;\n const ownBefore = own.get(id);\n own.set(id, (ownBefore ?? 0) + 1);\n const shared = this.host.sharedVisits;\n const before = shared.get(id);\n shared.set(id, (before ?? 0) + 1);\n if (this.host.journal) this.host.journal.undo.push(() => {\n if (ownBefore === undefined) own.delete(id); else own.set(id, ownBefore);\n if (before === undefined) shared.delete(id); else shared.set(id, before);\n });\n }\n\n /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32.\n *\n * The mixing is @wildwinter/expr's makePrng, not a copy inline here. It is a\n * fixed published algorithm that both product families need and neither\n * owns, and it existed thirteen times across the two of them. `rngState` is\n * still the serialisable position, so saves are unaffected: a generator is\n * made from it, drawn once, and the new state written back. */\n private readonly rng = (): number => {\n if (this.host.customRng) return this.host.customRng();\n const prng = makePrng(this.rngState);\n const draw = prng.next();\n this.rngState = prng.state();\n return draw;\n };\n\n // -- Strings / beats ------------------------------------------------------\n\n private beatResult(beat: Beat): StepResult {\n // Accumulated author tags (#215): the beat's own tags unioned with every\n // ancestor's. Omitted from the step when empty (parity with `gameData`).\n const tags = this.host.tagIndex.get(beat.id);\n const withTags = tags && tags.length ? { tags } : {};\n // Inline `{@ref}` interpolation (spec §16): text beats always interpolate;\n // line beats interpolate only in a non-voiced project (voiced lines are\n // static). Game-event beats carry no localised content.\n switch (beat.kind) {\n case \"gameEvent\":\n return { type: \"gameEvent\", id: beat.id, gameData: beat.gameData, ...withTags };\n case \"text\":\n return { type: \"text\", id: beat.id, text: this.interpolate(this.resolveString(beat.id)), gameData: beat.gameData, ...withTags };\n case \"line\": {\n const raw = this.resolveString(beat.id);\n // Closed captions (#214) apply to DIALOGUE lines only: strip cues when captions are off. Two ways a\n // line goes SILENT (off only): the caption CHARACTER speaks it (whole line is a caption - omit all\n // dialogue, delimiters or not), or stripping cues leaves it empty. A silent line still FIRES (audio\n // plays + visits count) but carries no text + no speaker, so no caption shows.\n const off = !this.host.captionsOn;\n const captionChar = off && beat.character === this.host.captionCharacter; // captionCharacter is always set (defaults SFX)\n const text = captionChar ? \"\" : this.captionLine(this.host.bundle.voiced ? raw : this.interpolate(raw));\n const silent = off && text.length === 0;\n return {\n type: \"line\",\n id: beat.id,\n text,\n character: silent ? undefined : beat.character,\n characterName: silent ? undefined : this.resolveCharacterName(beat.character),\n direction: silent ? undefined : beat.direction,\n gameData: beat.gameData,\n ...withTags,\n };\n }\n }\n }\n\n /**\n * Expand inline `{@ref}` slots (spec §16) against this flow's CURRENT property state. Public so an\n * IDs-only game can apply the same property replacement to a string it looked up in its own loc system:\n * the engine handed it the beat ID, the game fetched its translation, then calls `flow.interpolate(...)`.\n */\n interpolate(raw: string): string {\n return interpolate(raw, (ref) => this.getProperty(ref));\n }\n\n /**\n * Apply the project's caption rule to a string UNCONDITIONALLY (#214): remove every cue span between\n * the project's delimiters and collapse the whitespace. Public so an IDs-only game - which looks up\n * its own strings - can match the embedded runtime: `flow.stripCaptions(flow.interpolate(text))` when\n * its own captions setting is off. (Embedded play does this automatically for dialogue lines.)\n */\n stripCaptions(raw: string): string {\n return stripCaptions(raw, this.host.captionOpen, this.host.captionClose);\n }\n\n /** Caption-strip a dialogue line ONLY when captions are off; otherwise pass the text through. The\n * internal gate the engine applies to every `line` beat / line-kind prompt. */\n private captionLine(text: string): string {\n return this.host.captionsOn ? text : this.stripCaptions(text);\n }\n\n /**\n * An option's prompt (spec §5): the Option group's `prompt` beat, resolved + interpolated\n * (choice labels are on-screen text, so they interpolate, spec §16). For the degenerate\n * bare-snippet tolerance - or an Option group authored without a prompt - it falls back to the\n * option's first content line. NO look-ahead. Undefined only when even that is absent.\n */\n private promptFor(node: SelectableNode): ChoicePrompt | undefined {\n const beat = this.promptBeatOf(node);\n if (!beat) return undefined;\n const text = this.interpolate(this.resolveString(beat.id));\n // A line-kind prompt is dialogue, so captions apply to it; a text-kind prompt is left as-is.\n return beat.kind === \"line\"\n ? { kind: \"line\", text: this.captionLine(text), character: beat.character, characterName: this.resolveCharacterName(beat.character), direction: beat.direction }\n : { kind: \"text\", text };\n }\n\n /** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */\n private authoredPromptOf(node: SelectableNode): LineBeat | TextBeat | undefined {\n return node.type === \"group\" ? node.prompt : undefined;\n }\n\n /** A replayed prompt as a step: the beat's id, gameData, and tags, and the text and speaker fields the\n * choice showed. */\n private promptResult(beat: LineBeat | TextBeat, shown: ChoicePrompt): StepResult {\n const tags = this.host.tagIndex.get(beat.id);\n const withTags = tags && tags.length ? { tags } : {};\n if (shown.kind === \"text\") return { type: \"text\", id: beat.id, text: shown.text, gameData: beat.gameData, ...withTags };\n return {\n type: \"line\", id: beat.id, text: shown.text,\n character: shown.character, characterName: shown.characterName, direction: shown.direction,\n gameData: beat.gameData, ...withTags,\n };\n }\n\n /** The prompt BEAT of an option: the Option group's `prompt`, else (tolerance) its first content line. */\n private promptBeatOf(node: SelectableNode): LineBeat | TextBeat | undefined {\n if (node.type === \"group\" && node.prompt) return node.prompt;\n const snippet = node.type === \"snippet\" ? node : this.firstTextSnippetIn(node.children);\n return (snippet?.beats ?? []).find((b): b is LineBeat | TextBeat => b.kind === \"line\" || b.kind === \"text\");\n }\n\n /** The first snippet with a line/text beat within a child list, depth-first in authored order. */\n private firstTextSnippetIn(children: SelectableNode[]): CompiledSnippet | undefined {\n let found: CompiledSnippet | undefined;\n walkNodes<SelectableNode>(children, (n) => {\n if (!found && n.type === \"snippet\" && (n.beats ?? []).some((b) => b.kind === \"line\" || b.kind === \"text\")) {\n found = n;\n }\n });\n return found;\n }\n\n private resolveString(id: string): string {\n if (this.host.emitIds) return id; // IDs-only build: the game resolves text from this id itself\n const active = this.host.strings[id];\n if (active !== undefined) return active;\n // A key the active locale is missing falls back to the default-locale (source) text, but is flagged\n // LOUDLY: an untranslated string is a hard fail authors must notice, not silently paper over. Only a\n // key absent from the default locale too (never extracted) degrades to its bare id.\n const source = this.host.defaultStrings[id];\n return source !== undefined ? `<Untranslated: ${id}> ${source}` : id;\n }\n\n /** A character's player-facing name: the `cast:<name>` string in the active locale, else the default\n * locale, else the authoring `displayName`. Undefined when the character has no display name at all\n * (the host falls back to the `character` token itself). */\n private resolveCharacterName(character: string | undefined): string | undefined {\n if (character === undefined) return undefined;\n if (this.host.emitIds) return undefined; // IDs-only: omit the display name; the game maps the `character` token\n const key = castStringKey(character);\n return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.castDisplay.get(character);\n }\n\n /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */\n private splitRef(ref: string): { scope: string; name: string } {\n return splitHostRef(this.host, ref);\n }\n\n /** A fresh, unregistered bag for the NOT-shared `@patter` globals (the shared ones live on the host). */\n private newLocal(): PropertyBag {\n return new PropertyBag(this.host.patterLocalDecls, { pathPrefix: \"@patter.\" });\n }\n\n /** Register a fresh globals bag under this flow's key; it claims any values waiting there. */\n private mountLocal(): void {\n this.local = this.newLocal();\n const key = keys.flowGlobals(this.id);\n this.host.registry.mountOwned(key, this.local, { owner: OWNER });\n this.registered.add(key);\n }\n\n /** @internal Remove every bag this flow registered; with `keep`, their values wait in the registry for\n * the flow that replaces this one. Engine-driven (close, loadGame, hotSwap). */\n releaseBags(keep: boolean): void {\n for (const key of this.registered) this.host.registry.remove(key, { keep });\n this.registered.clear();\n this.sceneBags.clear();\n }\n\n /** The bag a `@scene` property of scene `s` lives in (stage or this flow's), made if missing. */\n private sceneBagFor(s: string, name: string): PropertyBag | undefined {\n if (!this.host.bundle.scenes[s]) return undefined;\n this.ensureSceneBags(s);\n return this.host.sceneSharedNames.get(s)?.has(name.toLowerCase()) ? this.host.stageBags.get(s) : this.sceneBags.get(s);\n }\n\n /** Make (and register) scene `s`'s stage bag and this flow's bag for it, if not made yet. A bag made\n * here claims whatever values the registry holds for its key: that is how a loaded save reaches it. */\n private ensureSceneBags(s: string): void {\n const shared = this.host.sceneSharedNames.get(s) ?? new Set<string>();\n // The bag's constructor seeds each declared default (the type's when none), normalises the name,\n // and clones the default so two bags from one declaration set never share a mutable flags array.\n const props = this.host.bundle.scenes[s]?.sceneProps ?? [];\n const journal = this.host.journal;\n if (!this.sceneBags.has(s)) {\n const bag = new PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())) as never, { pathPrefix: \"@scene.\" });\n const key = keys.flowScene(this.id, s);\n const claimed = mountClaims(this.host.registry, key, bag, journal !== null);\n this.registered.add(key);\n this.sceneBags.set(s, bag);\n // Made inside a checkpoint: a rollback unmakes it, so the scene seeds afresh on its next real entry,\n // unless the mount claimed values a load had parked, which go back to the registry rather than away.\n if (journal) journal.undo.push(() => {\n if (this.sceneBags.get(s) !== bag) return; // already released with the flow\n this.host.registry.remove(key, claimed ? { keep: true } : undefined);\n this.registered.delete(key);\n this.sceneBags.delete(s);\n });\n }\n if (!this.host.stageBags.has(s)) {\n const bag = new PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())) as never, { pathPrefix: \"@scene.\" });\n const claimed = mountClaims(this.host.registry, keys.stage(s), bag, journal !== null);\n this.host.stageBags.set(s, bag);\n if (journal) journal.undo.push(() => {\n if (this.host.stageBags.get(s) !== bag) return;\n this.host.registry.remove(keys.stage(s), claimed ? { keep: true } : undefined);\n this.host.stageBags.delete(s);\n });\n }\n }\n\n /**\n * Seed a scene's `@scene` props (spec §7). The not-shared props seed THIS flow's\n * bag the first time it enters (persist across re-entries thereafter); the shared\n * props seed the host's stage bag the first time ANY flow enters the scene (shared\n * and persistent thereafter - a later flow finds it present and leaves it).\n * `temporary` props are the exception: reseeded to their default on every entry.\n */\n private seedScene(scene: CompiledScene): void {\n const shared = this.host.sceneSharedNames.get(scene.id) ?? new Set<string>();\n this.ensureSceneBags(scene.id);\n\n // `temporary` props are reseeded to their default on EVERY entry (\"fresh each\n // playthrough\"), rather than persisting across re-entries like the rest.\n for (const decl of scene.sceneProps ?? []) {\n if (!decl.temporary) continue;\n const name = decl.name.toLowerCase();\n const bag = shared.has(name) ? this.host.stageBags.get(scene.id) : this.sceneBags.get(scene.id);\n if (!bag) continue;\n const prev = this.host.journal ? bag.get(name) : undefined;\n if (prev !== undefined) this.host.journal!.undo.push(() => bag.set(name, prev));\n bag.set(name, defaultFor(decl));\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers.\n// ---------------------------------------------------------------------------\n\n/** Collect the speakers under a run of nodes into `out`, in document order (insertion order IS the\n * result order). Groups contribute their option prompt's speaker - a prompt is a line | text beat, so\n * a line prompt is spoken by someone - and `walkNodes` carries the recursion into nested groups. */\nfunction collectCast(nodes: Array<CompiledGroup | CompiledSnippet>, out: Set<string>): void {\n walkNodes<SelectableNode>(nodes, (n) => {\n if (n.type === \"group\") {\n if (n.prompt?.kind === \"line\" && n.prompt.character) out.add(n.prompt.character);\n return;\n }\n for (const beat of n.beats ?? []) if (beat.kind === \"line\" && beat.character) out.add(beat.character);\n });\n}\n\n/** A `{ gameData }` outline fragment, present only when the node sets any (parity with a beat's). */\nfunction gameDataField(gameData: GameData | undefined): { gameData?: GameData } {\n return gameData && Object.keys(gameData).length ? { gameData } : {};\n}\n\n/** Serialise a `sequence` selector-cursor map to plain snapshots. */\n/** A prompt as a save carries it: the set fields only (a field set to \"\" included), never an `undefined`. */\nfunction savedPrompt(p: ChoicePrompt): SavedChoicePrompt {\n const out: SavedChoicePrompt = { kind: p.kind, text: p.text };\n if (p.character !== undefined) out.character = p.character;\n if (p.characterName !== undefined) out.characterName = p.characterName;\n if (p.direction !== undefined) out.direction = p.direction;\n return out;\n}\n\nfunction serialiseSelectors(map: Map<string, SelectorState>): Record<string, SelectorSnapshot> {\n const out: Record<string, SelectorSnapshot> = {};\n for (const [id, st] of map) {\n const v: SelectorSnapshot = {};\n if (st.seq !== undefined) v.seq = st.seq;\n if (st.bag) v.bag = [...st.bag];\n if (st.last !== undefined) v.last = st.last;\n out[id] = v;\n }\n return out;\n}\n\n/** Rebuild a `sequence` selector-cursor map from snapshots. */\nfunction deserialiseSelectors(rec: Record<string, SelectorSnapshot> | undefined): Map<string, SelectorState> {\n const map = new Map<string, SelectorState>();\n for (const [id, v] of Object.entries(rec ?? {})) {\n const st: SelectorState = {};\n if (v.seq !== undefined) st.seq = v.seq;\n if (v.bag) st.bag = [...v.bag];\n if (v.last !== undefined) st.last = v.last;\n map.set(id, st);\n }\n return map;\n}\n\n/** Adapt a Patter `PropertyDecl` to a registry `ScopeDeclaration` (same type vocabulary). */\n/**\n * Mount `bag` at `key` and say whether the mount CLAIMED values a load had parked there. Only asked when a\n * checkpoint is open (`ask`), since only a rollback needs to know: it must hand claimed values back to the\n * registry, or a scene first entered inside the checkpoint loses its saved values. A fresh bag holds only\n * its defaults, so a change across the mount is a claim. Parked values that equal the defaults are not told\n * apart, and need not be: dropping them changes nothing a story can read.\n */\nfunction mountClaims(registry: ScopeRegistry, key: string, bag: PropertyBag, ask: boolean): boolean {\n const before = ask ? JSON.stringify(bag.save()) : \"\";\n registry.mountOwned(key, bag, { owner: OWNER });\n return ask && JSON.stringify(bag.save()) !== before;\n}\n\nfunction toDecl(decl: PropertyDecl): ScopeDeclaration {\n return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };\n}\n\n\n/** A host-scope declaration (`@world.x`) → registry declaration. */\nfunction toForeignDecl(decl: HostScopeDecl): ScopeDeclaration {\n return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default, writable: decl.writable };\n}\n\n\n/** A self-backed host scope's declaration (the standalone `@world`): the scope's own `writable` default\n * folded in, since an owned bag reads writability per declaration. Names fold to lower case in the bag,\n * as the compiler emits every reference (`isNight` is read as `isnight`). */\nfunction selfBackedDecl(decl: HostScopeDecl, scopeWritable: boolean | undefined): ScopeDeclaration {\n const d = toForeignDecl(decl);\n const writable = decl.writable ?? scopeWritable;\n return writable === undefined ? d : { ...d, writable };\n}\n\n/** Split a ref into scope + name against the registry's current tokens (`@scene` is always Patter's).\n * Memoised per ref; the memo is dropped when the registry's set of scopes moves. */\n/**\n * The ONE rule for resolving a scene address, used by openFlow, goto, and every engine lookup: its gameId\n * (the host-facing address, spec §6) first, then its internal id. openFlow used to try the internal id\n * first and goto the gameId, so the two could land in different scenes when one scene's id was another's\n * gameId.\n */\nfunction resolveScene(host: FlowHost, ref: string): string | undefined {\n return host.sceneGameIdToId.get(ref) ?? (host.bundle.scenes[ref] ? ref : undefined);\n}\n\n/** The one rule for a block address, always WITHIN a scene: its gameId there first, then the internal id\n * of a block in that scene. A real block of another scene does not resolve. */\nfunction resolveBlock(host: FlowHost, sceneId: string, ref: string): string | undefined {\n return host.blockGameIdToId.get(sceneId)?.get(ref) ?? (host.blockIndex.get(ref)?.sceneId === sceneId ? ref : undefined);\n}\n\nfunction splitHostRef(host: FlowHost, ref: string): { scope: string; name: string } {\n if (host.refSplitRevision !== host.registry.revision) {\n host.refSplitCache.clear();\n host.refSplitRevision = host.registry.revision;\n }\n let hit = host.refSplitCache.get(ref);\n // Another engine's scope the content names (`@story.act`) is a scope even before that engine\n // registers it: a write then fails naming it, where it would otherwise land in @patter as `story.act`.\n if (!hit) {\n hit = splitRef(ref, (t) => t === \"scene\" || host.registry.has(t) || (host.bundle.externalScopes?.includes(t) ?? false));\n host.refSplitCache.set(ref, hit);\n }\n return hit;\n}\n\n/** A version 2 save's property values, as registry sections under this engine's keys. */\nfunction sectionsFromV2(save: SaveGameV2): EngineSave {\n const out: EngineSave = {};\n if (save.shared?.patter) out[\"patter\"] = save.shared.patter;\n for (const [s, v] of Object.entries(save.stageBags ?? {})) out[keys.stage(s)] = v;\n for (const [id, f] of Object.entries(save.flows ?? {})) {\n if (f.scopes?.patter) out[keys.flowGlobals(id)] = f.scopes.patter;\n for (const [s, v] of Object.entries(f.sceneBags ?? {})) out[keys.flowScene(id, s)] = v;\n }\n return out;\n}\n\n\nfunction truthy(v: ScalarValue): boolean {\n if (typeof v === \"boolean\") return v;\n if (typeof v === \"number\") return v !== 0;\n if (typeof v === \"string\") return v !== \"\";\n return v.length > 0; // string[]\n}\n","// ---------------------------------------------------------------------------\n// describeBundle - the bundle inspector's runtime half.\n//\n// A BUNDLE-level function, deliberately NOT an Engine method. It answers the\n// integrator's question from the imported asset alone, with no engine, no state\n// and nothing running:\n//\n// I dropped a .patterc into my project. What may my game code call, and is\n// this the bundle I think it is?\n//\n// That is a different question from the one the property examiner answers. The\n// examiner (Engine.listProperties + the per-engine state panels) watches and\n// edits a LIVE game. This is static: it is the API boundary made visible, read\n// off the asset in an editor inspector before anything runs.\n//\n// Three readers. The integrator reads the addresses and the host scopes: those\n// are what game code may call and what it must supply. The writer reads them\n// when nothing happens on `runFlow(\"x\", \"some_scene\")` - the address they typed\n// is not in the list. The designer reads the identity and counts to confirm\n// what actually shipped.\n//\n// Everything is in BUNDLE ORDER, never sorted: two runtimes must render the\n// same rows in the same sequence, and bundle order is the only order all four\n// ports can agree on without importing a collation rule.\n//\n// Cheap by construction. Scenes, blocks and declarations are walked once;\n// nothing here parses an expression, resolves a string table, or touches the\n// per-locale text. `beats` is the one count that requires descending to the\n// leaves, and it is a running total taken during the same single walk.\n// ---------------------------------------------------------------------------\n\nimport { effectiveGameId } from \"@patterkit/model\";\nimport type {\n Bundle, CompiledBlock, CompiledGroup, CompiledSnippet, GameDataField,\n GameDataNodeKind, PropertyDecl, PropertyType, ScalarValue,\n} from \"@patterkit/model\";\n\n/** Which bundle this is: identity, staleness fingerprints, and how it ships. */\nexport interface BundleIdentity {\n /** The bundle schema tag (\"patter/bundle@0\"). */\n schema: string;\n /** The project name. A save must agree with this. */\n project: string;\n /** The authored bundle version, if the project stamps one. */\n version?: string;\n /** Fingerprint over the WHOLE bundle: what binds saves and gates staleness. */\n hash?: string;\n /** The same fingerprint with the string tables left out. Equal structureHash\n * plus a different hash means a TEXT-ONLY edit, which is what makes a live\n * hot-swap safe. Showing both lets an integrator tell those apart at sight. */\n structureHash?: string;\n /** Project-wide VO mode. */\n voiced: boolean;\n defaultLocale: string;\n locales: string[];\n /** How strings ship: \"embedded\" (the runtime resolves text) or \"ids\" (the\n * runtime emits beat IDs and the game localises them itself). */\n localisation: \"embedded\" | \"ids\";\n /** True when the source locale was embedded purely for debug playback. Such a\n * build is NOT shippable, which is worth saying loudly in an inspector. */\n sourceDebug: boolean;\n}\n\n/** One scene, and the addresses game code may aim at inside it. */\nexport interface AddressSummary {\n /** The host-facing scene address: what `runFlow` / `goto` take. Derived from\n * the name when the author set no explicit gameId, exactly as the runtime\n * resolves it, so this list is the truth rather than an approximation. */\n gameId: string;\n /** The authored scene name, for recognising the row. */\n name: string;\n /** Block addresses within this scene. A block address is SCENE-SCOPED: the\n * pair is the address, which is why these are nested rather than flattened. */\n blocks: { gameId: string; name: string }[];\n}\n\n/** One author-defined gameData field: part of the host-facing data surface. */\nexport interface GameDataFieldSummary {\n name: string;\n type: string;\n /** Whether the schema carries a fallback. Sparse storage means a node that\n * sets nothing reads this, so a field with no default can arrive absent. */\n hasDefault: boolean;\n /** Allowed values for an enum field: the set host code switches on. */\n values?: string[];\n purpose?: string;\n}\n\n/** The gameData fields declared for one kind of node. */\nexport interface GameDataSummary {\n kind: GameDataNodeKind;\n fields: GameDataFieldSummary[];\n}\n\n/** One declared property. `hasDefault` rather than the value itself: an\n * inspector wants to know whether the host MUST supply something. */\nexport interface PropertySummary {\n name: string;\n type: PropertyType;\n hasDefault: boolean;\n default?: ScalarValue;\n /** Shared across all flows, or kept per-flow. Defaults differ by scope\n * (`@patter` shared, `@scene` per-flow), so it is resolved here. */\n shared: boolean;\n}\n\n/** A host scope (`@world` and friends): what the GAME must supply.\n *\n * The highest-value section of the whole description. Today an integrator\n * discovers a missing world property when a condition silently reads a\n * self-backed default and a branch never fires. */\nexport interface HostScopeSummary {\n /** The token after `@`, e.g. \"world\". */\n token: string;\n /** Scope-level read/write default for its declarations. */\n writable: boolean;\n /** An OPAQUE scope declares no names: any name is accepted, unchecked. The\n * host contract is then \"anything\", which is worth showing as such rather\n * than as an empty property list. */\n opaque: boolean;\n properties: PropertySummary[];\n}\n\n/** Story-owned declarations, for orientation rather than for calling. */\nexport interface OwnedProperties {\n /** Project-level (`@patter`). */\n patter: PropertySummary[];\n /** Per scene (`@scene`), keyed by the scene's host address. */\n scene: { gameId: string; properties: PropertySummary[] }[];\n}\n\n/** \"Is this the right build?\" at a glance. */\nexport interface BundleCounts {\n scenes: number;\n blocks: number;\n groups: number;\n snippets: number;\n /** Snippet beats. This is the SAME population `Engine.getBeatSequence` walks, deliberately, so a\n * tool that lists beats and an inspector that counts them never disagree. Choice prompts are not\n * in it - see `prompts`. */\n beats: number;\n /** Choice-option prompts: beats that live on a group rather than in a snippet.\n *\n * Counted separately rather than folded into `beats` because folding them in would make this\n * number disagree with `getBeatSequence`, and leaving them out entirely would understate a\n * choice-heavy story - a branching script could report a handful of beats and look like the wrong\n * build. Neither silence nor a redefinition; a second row. */\n prompts: number;\n /** Beats that fire a game event rather than producing player-facing words. */\n gameEvents: number;\n /** Cast members the bundle carries (player-facing only; the compiler strips\n * the authoring fields). */\n cast: number;\n}\n\nexport interface BundleDescription {\n identity: BundleIdentity;\n /** Everything game code may aim at, in bundle order. */\n addresses: AddressSummary[];\n /** What the host must supply. */\n hostScopes: HostScopeSummary[];\n /** What the story owns. */\n properties: OwnedProperties;\n /** The author-defined data surface, grouped by node kind. */\n gameData: GameDataSummary[];\n counts: BundleCounts;\n}\n\n/** Resolve a declaration's sharing default, which differs by the scope it sits\n * in: a project-level property is shared, a scene-local one is per-flow. */\nconst isShared = (d: PropertyDecl, scopeDefault: boolean): boolean =>\n d.shared ?? scopeDefault;\n\nfunction summariseProperty(d: PropertyDecl, scopeDefault: boolean): PropertySummary {\n return {\n name: d.name,\n type: d.type,\n hasDefault: d.default !== undefined,\n ...(d.default !== undefined ? { default: d.default } : {}),\n shared: isShared(d, scopeDefault),\n };\n}\n\nfunction summariseField(f: GameDataField): GameDataFieldSummary {\n return {\n name: f.name,\n type: f.type,\n hasDefault: f.default !== undefined,\n ...(f.values ? { values: [...f.values] } : {}),\n ...(f.purpose ? { purpose: f.purpose } : {}),\n };\n}\n\n/** One pass over a block's tree, accumulating counts. Iterative rather than\n * recursive: a deeply nested choice tree should not put an inspector's stack\n * at risk, and the traversal order does not matter for a count. */\nfunction countBlock(block: CompiledBlock, counts: BundleCounts): void {\n counts.blocks++;\n const stack: Array<CompiledGroup | CompiledSnippet> = [...block.children];\n while (stack.length) {\n const node = stack.pop()!;\n if (node.type === \"group\") {\n counts.groups++;\n if (node.prompt) counts.prompts++;\n stack.push(...node.children);\n continue;\n }\n counts.snippets++;\n for (const beat of node.beats ?? []) {\n counts.beats++;\n if (beat.kind === \"gameEvent\") counts.gameEvents++;\n }\n }\n}\n\n/**\n * Describe a compiled bundle: what it is, and what a game may call on it.\n *\n * Pure and allocation-light. Safe to call from an editor inspector on every\n * selection, though a details panel should still build its rows once rather\n * than per repaint.\n */\nexport function describeBundle(bundle: Bundle): BundleDescription {\n const counts: BundleCounts = {\n scenes: 0, blocks: 0, groups: 0, snippets: 0, beats: 0, prompts: 0, gameEvents: 0,\n cast: bundle.cast?.length ?? 0,\n };\n\n const addresses: AddressSummary[] = [];\n const sceneProps: OwnedProperties[\"scene\"] = [];\n for (const scene of Object.values(bundle.scenes)) {\n counts.scenes++;\n const gameId = effectiveGameId(scene);\n addresses.push({\n gameId,\n name: scene.name,\n blocks: scene.blocks.map((b) => ({ gameId: effectiveGameId(b), name: b.name })),\n });\n for (const block of scene.blocks) countBlock(block, counts);\n // Scene-local declarations default to PER-FLOW, unlike project-level ones.\n if (scene.sceneProps?.length) {\n sceneProps.push({ gameId, properties: scene.sceneProps.map((d) => summariseProperty(d, false)) });\n }\n }\n\n const hostScopes: HostScopeSummary[] = (bundle.scopeRegistry?.scopes ?? []).map((s) => ({\n token: s.token,\n writable: s.writable ?? true,\n opaque: s.declarations === undefined,\n // A host scope's values live outside the story, so \"shared\" is not a choice\n // its declarations make; they are world-wide by nature.\n properties: (s.declarations ?? []).map((d) => summariseProperty(d as PropertyDecl, true)),\n }));\n\n const gameData: GameDataSummary[] = Object.entries(bundle.gameDataFields ?? {})\n .filter(([, fields]) => (fields?.length ?? 0) > 0)\n .map(([kind, fields]) => ({\n kind: kind as GameDataNodeKind,\n fields: (fields ?? []).map(summariseField),\n }));\n\n return {\n identity: {\n schema: bundle.schema,\n project: bundle.content.project,\n ...(bundle.content.version !== undefined ? { version: bundle.content.version } : {}),\n ...(bundle.content.hash !== undefined ? { hash: bundle.content.hash } : {}),\n ...(bundle.content.structureHash !== undefined ? { structureHash: bundle.content.structureHash } : {}),\n voiced: bundle.voiced,\n defaultLocale: bundle.locales.default,\n locales: [...bundle.locales.included],\n // Absent means \"embedded\": the back-compat default a bundle written before\n // the field existed relies on.\n localisation: bundle.localisation?.mode ?? \"embedded\",\n sourceDebug: bundle.localisation?.sourceDebug ?? false,\n },\n addresses,\n hostScopes,\n properties: {\n patter: (bundle.properties ?? []).map((d) => summariseProperty(d, true)),\n scene: sceneProps,\n },\n gameData,\n counts,\n };\n}\n","// gameData read helpers (spec: author-defined custom fields per node type). The published bundle\n// carries the field SCHEMA per node type (`bundle.gameDataFields`, each field with its default) plus\n// each node's SPARSE overrides (`node.gameData`). Storage is sparse + merge-at-read: a node holds only\n// the values it overrides, and a reader falls back to the field's default. These pure helpers do that\n// resolution so a host doesn't re-implement it.\n\nimport type { Bundle, GameData, GameDataField, GameDataNodeKind } from \"@patterkit/model\";\n\n/** The author-defined gameData fields declared for a node TYPE in a bundle (empty when none). */\nexport function gameDataFields(bundle: Bundle, kind: GameDataNodeKind): GameDataField[] {\n return bundle.gameDataFields?.[kind] ?? [];\n}\n\n/** One node's effective value for a field: its sparse OVERRIDE if present, else the field's declared\n * default (undefined if neither is set). `fields` is the schema for the node's type. */\nexport function gameDataValue(fields: GameDataField[], node: GameData | undefined, name: string): unknown {\n if (node && Object.prototype.hasOwnProperty.call(node, name)) return node[name];\n return fields.find((f) => f.name === name)?.default;\n}\n\n/** A node's FULL effective gameData: every declared field resolved (override or default), plus any\n * override keys with no matching field (orphans, kept verbatim). Fields left with no value are omitted. */\nexport function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData {\n const out: GameData = {};\n for (const f of fields) {\n const v = gameDataValue(fields, node, f.name);\n if (v !== undefined) out[f.name] = v;\n }\n for (const [k, v] of Object.entries(node ?? {})) if (!(k in out)) out[k] = v;\n return out;\n}\n","// ---------------------------------------------------------------------------\n// Save / load: wrap the Engine's whole-game snapshot in a tagged, versioned\n// envelope so a host can drop it into localStorage / a file and restore it\n// safely (a foreign blob throws instead of corrupting a run).\n// ---------------------------------------------------------------------------\n\nimport type { Engine } from \"@patterkit/runtime\";\nimport { SAVE_SCHEMA } from \"@patterkit/model\";\nimport type { SaveEnvelope } from \"@patterkit/model\";\n\n// The envelope (`schema` + `save`) and the save shape inside it are the FAMILY's contract, defined in\n// @patterkit/model and written identically by every Patterplay runtime. Re-exported so existing\n// imports keep working.\nexport { SAVE_SCHEMA };\nexport type { SaveEnvelope };\n\n/** Capture the whole game as a tagged envelope (wraps `engine.saveGame()`). */\nexport function saveState(engine: Engine): SaveEnvelope {\n return { schema: SAVE_SCHEMA, save: engine.saveGame() };\n}\n\n/** Restore a {@link saveState} envelope into an engine (fresh or live). Throws on a foreign/blank blob. */\nexport function loadState(engine: Engine, env: SaveEnvelope): void {\n if (!env || env.schema !== SAVE_SCHEMA || !env.save) {\n throw new Error(`loadState: not a ${SAVE_SCHEMA} envelope`);\n }\n engine.loadGame(env.save);\n}\n\n/** Serialise the whole game to a JSON string (envelope + save-game) - drop into localStorage or a file. */\nexport function serializeState(engine: Engine): string {\n return JSON.stringify(saveState(engine));\n}\n\n/** Parse + restore a {@link serializeState} string. Throws on malformed JSON or a foreign envelope. */\nexport function deserializeState(engine: Engine, json: string): void {\n loadState(engine, JSON.parse(json) as SaveEnvelope);\n}\n","// ---------------------------------------------------------------------------\n// Runtime property setters: read / write `@patter` globals, `@scene` props, or\n// a wired foreign scope at runtime - e.g. the game pushing inventory into the\n// dialogue, or reading a flag the dialogue set. Thin pass-throughs, plus a batch\n// setter for convenience.\n//\n// They take an Engine or a Flow. Game-wide properties (`@hp`, `@world.x`) go\n// through either; `@scene` props belong to a flow, since each flow has its own,\n// so they need the Flow: the Engine refuses an `@scene` ref.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@patterkit/runtime\";\n\n/** A property value the engine accepts (a Patter scalar: number / boolean / string / string[] flags). */\nexport type PropertyValue = Parameters<Engine[\"setProperty\"]>[1];\n\n/** Where properties are read and written: the Engine (game-wide refs), or a Flow (those plus `@scene`). */\nexport type PropertyHolder = Engine | Flow;\n\n/** Read a runtime property (`@hp`, a foreign `@world.x`, or with a Flow `@scene.locked`). Undefined when unset. */\nexport function getProperty(holder: PropertyHolder, ref: string): PropertyValue | undefined {\n return holder.getProperty(ref);\n}\n\n/** Set one runtime property. Mirrors `setProperty` on the Engine or Flow, exported for discoverability + symmetry. */\nexport function setProperty(holder: PropertyHolder, ref: string, value: PropertyValue): void {\n holder.setProperty(ref, value);\n}\n\n/** Set many at once, e.g. `setProperties(engine, { \"@hp\": 10 })` or `setProperties(flow, { \"@scene.locked\": false })`. */\nexport function setProperties(holder: PropertyHolder, values: Record<string, PropertyValue>): void {\n for (const [ref, value] of Object.entries(values)) holder.setProperty(ref, value);\n}\n","// ---------------------------------------------------------------------------\n// The Patterplay state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry, plus `logStep`, which is this product's own.\n//\n// It used to diff whole `engine.saveGame()` snapshots. That could only ever say\n// what changed BETWEEN captures, which means it could not see a value that\n// changed and changed back, could not name the reason a host attached to a\n// write, and reported a write only once something asked. The kernel logs a\n// property write as it LANDS, on the PropertyBag audit hook, and keeps the diff\n// for state that has no hook - here, the visit counts.\n//\n// The Storylet Engine's logger was already this shape; its core is what moved\n// into the kernel. Same core, two adapters.\n//\n// Paths, unchanged:\n// @patter.x the shared globals\n// @scene:<sceneId>.x the shared scene props\n// visit:<nodeId> shared visit counts\n// <flowId>/... the same three, per flow (its not-shared halves)\n// ---------------------------------------------------------------------------\n\nimport type { Engine, StepResult } from \"@patterkit/runtime\";\nimport { createStateLogger as createKernelStateLogger, diffState } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, ScalarValue, StateChange, StateSnapshot } from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @patterkit/play-helpers should not have to care that they moved.\nexport { diffState };\nexport type { StateChange, StateSnapshot };\n\n/** A flattened runtime-state value: a Patter scalar. The kernel calls this `ScalarValue`;\n * the alias stays because it is exported API and reads in Patter's own vocabulary. */\nexport type StateValue = ScalarValue;\n\n/** Flatten the engine's whole-game state into a path -> value map (shared scopes + every live\n * flow). The logger does not diff this - it mounts the bags directly - but it stays as the public\n * \"what is the state right now\" call, and it reads the same mounts, so the two agree on the path\n * space. A bag loaded into the registry but not yet claimed (a scene no flow has re-entered since\n * a load) appears once it is. */\nexport function snapshotState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const m of allMounts(engine)) {\n const prefix = m.pathPrefix ?? m.bag.pathPrefix;\n for (const [name, v] of Object.entries(m.bag.save())) out[`${prefix}${name}`] = v;\n }\n Object.assign(out, visitState(engine));\n return out;\n}\n\n/** Every bag the engine and its flows hold, with the path each answers to in a log. */\nfunction allMounts(engine: Engine): LogMount[] {\n return [...engine.listBags(), ...engine.flows().flatMap((f) => f.listBags())];\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to `console.log`. */\n sink?: (line: string) => void;\n /** Prefix tag for every line, e.g. the flow / save-slot name. */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state (no logging). */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the property writes already logged as they landed,\n * plus the visit counts, diffed and re-baselined. */\n capture(): StateChange[];\n /** Trace one played step (line / text / game-event / choice / end), including any `gameData`. */\n logStep(step: StepResult): void;\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\nfunction describeStep(step: StepResult): string {\n switch (step.type) {\n case \"line\": return `line ${step.character ?? \"?\"}: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"text\": return `text: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"gameEvent\": return `game event ${step.id}${gd(step.gameData)}`;\n case \"choice\": return `choice (${step.options.length} option${step.options.length === 1 ? \"\" : \"s\"})`;\n case \"end\": return \"end\";\n }\n}\nconst gd = (data: unknown): string => (data ? ` gameData=${JSON.stringify(data)}` : \"\");\n\n/** The visit counts, which live in no bag and so have no audit hook: the kernel diffs\n * these on capture, exactly as the whole logger used to work. */\nfunction visitState(engine: Engine): StateSnapshot {\n const save = engine.saveGame();\n const out: StateSnapshot = {};\n for (const [id, n] of Object.entries(save.sharedVisits)) out[`visit:${id}`] = n;\n for (const [fid, snap] of Object.entries(save.flows)) {\n for (const [id, n] of Object.entries(snap.visits)) out[`${fid}/visit:${id}`] = n;\n }\n return out;\n}\n\n/** Create a state logger over an engine. Property writes log as they land; call `capture()`\n * after each `advance`/`choose` to pick up the visit counts and re-baseline. */\nexport function createStateLogger(engine: Engine, opts: StateLoggerOptions = {}): StateLogger {\n const tag = opts.label ? `[${opts.label}] ` : \"\";\n const kernel = createKernelStateLogger({\n // Re-read on every capture: openFlow and loadGame both replace bags, and the kernel\n // re-mounts whatever it is handed.\n mounts: (): LogMount[] => allMounts(engine),\n extra: () => visitState(engine),\n }, { sink: opts.sink, label: tag });\n\n return {\n snapshot: () => snapshotState(engine),\n capture: () => kernel.capture(),\n dispose: () => kernel.dispose(),\n logStep(step) { (opts.sink ?? ((l: string) => console.log(l)))(`${tag}${describeStep(step)}`); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live debug link (#181) - the game-side client.\n//\n// Streams a running game's story position to Patterpad over a localhost\n// WebSocket, so the editor can follow the live cursor like a debugger. It is\n// OBSERVE-ONLY: the game stays in control; the editor is a passive mirror.\n//\n// Wire protocol `patterplay/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:1, build, project?, flows? } - on connect\n// frame : { t:\"frame\", flow, sceneId, beatId, type, choiceId? } - per transition\n// flow : { t:\"flowOpen\" | \"flowClose\", flow } - lifecycle\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: live bundle refresh.\n// `data` is the full .patterc JSON. Pass an `onBundle` handler (or just feed it to\n// `applyLiveBundle`, which picks strings-only vs full hot swap itself), then call\n// `link.setBuild(newHash)` so the editor's match/stale pill updates.\n//\n// The link is loopback-only on the editor side (127.0.0.1), so there's no pairing\n// token - only processes on the same machine can reach it. The client never throws\n// into the game loop - a missing/closed editor just means frames go nowhere. Wire\n// it up after each advance()/choose():\n//\n// const link = createDebugLink({ build: bundle.content.hash });\n// link.flowOpened(\"main\");\n// // ...after each step:\n// link.observe(\"main\", flow.currentScene, step.id ?? null, step.type);\n// ---------------------------------------------------------------------------\n\n/** A minimal structural type for a WebSocket implementation (browsers + Node 21+ have a global one). */\nexport interface DebugSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"close\" | \"error\", listener: () => void): void;\n /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype DebugSocketCtor = new (url: string) => DebugSocketLike;\n\nexport interface DebugLinkOptions {\n /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */\n build: string;\n /** Optional project name, shown in the editor's debug panel. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4471`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (Node < 21, or tests with `ws`). */\n WebSocket?: DebugSocketCtor;\n /** Live bundle refresh: the editor pushed a freshly compiled bundle. `data` is the .patterc JSON;\n * hand it (with your current engine + bundle) to `applyLiveBundle`, re-bind your flow handles if it\n * hot-swapped, then call `link.setBuild(build)`. Never called with malformed payloads. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\nexport interface DebugLink {\n /** Tell the editor a flow opened (so it can list it in the follow selector). */\n flowOpened(flowId: string): void;\n /** Report the current position of a flow - call after each advance()/choose(). */\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void;\n /** Tell the editor a flow closed. */\n flowClosed(flowId: string): void;\n /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's\n * match/stale pill updates and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link. */\n close(): void;\n}\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/**\n * Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects\n * or if it was never listening - safe to leave wired into a shipping build behind a flag.\n */\nexport function createDebugLink(opts: DebugLinkOptions): DebugLink {\n const url = opts.url ?? \"ws://127.0.0.1:4471\";\n const Ctor: DebugSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: DebugSocketCtor }).WebSocket;\n const flows = new Set<string>();\n let queue: string[] = [];\n let sock: DebugSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live bundle refresh lands\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n // Queue only while there is a socket that is connecting or open. Once the editor is gone (or never\n // answered), the socket is dropped and a message has nowhere to go, so it is dropped too: queueing it\n // anyway grew the queue by one string per step for the rest of a game that shipped with the link wired\n // in, which is exactly the build the docs say this is safe in.\n const post = (msg: object): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n queue.push(JSON.stringify(msg));\n flush();\n };\n\n if (!Ctor) {\n // No WebSocket available (old Node, no impl passed) - degrade to a silent no-op link.\n return { flowOpened() {}, observe() {}, flowClosed() {}, setBuild() {}, close() { closed = true; } };\n }\n\n const sendHello = (): void => {\n const hello = JSON.stringify({ t: \"hello\", v: 1, build, project: opts.project, flows: [...flows] });\n try { sock?.send(hello); } catch { /* race: closed immediately */ }\n };\n\n try {\n sock = new Ctor(url);\n sock.addEventListener(\"open\", () => {\n // Handshake first, so the editor can verify the build + seed the flow list before frames arrive.\n sendHello();\n flush();\n });\n // Live bundle refresh: the editor pushed a new bundle. Validate the shape here so the host's\n // handler never sees a malformed payload; everything else the editor might send is ignored.\n sock.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n sock.addEventListener(\"error\", () => { /* editor not listening - stay a no-op */ });\n sock.addEventListener(\"close\", () => { sock = null; queue = []; });\n } catch { sock = null; } // malformed URL etc. - never throw into the game\n\n return {\n flowOpened(flowId: string): void { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); },\n flowClosed(flowId: string): void { flows.delete(flowId); post({ t: \"flowClose\", flow: flowId }); },\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void {\n // A flow reports itself. `flowOpened` is the host's job and nothing could check it, so a game\n // that opened a flow and forgot to announce it left the editor's list short - and the omission\n // survived a reconnect, because the hello sends this set. Observing a flow we have not seen\n // announces it first, which makes the host's call a courtesy (it lists the flow before its\n // first step) rather than something to remember.\n if (!flows.has(flowId)) { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); }\n post({ t: \"frame\", flow: flowId, sceneId, beatId, type, choiceId });\n },\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n if (sock && sock.readyState === OPEN) sendHello(); // re-handshake: the editor re-reads the build\n },\n close(): void { closed = true; queue = []; try { sock?.close(); } catch { /* already gone */ } sock = null; },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live bundle refresh (design/proposals/live-bundle-refresh.md) - the game-side\n// applier. The editor pushes a freshly compiled bundle over the debug link\n// (createDebugLink's `onBundle`); this helper picks the right tier and applies\n// it:\n// - same structureHash -> tier 1: engine.replaceStrings() - nothing restarts.\n// - changed structure -> tier 2: engine.hotSwap() - the run carried over,\n// content drift resolved by the save system (§9.8).\n// Returns the engine to keep using (the SAME one for \"text\", a REPLACEMENT for\n// \"structure\" - re-bind your Flow handles via engine.getFlow(id)) plus the\n// parsed bundle for the next comparison. Wire-up:\n//\n// let { engine, bundle } = boot();\n// const link = createDebugLink({\n// build: bundle.content.hash!,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, bundle, data);\n// engine = r.engine; bundle = r.bundle;\n// if (r.kind === \"structure\") flow = engine.getFlow(\"main\")!;\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport { Engine } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/runtime\";\n\nexport interface LiveBundleResult {\n /** The engine to keep using: the same instance for a \"text\" swap, a replacement for \"structure\". */\n engine: Engine;\n /** The parsed pushed bundle - hold on to it for the next apply's comparison. */\n bundle: Bundle;\n /** Which tier applied: \"text\" (strings-only, nothing restarted) or \"structure\" (full hot swap). */\n kind: \"text\" | \"structure\";\n}\n\n/**\n * Apply a bundle the editor pushed over the debug link. `current` is the bundle `engine` is running\n * (needed for the structure-hash comparison); `data` is the pushed .patterc JSON. Throws only on\n * unparseable JSON - a structural swap's edge cases are absorbed by the §9.8 drift policy inside\n * `hotSwap`, which never throws for ordinary edits.\n */\nexport function applyLiveBundle(engine: Engine, current: Bundle, data: string): LiveBundleResult {\n const next = JSON.parse(data) as Bundle;\n // Same structure = a text-only edit: swap the string tables in place and keep everything running.\n // Missing structureHash on either side (an older compiler) falls through to the full swap - safe,\n // just less gentle than it could be.\n const sameStructure = current.content.structureHash !== undefined\n && current.content.structureHash === next.content.structureHash;\n if (sameStructure) {\n engine.replaceStrings(next);\n return { engine, bundle: next, kind: \"text\" };\n }\n return { engine: engine.hotSwap(next), bundle: next, kind: \"structure\" };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// A live @patter property inspector for the browser: a small DOM panel that\n// watches AND edits a running Engine's shared @patter properties. Type-aware\n// editors (bool / number / text / enum / flags), a reset-to-default per row, and\n// a poll that refreshes values WITHOUT clobbering the field you're editing.\n//\n// The JS parity of the Unity PatterStateWindow / Godot PatterStatePanel. Those\n// use a PatterDebug registry because the editor window is separate from the game;\n// here the game runs in-process, so you just pass the engine directly:\n//\n// const inspector = createPropertyInspector(engine, { container: document.body });\n// // ...later: inspector.destroy();\n// ---------------------------------------------------------------------------\n\nimport type { Engine, PropertyRow } from \"@patterkit/runtime\";\nimport { serializeState, deserializeState } from \"./save.js\";\nimport type { PropertyValue } from \"./properties.js\";\n\nexport interface PropertyInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Runtime state\". */\n title?: string;\n /** Live-refresh interval in ms; 0 disables polling (call `refresh()` yourself). Default 250. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** Re-read every property and update the editors, skipping the field you're editing. */\n refresh(): void;\n /** Stop polling and remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-inspector-style\";\nconst CSS = `\n.pp-insp{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:22rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-insp h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-insp-empty{color:#8a9691;font-style:italic}\n.pp-insp-io { display: flex; gap: 6px; margin: 0 0 8px; }\n .pp-insp-io button { font: inherit; padding: 2px 8px; cursor: pointer; }\n .pp-insp-row{display:flex;align-items:center;gap:.4rem;margin:.18rem 0}\n.pp-insp-ref{flex:0 0 8rem;font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.pp-insp-ctl{flex:1;min-width:0;display:flex}\n.pp-insp-ctl input[type=text],.pp-insp-ctl input[type=number],.pp-insp-ctl select{width:100%;box-sizing:border-box;font:inherit;padding:.15rem .3rem;border:1px solid #cfc7b8;border-radius:6px;background:#fff;color:inherit}\n.pp-insp-reset{flex:0 0 auto;border:1px solid #cfc7b8;background:#fff;border-radius:6px;cursor:pointer;color:#5c6b62;width:1.6rem;height:1.6rem;line-height:1;padding:0}\n.pp-insp-reset:disabled{opacity:.35;cursor:default}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\n/** Has the DISPLAYED value changed? Order-sensitive on purpose, and deliberately\n * NOT the language's value equality, which compares flags as a set: this asks\n * whether the row needs redrawing, and the row renders the list in its stored\n * order, so a reordering is a change here even though it is not a change to\n * what the value means. */\nfunction sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => x === b[i]);\n return a === b;\n}\n\nexport function createPropertyInspector(engine: Engine, opts: PropertyInspectorOptions = {}): PropertyInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n\n const el = doc.createElement(\"div\");\n el.className = \"pp-insp\";\n const heading = doc.createElement(\"h4\");\n heading.textContent = opts.title ?? \"Runtime state\";\n const list = doc.createElement(\"div\");\n\n // Save/Load the whole run as a patter/save@0 envelope file - the parity of Unity's\n // PatterStateWindow buttons, Godot's state-panel dialogs, and Unreal's Slate panel.\n const io = doc.createElement(\"div\");\n io.className = \"pp-insp-io\";\n const saveBtn = doc.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.textContent = \"Save state\\u2026\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const a = doc.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"save.patterstate\";\n a.click();\n URL.revokeObjectURL(a.href);\n });\n const loadBtn = doc.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.textContent = \"Load state\\u2026\";\n loadBtn.addEventListener(\"click\", () => {\n const input = doc.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".patterstate,application/json\";\n input.addEventListener(\"change\", () => {\n const file = input.files?.[0];\n if (!file) return;\n void file.text().then((text) => {\n try { deserializeState(engine, text); refresh(); }\n catch (err) { console.error(\"Load state failed:\", err instanceof Error ? err.message : err); }\n });\n });\n input.click();\n });\n io.append(saveBtn, loadBtn);\n el.append(heading, io, list);\n\n const rowRefreshers: Array<() => void> = [];\n\n const buildRow = (row: PropertyRow): void => {\n const r = doc.createElement(\"div\");\n r.className = \"pp-insp-row\";\n const label = doc.createElement(\"span\");\n label.className = \"pp-insp-ref\";\n label.textContent = row.path;\n label.title = row.path;\n const ctl = doc.createElement(\"div\");\n ctl.className = \"pp-insp-ctl\";\n const reset = doc.createElement(\"button\");\n reset.className = \"pp-insp-reset\";\n reset.type = \"button\";\n reset.textContent = \"↺\"; // ↺\n reset.title = \"Reset to default\";\n r.append(label, ctl, reset);\n list.appendChild(r);\n\n // `read` pushes the live engine value into the editor, but never while that editor has focus\n // (so it can't wipe what you're typing). `syncReset` disables the ↺ when the value is the default.\n let read: () => void;\n const commit = (v: PropertyValue): void => { engine.setProperty(row.path, v); syncReset(); };\n const focused = (node: Element): boolean => doc.activeElement === node;\n function syncReset(): void { reset.disabled = sameValue(engine.getProperty(row.path), row.default); }\n reset.addEventListener(\"click\", () => { engine.setProperty(row.path, row.default); read(); syncReset(); });\n\n if (row.type === \"boolean\") {\n const cb = doc.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.addEventListener(\"change\", () => commit(cb.checked));\n ctl.appendChild(cb);\n read = () => { if (!focused(cb)) cb.checked = engine.getProperty(row.path) === true; };\n } else if (row.type === \"number\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"number\";\n inp.addEventListener(\"change\", () => commit(Number(inp.value)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"enum\" || row.type === \"quality\") {\n // A quality edits as a dropdown of its STAGES - the ladder is closed, like an enum's values.\n const sel = doc.createElement(\"select\");\n for (const v of (row.type === \"quality\" ? row.stages : row.values) ?? []) {\n const o = doc.createElement(\"option\");\n o.value = v; o.textContent = v;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", () => commit(sel.value));\n ctl.appendChild(sel);\n read = () => { if (!focused(sel)) sel.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"flags\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.placeholder = \"comma, separated, flags\";\n inp.addEventListener(\"change\", () => commit(inp.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) { const v = engine.getProperty(row.path); inp.value = Array.isArray(v) ? v.join(\", \") : \"\"; } };\n } else {\n // string (and any unrecognised type) -> a text field\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.addEventListener(\"change\", () => commit(inp.value));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n }\n\n read();\n syncReset();\n rowRefreshers.push(() => { read(); syncReset(); });\n };\n\n // Properties are fixed for a bundle, so build the rows once; polling just refreshes their values.\n const props = engine.listProperties();\n if (props.length === 0) {\n const empty = doc.createElement(\"div\");\n empty.className = \"pp-insp-empty\";\n empty.textContent = \"No @patter properties.\";\n list.appendChild(empty);\n } else {\n for (const row of props) buildRow(row);\n }\n\n const refresh = (): void => { for (const fn of rowRefreshers) fn(); };\n\n opts.container?.appendChild(el);\n const pollMs = opts.pollMs ?? 250;\n let timer: ReturnType<typeof setInterval> | undefined;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n return {\n el,\n refresh,\n destroy() { if (timer !== undefined) clearInterval(timer); el.remove(); },\n };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// The bundle inspector's VIEW, for the web: a read-only panel over\n// `describeBundle`, in the same visual grammar as the live property inspector.\n//\n// The other three runtimes hang their view off an imported asset - a Unity\n// CustomEditor, an Unreal details customisation, a Godot EditorInspectorPlugin.\n// JS has no asset pipeline, so there is no inspector to extend and the summary\n// function IS the surface. This exists because \"the function is the surface\"\n// leaves the JS side without the thing the other three give you for free: a\n// place to LOOK at a bundle. A drop-in page can now do\n//\n// const panel = createBundleInspector(bundle, { container: document.body });\n// // ...later: panel.destroy();\n//\n// Read-only on purpose. The live inspector edits because a running game has\n// state worth changing; this describes an artefact, and there is nothing here a\n// reader could sensibly write to.\n// ---------------------------------------------------------------------------\n\nimport { describeBundle } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/model\";\nimport type { BundleDescription, PropertySummary } from \"@patterkit/runtime\";\n\nexport interface BundleInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Bundle\". */\n title?: string;\n /** Sections open on mount. Default: identity and addresses, the two an\n * integrator reaches for first; the rest are a click away. */\n open?: BundleSection[];\n}\n\nexport type BundleSection = \"identity\" | \"addresses\" | \"hostScopes\" | \"properties\" | \"gameData\" | \"counts\";\n\nexport interface BundleInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** The description the panel is showing, for a caller that wants the data too. */\n readonly description: BundleDescription;\n /** Remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-bundle-style\";\n// Deliberately the property inspector's palette and metrics: the two panels are\n// often on the same page, and a second look-and-feel would read as a second tool.\nconst CSS = `\n.pp-bundle{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:26rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-bundle h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-bundle details{border-top:1px solid #e3dccf;padding:.3rem 0}\n.pp-bundle details:first-of-type{border-top:0}\n.pp-bundle summary{cursor:pointer;font-weight:600;color:#214f4b;list-style:none}\n.pp-bundle summary::-webkit-details-marker{display:none}\n.pp-bundle summary::before{content:\"\\\\25B8\";display:inline-block;width:1rem;color:#8a9691}\n.pp-bundle details[open]>summary::before{content:\"\\\\25BE\"}\n.pp-bundle-count{color:#8a9691;font-weight:400}\n.pp-bundle-row{display:flex;gap:.4rem;margin:.16rem 0 .16rem 1rem}\n.pp-bundle-key{flex:0 0 8rem;color:#5c6b62}\n.pp-bundle-val,.pp-bundle-addr{font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;word-break:break-all}\n.pp-bundle-sub{margin-left:2rem}\n.pp-bundle-empty{color:#8a9691;font-style:italic;margin-left:1rem}\n.pp-bundle-warn{color:#8a3a2f;font-weight:600}\n.pp-bundle-tag{font-size:.7rem;color:#5c6b62;border:1px solid #cfc7b8;border-radius:5px;padding:0 .25rem;margin-left:.3rem}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\nconst el = (doc: Document, tag: string, cls?: string, text?: string): HTMLElement => {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text !== undefined) e.textContent = text;\n return e;\n};\n\n/** One \"key: value\" line. */\nfunction row(doc: Document, key: string, value: string, valueClass = \"pp-bundle-val\"): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key\", key), el(doc, \"span\", valueClass, value));\n return r;\n}\n\n/** A property line: the name, its type, and whether the HOST must supply a value.\n * \"no default\" is the part an integrator is actually scanning for. */\nfunction propertyRow(doc: Document, p: PropertySummary): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", p.name));\n const meta = el(doc, \"span\", \"pp-bundle-val\", p.type);\n if (!p.hasDefault) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"no default\"));\n r.append(meta);\n return r;\n}\n\nfunction section(doc: Document, name: BundleSection, label: string, count: number | null, open: boolean): HTMLDetailsElement {\n const d = doc.createElement(\"details\");\n d.dataset[\"section\"] = name;\n d.open = open;\n const s = doc.createElement(\"summary\");\n s.textContent = label;\n if (count !== null) s.append(el(doc, \"span\", \"pp-bundle-count\", ` ${count}`));\n d.append(s);\n return d;\n}\n\n/**\n * Build a read-only panel describing a compiled bundle: what it is, what game\n * code may call on it, and what the host must supply.\n */\nexport function createBundleInspector(bundle: Bundle, opts: BundleInspectorOptions = {}): BundleInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n const d = describeBundle(bundle);\n const open = new Set<BundleSection>(opts.open ?? [\"identity\", \"addresses\"]);\n\n const root = el(doc, \"div\", \"pp-bundle\");\n root.append(el(doc, \"h4\", undefined, opts.title ?? \"Bundle\"));\n\n // --- identity ------------------------------------------------------------\n const id = section(doc, \"identity\", d.identity.project || \"(unnamed project)\", null, open.has(\"identity\"));\n if (d.identity.version) id.append(row(doc, \"version\", d.identity.version));\n id.append(row(doc, \"schema\", d.identity.schema));\n id.append(row(doc, \"locales\", `${d.identity.defaultLocale}${d.identity.locales.length > 1 ? ` (+${d.identity.locales.length - 1})` : \"\"}`));\n id.append(row(doc, \"strings\", d.identity.localisation));\n if (d.identity.voiced) id.append(row(doc, \"voiced\", \"yes\"));\n if (d.identity.hash) id.append(row(doc, \"hash\", d.identity.hash));\n // Equal structureHash + a different hash = a text-only edit, which is what makes\n // a live hot-swap safe. Both are shown so an integrator can tell those apart.\n if (d.identity.structureHash) id.append(row(doc, \"structure\", d.identity.structureHash));\n if (d.identity.sourceDebug) {\n id.append(row(doc, \"build\", \"SOURCE DEBUG - not shippable\", \"pp-bundle-val pp-bundle-warn\"));\n }\n root.append(id);\n\n // --- addresses -----------------------------------------------------------\n const addr = section(doc, \"addresses\", \"Addresses\", d.addresses.length, open.has(\"addresses\"));\n if (!d.addresses.length) addr.append(el(doc, \"div\", \"pp-bundle-empty\", \"no scenes\"));\n for (const a of d.addresses) {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", a.gameId), el(doc, \"span\", \"pp-bundle-val\", a.name));\n addr.append(r);\n // Nested, because a block address is scene-scoped: the pair is the address.\n for (const b of a.blocks) {\n const br = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n br.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", b.gameId), el(doc, \"span\", \"pp-bundle-val\", b.name));\n addr.append(br);\n }\n }\n root.append(addr);\n\n // --- host scopes ---------------------------------------------------------\n // Counted in PROPERTIES rather than scopes: the heading says \"properties\", and a\n // reader comparing it with the rows below should not have to work out which it meant.\n // An opaque scope contributes none, and says so on its own row.\n const hostCount = d.hostScopes.reduce((n, s) => n + s.properties.length, 0);\n const host = section(doc, \"hostScopes\", \"Host properties\", hostCount, open.has(\"hostScopes\"));\n if (!d.hostScopes.length) host.append(el(doc, \"div\", \"pp-bundle-empty\", \"the game supplies nothing\"));\n for (const s of d.hostScopes) {\n const head = el(doc, \"div\", \"pp-bundle-row\");\n head.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", `@${s.token}`));\n const meta = el(doc, \"span\", \"pp-bundle-val\", s.opaque ? \"any name, unchecked\" : `${s.properties.length} declared`);\n if (!s.writable) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"read-only\"));\n head.append(meta);\n host.append(head);\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n host.append(pr);\n }\n }\n root.append(host);\n\n // --- owned properties ----------------------------------------------------\n const ownedCount = d.properties.patter.length + d.properties.scene.reduce((n, s) => n + s.properties.length, 0);\n const owned = section(doc, \"properties\", \"Story properties\", ownedCount, open.has(\"properties\"));\n if (!ownedCount) owned.append(el(doc, \"div\", \"pp-bundle-empty\", \"none declared\"));\n for (const p of d.properties.patter) owned.append(propertyRow(doc, p));\n for (const s of d.properties.scene) {\n owned.append(row(doc, `@scene`, s.gameId, \"pp-bundle-val pp-bundle-addr\"));\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n owned.append(pr);\n }\n }\n root.append(owned);\n\n // --- gameData ------------------------------------------------------------\n if (d.gameData.length) {\n const gd = section(doc, \"gameData\", \"Game data\", d.gameData.reduce((n, g) => n + g.fields.length, 0), open.has(\"gameData\"));\n for (const g of d.gameData) {\n gd.append(row(doc, \"on\", g.kind));\n for (const f of g.fields) {\n const fr = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n fr.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", f.name), el(doc, \"span\", \"pp-bundle-val\", f.type));\n gd.append(fr);\n }\n }\n root.append(gd);\n }\n\n // --- counts --------------------------------------------------------------\n const counts = section(doc, \"counts\", \"Counts\", null, open.has(\"counts\"));\n for (const [key, value] of Object.entries(d.counts)) counts.append(row(doc, key, String(value)));\n root.append(counts);\n\n opts.container?.append(root);\n return {\n el: root,\n description: d,\n destroy() { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Audio resolver (#206): map a beat id to the path/URL of its winning audio\n// take, using the `patteraudio.json` manifest Patterpad (or the CLI) emits next\n// to the Audio Folders. It RESOLVES ONLY - playback stays yours (Web Audio, an\n// <audio> element, etc.), which can't be portable across platforms anyway.\n//\n// The manifest already encodes the highest-rung \"winner\" per beat, so there's no\n// folder search at runtime. Deploy your audio folder somewhere your game can read\n// (e.g. under /public), fetch the manifest once, and:\n//\n// const audio = createAudioResolver(manifestJson, \"audio\"); // base = where the folder is served\n// const src = audio.resolve(step.id); // \"audio/final/L1.wav\", or null when the beat has no recording\n// if (src) new Audio(src).play();\n// ---------------------------------------------------------------------------\n\nexport interface AudioResolver {\n /** The full path/URL of a beat's winning audio take, or null when it has none. */\n resolve(beatId: string): string | null;\n}\n\ninterface AudioManifest {\n schema?: string;\n clips?: Record<string, { file: string; status?: string }>;\n}\n\n/**\n * Build an audio resolver from a `patteraudio.json` manifest string plus the base path/URL its files live\n * under (wherever you deployed the audio folder). `resolve(beatId)` joins the base with the winning clip's\n * relative path, or returns null when the beat has no recording. Never throws on a missing beat.\n */\nexport function createAudioResolver(manifestJson: string, basePath: string): AudioResolver {\n const manifest = JSON.parse(manifestJson) as AudioManifest;\n const clips = manifest.clips ?? {};\n const base = basePath.replace(/[/\\\\]+$/, \"\"); // trim trailing slash(es); we add exactly one when joining\n return {\n resolve(beatId: string): string | null {\n const clip = clips[beatId];\n if (!clip || !clip.file) return null;\n return base ? `${base}/${clip.file}` : clip.file;\n },\n };\n}\n"],"mappings":"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","names":["browser_exports","__export","Checkpoint","Engine","Flow","SAVE_SCHEMA","applyLiveBundle","buildTagIndex","createAudioResolver","createBundleInspector","createDebugLink","createPropertyInspector","createStateLogger","describeBundle","deserializeState","diffState","effectiveGameData","gameDataFields","gameDataValue","getProperty","loadState","saveState","serializeState","setProperties","setProperty","snapshotState","own","record","key","deserialiseAst","node","args","EvalError","message","evaluate","node","ctx","dialect","missingPolicy","s","rec","n","scope","own","val","arg","ladder","ladderOf","current","stageIndex","def","r","left","right","lLadder","rLadder","sameLadder","valueEquals","assertNumbers","a","b","x","y","i","l","op","value","toUint32","seed","modded","makePrng","s","CHECK_FLAGS_COUNTING_CALL","node","DEFAULT_COUNTING_CALLS","matchedSpecificity","evalTruthy","opts","countingCalls","walk","want","l","r","rule","c","operands","holds","diffState","prev","next","changes","paths","path","from","to","show","v","prefixOf","m","createStateLogger","adapter","opts","sink","line","label","emit","c","full","out","prefix","name","value","baseline","pushed","mounted","hook","bag","change","mount","mounts","i","diffed","PropertyBag","_PropertyBag","declarations","opts","d","name","defaultFor","value","n","change","audit","fn","rowFor","c","k","values","v","writable","pathPrefix","rowName","lowerCase","SAVE_FRAGMENT_VERSION","ScopeRegistry","token","o","bag","waiting","e","prefix","key","resolver","norm","decls","scope","out","owner","row","host","view","scopes","qualities","normOf","aliases","declsOf","m","properties","vals","blob","fragment","by","wants","ENGINE_SCOPES","host","h","patterDialect","args","EvalError","next","a","b","lo","hi","flagsCall","flags","readFlags","i","arg","result","idx","idArg","nodeId","EXTERNAL_SCOPES","ENGINE_SCOPES","s","t","splitRef","ref","isScope","parts","nodeId","args","h","fn","v","EvalError","idArg","fnName","first","readFlags","arg","flagsCall","meta","i","BARE_REF","tokenise","text","buf","i","c","close","raw","inner","renderSlotValue","v","isCaptionWs","c","collapseCaptionWs","s","out","pendingSpace","stripCaptions","text","open","close","i","removed","end","interpolate","resolve","tok","tokenise","walkNodes","nodes","visit","node","children","gameIdify","text","effectiveGameId","entity","g","gameIdify","castStringKey","name","DEFAULT_CAPTION_DELIMITERS","DEFAULT_CAPTION_CHARACTER","SAVE_SCHEMA","SAVE_VERSION","dedupe","tags","seen","out","t","buildTagIndex","bundle","index","visit","node","inherited","acc","child","beat","scene","sceneAcc","block","blockAcc","astCache","OWNER","esc","id","OWNS_REGISTRY","keys","sceneId","flowId","Checkpoint","journal","Engine","_Engine","bundle","options","locale","allStrings","strings","defaultStrings","loc","emitIds","castDisplay","c","nodeIndex","blockIndex","blockById","scene","effectiveGameId","blockAddrs","block","walkNodes","n","props","patterSharedDecls","p","toDecl","patterLocalDecls","patterSharedNames","d","registry","ScopeRegistry","ownsRegistry","patterBag","PropertyBag","hostScopes","registered","hostBound","worldSpec","s","decls","toForeignDecl","spec","selfBackedDecl","e","k","sceneSharedNames","names","flow","event","buildTagIndex","DEFAULT_CAPTION_DELIMITERS","DEFAULT_CAPTION_CHARACTER","snapshot","carryOver","next","make","fresh","f","on","opts","blockId","prior","Flow","existing","token","resolveScene","resolveBlock","ref","beatId","sceneRef","blockRef","out","collectCast","gameDataField","beat","b","tags","info","name","castStringKey","source","mounts","bag","checkpoint","undo","i","what","keep","reg","t","scope","value","prev","handler","h","defaultFor","split","splitHostRef","blob","flows","SAVE_VERSION","serialiseSelectors","save","version","values","sectionsFromV2","st","deserialiseSelectors","snap","v","host","seed","toUint32","base","scopes","key","fromDecls","first","rngState","started","flowEnded","currentSceneId","activeSnippet","beatIndex","pendingChoice","pendingPrompt","pendingPromptOwnerId","stack","played","r","transitions","jump","frame","children","from","choice","option","o","node","authored","shared","savedPrompt","nextId","at","ch","byId","owner","selector","pick","containerId","snippet","group","fallbacks","child","eligible","hidden","fallback","to","mode","verdicts","_","order","exhaust","trace","picked","len","stick","fill","eligibleIds","last","pool","remaining","best","tier","x","want","matchedSpecificity","truthy","evaluate","patterDialect","err","map","seen","was","copy","effects","kind","expr","message","ast","deserialiseAst","own","ownBefore","before","prng","makePrng","draw","withTags","raw","off","text","silent","interpolate","stripCaptions","shown","found","active","character","claimed","mountClaims","decl","nodes","gameData","rec","ask","scopeWritable","writable","hit","splitRef","isShared","d","scopeDefault","summariseProperty","summariseField","f","countBlock","block","counts","stack","node","beat","describeBundle","bundle","addresses","sceneProps","scene","gameId","effectiveGameId","b","hostScopes","s","gameData","fields","kind","gameDataFields","bundle","kind","gameDataValue","fields","node","name","f","effectiveGameData","out","v","k","saveState","engine","SAVE_SCHEMA","loadState","env","serializeState","deserializeState","json","getProperty","holder","ref","setProperty","value","setProperties","values","snapshotState","engine","out","m","allMounts","prefix","name","v","visitState","f","describeStep","step","gd","data","save","id","n","fid","snap","createStateLogger","opts","tag","kernel","l","createDebugLink","opts","url","Ctor","flows","queue","sock","closed","build","flush","m","post","msg","sendHello","hello","ev","flowId","sceneId","beatId","type","choiceId","next","applyLiveBundle","engine","current","data","next","STYLE_ID","CSS","injectStyle","doc","s","sameValue","a","b","x","i","createPropertyInspector","engine","opts","el","heading","list","io","saveBtn","blob","serializeState","loadBtn","input","file","text","deserializeState","refresh","err","rowRefreshers","buildRow","row","r","label","ctl","reset","read","commit","v","syncReset","focused","node","cb","inp","sel","o","props","empty","fn","pollMs","timer","STYLE_ID","CSS","injectStyle","doc","s","el","tag","cls","text","e","row","key","value","valueClass","r","propertyRow","p","meta","section","name","label","count","open","d","createBundleInspector","bundle","opts","describeBundle","root","id","addr","a","b","br","hostCount","n","host","head","pr","ownedCount","owned","gd","g","f","fr","counts","createAudioResolver","manifestJson","basePath","clips","base","beatId","clip"]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../../../../expr/packages/expr/src/own.ts","../../../../expr/packages/expr/src/ast.ts","../../../../expr/packages/expr/src/evaluate.ts","../../../../expr/packages/expr/src/prng.ts","../../../node_modules/@wildwinter/expr-specificity/src/index.ts","../../../../expr/packages/scoperegistry/src/state-logger.ts","../../../../expr/packages/scoperegistry/src/index.ts","../../dialect/src/engine-scopes.ts","../../dialect/src/index.ts","../../model/src/index.ts","../../runtime/src/tags.ts","../../runtime/src/engine.ts","../../runtime/src/describe.ts","../../runtime/src/gamedata.ts","../src/save.ts","../src/properties.ts","../src/logger.ts","../src/debug.ts","../src/refresh.ts","../src/inspector.ts","../src/bundle-inspector.ts","../src/audio.ts"],"sourcesContent":["// The browser drop-in: ONE classic script, no build step, defining the `Patterplay` global with the\n// runtime AND these helpers on it, so a plain page can build an engine, play, and save and load the\n// family's save text from a single script tag (and the Storylet Engine's beside it).\n//\n// Built by this package rather than the runtime because this is the one package that depends on\n// both: the runtime cannot bundle the helpers without bundling itself twice. The export names of the\n// two do not overlap (pinned by test/browser-drop-in.test.ts), so `export *` from each is exact.\n// Asked for by the Storylets side (from-storylets/browser-drop-in-carries-the-helpers, 2026-09-04),\n// whose `storyletengine.min.js` took the same shape the same day.\nexport * from \"@patterkit/runtime\";\nexport * from \"./index.js\";\n","// ---------------------------------------------------------------------------\n// Own-property reads, for every lookup by a name an author wrote.\n//\n// The language's names come from authors (`@world.constructor`, a function\n// called `toString`), and a plain JavaScript object answers the names it\n// inherits from Object.prototype. Looked up with `record[name]`, the keyword\n// table turned `constructor` into a token that was not an identifier, a static\n// scope bag read a built-in function as a property's value, and a dialect's\n// function table offered `Object` as a function to call (October 2026 review).\n// Every such lookup goes through `own`, which sees a record's own entries only.\n// ---------------------------------------------------------------------------\n\n/** `record[key]` when the record has it as its OWN property, else undefined. */\nexport function own<T>(record: Readonly<Record<string, T>> | undefined, key: string): T | undefined {\n return record !== undefined && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;\n}\n","// ---------------------------------------------------------------------------\n// AST - the in-memory expression tree and its serialised tagged-tuple form.\n//\n// The in-memory `ExprNode` is a discriminated union (kind field). The published\n// `AstNode` is the compact tagged-tuple form that goes into a compiled bundle's\n// { src, ast } envelope - what a runtime walks, never parses.\n//\n// This module is dialect-agnostic: scope tokens and function names are plain\n// strings here; meaning is supplied by a Dialect (see dialect.ts).\n// ---------------------------------------------------------------------------\n\nexport type ScalarValue = boolean | number | string | string[];\n\nexport type BinaryOp =\n | \"==\" | \"!=\" | \">\" | \">=\" | \"<\" | \"<=\"\n | \"+\" | \"-\" | \"*\" | \"/\"\n | \"and\" | \"or\";\n\nexport type UnaryOp = \"not\" | \"neg\";\n\nexport type ExprNode =\n | { kind: \"bool\"; value: boolean }\n | { kind: \"number\"; value: number }\n | { kind: \"string\"; value: string }\n // All property references are scoped: bare `@name` is canonicalised to\n // `@<defaultScope>.name` at parse time. Names are lowercased at parse time.\n | { kind: \"scopedvar\"; scope: string; name: string }\n | { kind: \"call\"; name: string; args: ExprNode[] }\n | { kind: \"unary\"; op: UnaryOp; operand: ExprNode }\n | { kind: \"binary\"; op: BinaryOp; left: ExprNode; right: ExprNode }\n // Produced only by flag-delta function argument parsing (see Dialect\n // `flagDeltaArgs`) - not valid elsewhere.\n | { kind: \"flagdelta\"; sign: \"+\" | \"-\"; name: string };\n\n/**\n * Path into an ExprNode tree. Each segment names the field on the parent node,\n * with numeric indices for array elements (call args).\n * binary.left -> [\"left\"]\n * binary.right.args[0] -> [\"right\", \"args\", 0]\n * top-level node -> []\n */\nexport type AstPath = readonly (string | number)[];\n\n// ---------------------------------------------------------------------------\n// Published tagged-tuple form (JSON arrays, opcode at index 0).\n// ---------------------------------------------------------------------------\n\nexport type AstNode =\n | [\"b\", boolean]\n | [\"n\", number]\n | [\"s\", string]\n | [\"sv\", string, string]\n | [\"u\", UnaryOp, AstNode]\n | [\"bin\", BinaryOp, AstNode, AstNode]\n | [\"call\", string, ...AstNode[]]\n | [\"fd\", \"+\" | \"-\", string];\n\n/** In-memory ExprNode -> published tagged-tuple AstNode. */\nexport function serialiseAst(node: ExprNode): AstNode {\n switch (node.kind) {\n case \"bool\": return [\"b\", node.value];\n case \"number\": return [\"n\", node.value];\n case \"string\": return [\"s\", node.value];\n case \"scopedvar\": return [\"sv\", node.scope, node.name];\n case \"unary\": return [\"u\", node.op, serialiseAst(node.operand)];\n case \"binary\": return [\"bin\", node.op, serialiseAst(node.left), serialiseAst(node.right)];\n case \"call\": return [\"call\", node.name, ...node.args.map(serialiseAst)];\n case \"flagdelta\": return [\"fd\", node.sign, node.name];\n }\n}\n\n/** Published tagged-tuple AstNode -> in-memory ExprNode. */\nexport function deserialiseAst(node: AstNode): ExprNode {\n switch (node[0]) {\n case \"b\": return { kind: \"bool\", value: node[1] };\n case \"n\": return { kind: \"number\", value: node[1] };\n case \"s\": return { kind: \"string\", value: node[1] };\n case \"sv\": return { kind: \"scopedvar\", scope: node[1], name: node[2] };\n case \"u\": return { kind: \"unary\", op: node[1], operand: deserialiseAst(node[2]) };\n case \"bin\": return { kind: \"binary\", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };\n case \"call\": {\n const args = (node.slice(2) as AstNode[]).map(deserialiseAst);\n return { kind: \"call\", name: node[1], args };\n }\n case \"fd\": return { kind: \"flagdelta\", sign: node[1], name: node[2] };\n }\n}\n","// ---------------------------------------------------------------------------\n// Evaluator - walk an ExprNode against an EvalContext, parameterised by Dialect.\n//\n// Operators (binary/unary), short-circuiting, and type-checking are generic.\n// Scope resolution uses the context's scope maps + the Dialect's per-scope\n// missing-property policy. Function calls dispatch to the Dialect's functions.\n//\n// Ported from @storylets/engine (storylets/packages/engine/src/expression.ts),\n// generalised by injecting scopes + functions from the Dialect.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode, ScalarValue } from \"./ast.js\";\nimport type { Dialect, EvalContext, ScopeResolver } from \"./dialect.js\";\nimport { own } from \"./own.js\";\n\nexport class EvalError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EvalError\";\n }\n}\n\nexport function evaluate(node: ExprNode, ctx: EvalContext, dialect: Dialect): ScalarValue {\n // Per-scope missing-property policy, precomputed once per top-level evaluate.\n const missingPolicy = new Map<string, \"false\" | \"throw\">(\n dialect.scopes.map((s) => [s.token, s.missing ?? \"false\"])\n );\n\n const rec = (n: ExprNode): ScalarValue => {\n switch (n.kind) {\n case \"bool\": return n.value;\n case \"number\": return n.value;\n case \"string\": return n.value;\n\n case \"scopedvar\": {\n const scope = own(ctx.scopes, n.scope);\n if (scope === undefined) {\n // Scope context absent -> graceful false. (A scope the dialect knows\n // about but the context didn't populate, or an unknown scope.)\n return false;\n }\n // A scope is either a static bag or a host resolver ({ get }). Bag values\n // are always ScalarValue (never functions), so a `get` function reliably\n // distinguishes a resolver.\n const val = typeof (scope as ScopeResolver).get === \"function\"\n ? (scope as ScopeResolver).get(n.name)\n : own(scope as Record<string, ScalarValue>, n.name);\n if (val === undefined) {\n // Property not declared on the present scope. Policy decides: \"false\"\n // for back-compat scopes, \"throw\" for scopes where a missing key is a\n // bug publish-time validation should have caught.\n if (missingPolicy.get(n.scope) === \"throw\") {\n throw new EvalError(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);\n }\n return false;\n }\n return val;\n }\n\n case \"call\": {\n // `advance` is the language's own, the first core built-in: the next\n // stage in the argument's ladder, saturating at the last. Core rather\n // than dialect because it IS the quality design's insertion mechanism\n // (an outcome that never names its destination routes through an\n // inserted stage automatically), and every dialect should say it the\n // same way. A dialect that defines its own `advance` wins, for\n // back-compat with any dialect that already had one.\n if (n.name === \"advance\" && !own(dialect.functions, n.name)) {\n const arg = n.args[0];\n if (n.args.length !== 1 || arg === undefined) {\n throw new EvalError(`advance() takes exactly 1 argument, got ${n.args.length}`);\n }\n const ladder = ladderOf(arg, ctx);\n if (ladder === undefined) {\n throw new EvalError(\"advance() needs a quality reference (@scope.name of a quality property)\");\n }\n const current = stageIndex(rec(arg), ladder, \"advance\");\n return ladder[Math.min(current + 1, ladder.length - 1)]!;\n }\n const def = own(dialect.functions, n.name);\n if (!def) throw new EvalError(`unknown function '${n.name}'`);\n return def.eval(n.args, { evaluate: rec, ctx });\n }\n\n case \"flagdelta\":\n throw new EvalError(\"flagdelta node is only valid as an argument to a flag-delta function\");\n\n case \"unary\": {\n if (n.op === \"not\") {\n const val = rec(n.operand);\n if (typeof val !== \"boolean\") throw new EvalError(`'not' requires a boolean operand, got ${typeof val}`);\n return !val;\n }\n // neg\n const val = rec(n.operand);\n if (typeof val !== \"number\") throw new EvalError(`unary '-' requires a numeric operand, got ${typeof val}`);\n return -val;\n }\n\n case \"binary\": {\n // Short-circuit operators first\n if (n.op === \"and\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'and' requires boolean operands, left is ${typeof l}`);\n if (!l) return false;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'and' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n if (n.op === \"or\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'or' requires boolean operands, left is ${typeof l}`);\n if (l) return true;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'or' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n\n const left = rec(n.left);\n const right = rec(n.right);\n\n // Quality: when either operand REFERENCES a quality (the node carries\n // the scope+name the channel resolves), ordering compares by ladder\n // position and arithmetic is refused. Everything else is untouched,\n // so a context with no channel behaves exactly as before.\n const lLadder = ladderOf(n.left, ctx);\n const rLadder = ladderOf(n.right, ctx);\n const ladder = lLadder ?? rLadder;\n if (ladder !== undefined) {\n if (lLadder && rLadder && !sameLadder(lLadder, rLadder)) {\n if (n.op === \">\" || n.op === \">=\" || n.op === \"<\" || n.op === \"<=\") {\n throw new EvalError(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);\n }\n }\n switch (n.op) {\n case \">\": return stageIndex(left, ladder, \">\") > stageIndex(right, ladder, \">\");\n case \">=\": return stageIndex(left, ladder, \">=\") >= stageIndex(right, ladder, \">=\");\n case \"<\": return stageIndex(left, ladder, \"<\") < stageIndex(right, ladder, \"<\");\n case \"<=\": return stageIndex(left, ladder, \"<=\") <= stageIndex(right, ladder, \"<=\");\n case \"+\": case \"-\": case \"*\": case \"/\":\n throw new EvalError(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);\n default: break; // == and != fall through to plain value equality\n }\n }\n\n switch (n.op) {\n case \"==\": return valueEquals(left, right);\n case \"!=\": return !valueEquals(left, right);\n case \">\": assertNumbers(left, right, \">\"); return (left as number) > (right as number);\n case \">=\": assertNumbers(left, right, \">=\"); return (left as number) >= (right as number);\n case \"<\": assertNumbers(left, right, \"<\"); return (left as number) < (right as number);\n case \"<=\": assertNumbers(left, right, \"<=\"); return (left as number) <= (right as number);\n case \"+\":\n if (typeof left === \"number\" && typeof right === \"number\") return left + right;\n if (typeof left === \"string\" && typeof right === \"string\") return left + right;\n throw new EvalError(`'+' requires two numbers or two strings, got ${typeof left} and ${typeof right}`);\n case \"-\": assertNumbers(left, right, \"-\"); return (left as number) - (right as number);\n case \"*\": assertNumbers(left, right, \"*\"); return (left as number) * (right as number);\n case \"/\":\n assertNumbers(left, right, \"/\");\n if ((right as number) === 0) throw new EvalError(\"division by zero\");\n return (left as number) / (right as number);\n }\n }\n }\n };\n\n return rec(node);\n}\n\n/**\n * Equality for `==` / `!=`. Primitives compare by value (JS `===`); arrays\n * (the flags value type) compare as SETS: same members, ORDER IRRELEVANT.\n *\n * Plain `===` on arrays would be reference equality - two distinct arrays with\n * the same contents would never be equal, and a fresh array (from a scope read\n * or a function result) would never equal another. Mixed array/non-array\n * operands are unequal, and never an error.\n *\n * Order was significant until 2026-09-01, and that was wrong: a flags value IS\n * a set, and its stored order is an artefact of the order somebody happened to\n * add things in. `set_flags(@f, +a)` then `+b` compared UNEQUAL to the same two\n * flags added the other way round, which is a difference no author can see and\n * none intends. The Storylet Engine papered over it by sorting in `set_flags`,\n * which only holds while every producer sorts: a bundle's declared default, or\n * a host handing a list in, does not.\n *\n * Compared as MULTISETS (sorted copies), so a duplicated flag still counts. A\n * well-formed flags value has no duplicates, but equality should not be the\n * thing that decides what happens if one appears.\n */\nfunction valueEquals(a: ScalarValue, b: ScalarValue): boolean {\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n const x = [...a].sort();\n const y = [...b].sort();\n for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;\n return true;\n }\n return a === b;\n}\n\nfunction assertNumbers(l: ScalarValue, r: ScalarValue, op: string): void {\n if (typeof l !== \"number\" || typeof r !== \"number\") {\n throw new EvalError(`'${op}' requires numeric operands, got ${typeof l} and ${typeof r}`);\n }\n}\n\n// --- quality (design: storylets-new/design/quality.md) -----------------------\n\n/** The ladder behind an operand NODE, when the context's quality channel says\n * it references a quality. Values are plain strings; the node is what carries\n * the (scope, name) the channel needs. */\nfunction ladderOf(node: ExprNode, ctx: EvalContext): readonly string[] | undefined {\n if (node.kind !== \"scopedvar\" || ctx.qualities === undefined) return undefined;\n return ctx.qualities(node.scope, node.name);\n}\n\n/** Index of a stage in a ladder; an unknown stage is an error naming the\n * value, never a silent pass (a drifted save is exactly what lands here). */\nfunction stageIndex(value: ScalarValue, ladder: readonly string[], op: string): number {\n if (typeof value !== \"string\") {\n throw new EvalError(`'${op}' on a quality compares stages, got ${typeof value}`);\n }\n const i = ladder.indexOf(value);\n if (i < 0) throw new EvalError(`\"${value}\" is not a stage of this quality (stages: ${ladder.join(\", \")})`);\n return i;\n}\n\nconst sameLadder = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((x, i) => x === b[i]);\n","// ---------------------------------------------------------------------------\n// mulberry32 - the contractual PRNG.\n//\n// A fixed, published algorithm that both product families need and neither\n// owns. It lived in `@storylet-studio/runtime` as prng.ts and inline in\n// `@patterkit/runtime`'s engine.ts, plus separately in three of Patterplay's\n// harnesses, plus a hand port in each of six native runtimes: thirteen copies\n// of forty lines. It belongs here, in the package both families already depend\n// on, for the same reason the evaluator does.\n//\n// All arithmetic is unsigned 32-bit, matching JavaScript's `>>> 0` and\n// Math.imul.\n// ---------------------------------------------------------------------------\n\nexport interface Prng {\n /** One draw in [0, 1); advances the state. */\n next(): number;\n /** The persisted state (a uint32; feed back into makePrng to restore). */\n state(): number;\n}\n\n/**\n * ECMA-262 7.1.6 ToUint32, which is what `seed >>> 0` means.\n *\n * Written out rather than spelled `seed >>> 0` because THIS is the obligation a\n * port in a language without JavaScript's shift semantics has to reproduce, and\n * the shorthand hides it. Patterplay's C++ port cast the seed straight to an\n * integer type, which is undefined behaviour outside that type's range and gave\n * two wrong answers before the parity corpus pinned them.\n */\nexport function toUint32(seed: number): number {\n if (Number.isNaN(seed) || !Number.isFinite(seed)) return 0;\n const modded = Math.trunc(seed) % 4294967296;\n return modded < 0 ? modded + 4294967296 : modded;\n}\n\n/**\n * Seed a generator. The state is UNSIGNED throughout (`>>> 0`, never `| 0`):\n * the two produce identical draws and differ only in the sign of the number a\n * save persists, and Patterplay shipped `| 0` for long enough that over half of\n * all its saves carried a negative state its own native ports could not read\n * back.\n */\nexport function makePrng(seed: number): Prng {\n let s = toUint32(seed);\n return {\n next(): number {\n s = (s + 0x6d2b79f5) >>> 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n },\n state(): number {\n return s;\n },\n };\n}\n\n/** The contractual shuffle: Fisher-Yates, descending. Runs of one element\n * consume no draws (the loop never executes for length < 2). */\nexport function shuffleInPlace<T>(arr: T[], prng: Prng): void {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(prng.next() * (i + 1));\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n }\n}\n","// ---------------------------------------------------------------------------\n// @wildwinter/expr-specificity - public surface.\n//\n// Matched-constraint specificity: score how many atomic constraints in an\n// expression are actively holding it true against the current state. An\n// evaluation-aware walk (unlike a static clause count, an `or`'s score depends\n// on which branch is currently matching). Shared by Storylet Studio (storylet\n// draw priority) and Patter (dialogue best-match), which had independently\n// grown the same algorithm.\n//\n// Built on @wildwinter/expr's `ExprNode`. The host supplies truthiness via an\n// `evalTruthy` closure, so this package stays ignorant of the eval context,\n// the dialect, and the host's truthiness rule - each host keeps its own\n// behaviour while sharing one definition of the walk.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode } from \"@wildwinter/expr\";\n\n/** A call node, narrowed from the ExprNode union. */\ntype CallNode = Extract<ExprNode, { kind: \"call\" }>;\n\n/**\n * Evaluate an expression subtree to a boolean. Host-bound: the host closes over\n * its own evaluate + eval context + dialect and applies its own truthiness\n * coercion (Storylets' `conditionPasses`, Patter's `truthy`, etc.).\n */\nexport type EvalTruthy = (node: ExprNode) => boolean;\n\n/**\n * A call treated as a conjunction of constraints rather than a single atom, so\n * it contributes its operand count instead of 1. `check_flags` is the built-in\n * example (see {@link CHECK_FLAGS_COUNTING_CALL}).\n */\nexport interface CountingCall {\n /** The function name this rule applies to. */\n name: string;\n /** How many constraints the call contributes when it holds (at least 1). */\n count: (node: CallNode) => number;\n}\n\nexport interface MatchedSpecificityOptions {\n /**\n * Root polarity - the truth value the whole condition must have. Production\n * only ever scores conditions already known eligible, so this defaults to\n * `true` and rarely needs setting.\n */\n want?: boolean;\n /**\n * Calls scored by operand count rather than as a single atom. Defaults to\n * `[CHECK_FLAGS_COUNTING_CALL]`. Supply your own to add or replace rules.\n */\n countingCalls?: readonly CountingCall[];\n}\n\n/**\n * `check_flags(v, f1..fN)` counts as N constraints - an N-ary AND over the flag\n * operands - never fewer than 1. `args[0]` is the flags source, so the operand\n * count is `args.length - 1`.\n */\nexport const CHECK_FLAGS_COUNTING_CALL: CountingCall = {\n name: \"check_flags\",\n count: (node) => Math.max(1, node.args.length - 1),\n};\n\nconst DEFAULT_COUNTING_CALLS: readonly CountingCall[] = [CHECK_FLAGS_COUNTING_CALL];\n\n/**\n * Score how many atomic constraints in `node` are actively holding it true\n * against current state, via `evalTruthy`.\n *\n * The walk carries a polarity flag `want` (\"the truth value this subtree must\n * have for the whole to hold\"), applying De Morgan as it descends:\n * - atom: 1 if its truth matches `want`, else 0\n * - and: under `want`, both must hold -> sum; under `!want`, behaves as or\n * - or: under `want`, strongest branch -> max; under `!want`, behaves as and\n * - not: recurse with `want` flipped\n * - counting call (e.g. check_flags): its operand count when it must hold and\n * does, else the negated rules apply\n *\n * @example\n * // `@x == 5 and @y > 3` with both holding -> 2\n * matchedSpecificity(ast, node => conditionPasses(evaluate(node, ctx)))\n */\nexport function matchedSpecificity(\n node: ExprNode,\n evalTruthy: EvalTruthy,\n opts?: MatchedSpecificityOptions,\n): number {\n const countingCalls = opts?.countingCalls ?? DEFAULT_COUNTING_CALLS;\n return walk(node, opts?.want ?? true, evalTruthy, countingCalls);\n}\n\nfunction walk(\n node: ExprNode,\n want: boolean,\n evalTruthy: EvalTruthy,\n countingCalls: readonly CountingCall[],\n): number {\n if (node.kind === \"binary\" && (node.op === \"and\" || node.op === \"or\")) {\n const l = walk(node.left, want, evalTruthy, countingCalls);\n const r = walk(node.right, want, evalTruthy, countingCalls);\n // De Morgan: an `and` under negation behaves like an `or`, and vice versa.\n const behaveAsAnd = (node.op === \"and\") === want;\n if (behaveAsAnd) return l > 0 && r > 0 ? l + r : 0; // both must hold -> sum\n return Math.max(l, r); // either holds -> strongest branch\n }\n if (node.kind === \"unary\" && node.op === \"not\") {\n return walk(node.operand, !want, evalTruthy, countingCalls);\n }\n if (node.kind === \"call\") {\n const rule = countingCalls.find((c) => c.name === node.name);\n if (rule) {\n const operands = rule.count(node);\n const holds = evalTruthy(node);\n if (want) return holds ? operands : 0;\n return holds ? 0 : 1; // negated: De Morgan -> at least one operand fails -> 1\n }\n }\n // Any other node is an atom worth one constraint when its truth matches want.\n return evalTruthy(node) === want ? 1 : 0;\n}\n","// ---------------------------------------------------------------------------\n// The state logger: what changed in the state kernel, as it changes.\n//\n// Both product families shipped one of these, in four runtimes each, and they\n// were not the same shape. The Storylet Engine's was PUSH-based on the\n// PropertyBag audit hook - a write logs the moment it lands, with the previous\n// value straight off the event. Patterplay's diffed whole saveGame() snapshots,\n// so it could only ever say what changed BETWEEN captures, and only for state a\n// save persists. This is the first one, because it is the better one: a diff\n// cannot tell a write from a write-and-write-back, cannot name the reason a\n// host attached to a write, and cannot see a value that changed and changed\n// back.\n//\n// A diff is still needed, and is kept, for everything that is NOT in a bag: a\n// product's own non-property state (turns, cooldowns, visit counts) arrives\n// through the adapter's `extra()` and is diffed on capture. Bags replaced\n// wholesale by a load fire no audit events either, so capture() re-reads and\n// re-mounts.\n//\n// Line format: `${label}${path}: ${from} -> ${to}`, `<unset>` for a value that\n// was not there.\n// ---------------------------------------------------------------------------\n\nimport type { PropertyBag, ScalarValue } from \"./index.js\";\n\n/** A flattened snapshot: path -> value. */\nexport type StateSnapshot = Record<string, ScalarValue>;\n\nexport interface StateChange {\n path: string;\n from: ScalarValue | undefined;\n to: ScalarValue | undefined;\n}\n\n/**\n * One bag on the logger's path space.\n *\n * Named for the LOG, not the bag: a product may already have its own type for enumerating\n * bags (the Storylet Engine's LogMount, which labels a mount \"story\" for its own purposes),\n * and in the ported runtimes both land in one namespace. They are also not the same thing,\n * which the prefix rule below is about.\n *\n * `pathPrefix` is used VERBATIM, separator included, exactly as the bag's own\n * is - it is not a scope token with a dot implied. Omit it and the bag's own\n * `pathPrefix` is used, which is what a product wants whenever its log paths\n * and its property addresses agree.\n *\n * They do not always agree, which is why this can be overridden: Patterplay\n * addresses a scene property `@scene.mood` (relative to the flow's current\n * scene) but has to LOG it as `@scene:kitchen.mood`, because a log covering\n * several scenes needs to say which one.\n */\nexport interface LogMount {\n bag: PropertyBag;\n pathPrefix?: string;\n}\n\n/** What a product supplies: its kernel bags (re-read on every capture, so a\n * product that replaces its bags on load re-mounts), and its non-property\n * state as flattened paths. */\nexport interface StateLoggerAdapter {\n mounts(): LogMount[];\n extra?(): StateSnapshot;\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to console.log. */\n sink?: (line: string) => void;\n /** Prefixed to every line, verbatim (e.g. `\"[board] \"`). */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state. Logs nothing. */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the audited writes already logged as\n * they landed, plus anything that changed WITHOUT an audit event, diffed,\n * logged and re-baselined. */\n capture(): StateChange[];\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\n/** The sorted set of paths that differ between two snapshots. */\nexport function diffState(prev: StateSnapshot, next: StateSnapshot): StateChange[] {\n const changes: StateChange[] = [];\n const paths = new Set([...Object.keys(prev), ...Object.keys(next)]);\n for (const path of [...paths].sort()) {\n const from = prev[path], to = next[path];\n if (JSON.stringify(from) !== JSON.stringify(to)) changes.push({ path, from, to });\n }\n return changes;\n}\n\nconst show = (v: ScalarValue | undefined): string => (v === undefined ? \"<unset>\" : JSON.stringify(v));\n\nconst prefixOf = (m: LogMount): string => m.pathPrefix ?? m.bag.pathPrefix;\n\nexport function createStateLogger(adapter: StateLoggerAdapter, opts: StateLoggerOptions = {}): StateLogger {\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const label = opts.label ?? \"\";\n const emit = (c: StateChange): void => { sink(`${label}${c.path}: ${show(c.from)} -> ${show(c.to)}`); };\n\n const full = (): StateSnapshot => {\n const out: StateSnapshot = {};\n for (const m of adapter.mounts()) {\n const prefix = prefixOf(m);\n for (const [name, value] of Object.entries(m.bag.values)) out[prefix + name] = value;\n }\n Object.assign(out, adapter.extra?.() ?? {});\n return structuredClone(out);\n };\n\n let baseline = full();\n let pushed: StateChange[] = [];\n let mounted: { bag: PropertyBag; off: () => void }[] = [];\n\n const hook = (prefix: string, bag: PropertyBag): (() => void) =>\n bag.onAudit((change) => {\n // The write logs as it lands, `from` straight off the event; the baseline\n // moves with it so capture() never re-reports what was already said.\n const c: StateChange = structuredClone({ path: prefix + change.name, from: change.prev, to: change.next });\n emit(c);\n pushed.push(c);\n baseline[c.path] = structuredClone(change.next);\n });\n\n const mount = (): void => {\n const mounts = adapter.mounts();\n const same = mounted.length === mounts.length && mounts.every((m, i) => mounted[i]!.bag === m.bag);\n if (same) return;\n for (const m of mounted) m.off();\n mounted = mounts.map((m) => ({ bag: m.bag, off: hook(prefixOf(m), m.bag) }));\n };\n mount();\n\n return {\n snapshot: full,\n capture(): StateChange[] {\n // Whatever arrived WITHOUT an audit event: the adapter's non-property\n // paths, and bag values replaced wholesale by a load (which fires none).\n const next = full();\n const diffed = diffState(baseline, next);\n for (const c of diffed) emit(c);\n const changes = [...pushed, ...diffed];\n pushed = [];\n baseline = next;\n mount(); // a load replaces a product's bags; re-hook them\n return changes;\n },\n dispose(): void {\n for (const m of mounted) m.off();\n mounted = [];\n pushed = [];\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only TO THE STORY; the HOST still writes it, by passing\n * `{ host: true }` (see `set`). Default is read/write. (`type`/`values` feed\n * validation.)\n *\n * The distinction is the whole point of the flag on a foreign scope, where the\n * value is the game's own: a flag carried in the story's bundle must not lock a\n * game out of its own state. Ruled 2026-09-05, after both products met it - the\n * Storylet Engine's venue clock and Patter's coverage driver were each blocked\n * from the one property they existed to move.\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n /** The author's note on what the property is for, which another tool's picker shows as a tip\n * (shared game scopes, scopes.ts). The registry itself ignores it. */\n purpose?: string;\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n /** The address this property answers to - what getProperty/setProperty take.\n * A bag composes it from its own `pathPrefix` and the name, so a row is\n * self-describing: an examiner can render and write a row without being told\n * separately where it came from.\n *\n * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,\n * because a prefix is not always a bare scope token: the Storylet Engine\n * addresses a deck's properties as `deck.<id>.name`, so the prefix is already\n * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain\n * case. (`@gold` also resolves - splitRef defaults an unqualified name to the\n * patter scope - but it is the shorthand, not the address a row reports.)\n *\n * With no prefix this is just the name. Both families forked this interface\n * to add exactly this field - once per runtime - which is the same reason\n * `stages` is here. */\n path: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies.\n *\n * It has no prototype, so a scope answers only its own properties: on a plain\n * object, `@world.constructor` read the built-in `Object` function rather than\n * \"missing\", and a write to `__proto__` vanished (October 2026 review). */\n readonly values: Record<string, ScalarValue> = Object.create(null) as Record<string, ScalarValue>;\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n /** The address prefix this bag's rows carry, separator included (`@`,\n * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */\n readonly pathPrefix: string;\n\n constructor(\n declarations: ScopeDeclaration[] = [],\n opts?: { normalise?: (name: string) => string; pathPrefix?: string },\n ) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.pathPrefix = opts?.pathPrefix ?? \"\";\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** A name as this bag keys it: its normalisation policy applied. The registry\n * uses it to key quality ladders and the validation schema the bag's own way,\n * so a case-significant (identity) bag is not quietly folded to lower case\n * one layer up. */\n normalise(name: string): string {\n return this.norm(name);\n }\n\n /** Whether a STORY write to this property is allowed: false only for a\n * declaration marked `writable: false`. A host write is always allowed. The\n * registry asks before writing, so it never has to read a refusal out of an\n * error that a listener might have raised instead. */\n writable(name: string): boolean {\n return this.decls.get(this.norm(name))?.writable !== false;\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property unless the caller says it is the\n * HOST (`host: true`), for whom `writable: false` was never a rule - it is\n * the story's promise, not the game's. `silent` and `host` are separate on\n * purpose: one is about who hears the write, the other about who may make\n * it. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string; host?: boolean }): BagChange {\n const n = this.norm(name);\n if (!opts?.host && !this.writable(n)) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n // The listeners as they stood when the write began, each told once, whatever any\n // of them does to the lists meanwhile (unsubscribing itself or another): the\n // contract every port keeps, pinned by the registry corpus.\n for (const audit of [...this.auditors]) audit(change);\n if (!change.silent) for (const fn of [...this.subscribers]) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name, this.pathPrefix));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events. */\n load(values: Record<string, ScalarValue>): void {\n for (const [k, v] of Object.entries(values)) this.values[this.norm(k)] = v;\n }\n}\n\nfunction rowFor(\n d: ScopeDeclaration,\n value: ScalarValue | undefined,\n writable?: boolean,\n name?: string,\n pathPrefix = \"\",\n): PropertyRow {\n const rowName = name ?? d.name.toLowerCase();\n return {\n name: rowName,\n path: pathPrefix + rowName,\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n // `stages` was added to the row so an examiner could offer a quality's ladder\n // instead of a free-text box, and then never populated here: every quality row\n // this function built came out without one. Fixed 2026-09-02.\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n owner?: string;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n norm: (name: string) => string;\n owner?: string;\n}\ntype Entry = OwnedScope | ForeignScope;\n\nconst lowerCase = (name: string): string => name.toLowerCase();\n\n/** Options for an owned scope the registry builds (`defineOwned`). */\nexport interface OwnedScopeOptions {\n /** The address prefix its examiner rows carry, separator included. Defaults\n * to `<token>.`; the address grammar is the product's, not the registry's. */\n pathPrefix?: string;\n /** Name normalisation: lower case by default; a case-significant product\n * passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it (an engine's name). Named in a clash error and carried\n * on examiner rows, so one examiner can group a combined game by engine. */\n owner?: string;\n}\n\n/** Options for a foreign scope (`defineForeign`). */\nexport interface ForeignScopeOptions {\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Name normalisation for the names passed to the resolver: lower case by\n * default; a case-significant product passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it. See `OwnedScopeOptions.owner`. */\n owner?: string;\n}\n\n/** Options for a context or schema built from the registry. */\nexport interface AliasOptions {\n /**\n * Expression token -> registered key. `{ scene: \"patter/flow-2/scene/tavern\" }`\n * makes `@scene` read that instance bag, for this context only.\n *\n * The registry learns nothing about what the token MEANS: which flow, which\n * scene, which deck is an engine's own idea, and the engine names the key per\n * evaluation. It has to be the registry's mechanism rather than the engine\n * patching the context afterwards, because quality ladders and validation are\n * looked up by token too, and an alias applies to all three alike.\n *\n * An alias to a key that is not registered throws: a condition evaluated\n * against a scope that is not there is an engine bug, not a graceful false.\n */\n aliases?: Record<string, string>;\n}\n\n/** Options for `remove`. */\nexport interface RemoveOptions {\n /** Park an owned scope's values, to be handed back when the same key is next\n * registered (a live reload rebuilding an engine). No effect on a foreign\n * scope, whose values were never the registry's. */\n keep?: boolean;\n}\n\n/** Options for `load`. */\nexport interface LoadOptions {\n /** Keep what earlier loads parked, adding this blob's unclaimed sections to\n * it (a section for the same key replaces the parked one). Without it, each\n * load replaces whatever an earlier load parked. For an engine moving an\n * older save's values into a registry the game has already loaded. */\n keepParked?: boolean;\n}\n\n/**\n * The versioned owned-state fragment.\n *\n * @deprecated Versioning belongs to the save that embeds the values, not to the\n * registry; no engine ever called this. Embed `save()` in your own versioned\n * save instead. Removed at the next breaking release.\n */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\n/** @deprecated See `OwnedStateFragment`. Removed at the next breaking release. */\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n /** Values loaded for keys nobody has registered yet, waiting to be claimed. */\n private readonly parked = new Map<string, Record<string, ScalarValue>>();\n private rev = 0;\n\n /**\n * A counter that moves whenever a scope is registered or removed, and at no\n * other time: it starts at 0 and each registration or removal adds 1. Values\n * changing does not move it. A caller that caches a context built by\n * `toEvalContext()` rebuilds it when this moves, because the context's set of\n * scopes is fixed when it is built while the values it reads stay live.\n */\n get revision(): number {\n return this.rev;\n }\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n *\n * The third argument may be the path prefix alone (the pre-0.7 form) or an\n * options object.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[], opts?: string | OwnedScopeOptions): this {\n const o: OwnedScopeOptions = typeof opts === \"string\" ? { pathPrefix: opts } : opts ?? {};\n // The scope knows its own token, so its rows can address themselves: `world.hp`.\n // The ADDRESS GRAMMAR is the product's, though, not the registry's - Patterplay\n // writes `@patter.gold` where the Storylet Engine writes `world.gold` - so a\n // caller may say how its addresses look. A bag MOUNTED here keeps whatever prefix\n // its holder gave it: the holder owns the addressing.\n const bag = new PropertyBag(declarations, {\n pathPrefix: o.pathPrefix ?? `${token}.`,\n ...(o.normalise ? { normalise: o.normalise } : {}),\n });\n return this.mountOwned(token, bag, o.owner !== undefined ? { owner: o.owner } : undefined);\n }\n\n /**\n * Attach an EXISTING bag as an owned scope: an engine (or a host) holds the\n * bag and this registry reads, writes, lists and saves it like its own.\n *\n * If values were loaded for this key before anyone registered it, the bag\n * claims them now: laid over its seeded defaults by the bag's own `load` rule.\n */\n mountOwned(token: string, bag: PropertyBag, opts?: { owner?: string }): this {\n this.assertFree(token, opts?.owner);\n this.scopes.set(token, { kind: \"owned\", bag, ...(opts?.owner !== undefined ? { owner: opts.owner } : {}) });\n this.rev++;\n const waiting = this.parked.get(token);\n if (waiting) {\n bag.load(waiting);\n this.parked.delete(token);\n }\n return this;\n }\n\n /**\n * Unregister a scope. With `{ keep: true }` an owned scope's values are parked\n * and handed back when the same key is next registered, which is how a live\n * reload hands an engine's state to its replacement. Throws on an unknown key.\n */\n remove(token: string, opts?: RemoveOptions): this {\n const e = this.scopes.get(token);\n if (!e) throw new Error(`unknown scope '@${token}'`);\n if (opts?.keep && e.kind === \"owned\") this.parked.set(token, e.bag.save());\n this.scopes.delete(token);\n this.rev++;\n return this;\n }\n\n /**\n * Drop parked values nobody claimed. Parked values are kept in the next save by\n * default, so nothing loaded is lost to a flow or deck that simply has not\n * reopened yet; a game that knows they are dead drops them here.\n *\n * With a `prefix`, only keys starting with it are dropped: an engine resetting\n * itself drops its own instance keys (`my-engine/`) and leaves every other\n * engine's alone.\n */\n discardParked(prefix?: string): this {\n if (prefix === undefined) this.parked.clear();\n else for (const key of [...this.parked.keys()]) if (key.startsWith(prefix)) this.parked.delete(key);\n return this;\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n opts: boolean | ForeignScopeOptions = true,\n ): this {\n // A boolean is the pre-0.7 form: the scope-level writable default alone.\n const o: ForeignScopeOptions = typeof opts === \"boolean\" ? { writable: opts } : opts;\n this.assertFree(token, o.owner);\n const norm = o.normalise ?? lowerCase;\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(norm(d.name), d);\n this.scopes.set(token, {\n kind: \"foreign\", resolver, decls, scopeWritable: o.writable ?? true, norm,\n ...(o.owner !== undefined ? { owner: o.owner } : {}),\n });\n this.rev++;\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(e.norm(name));\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown scope.\n *\n * `writable: false` is the STORY's promise, so a story write is refused and\n * a HOST write is not: pass `{ host: true }` from a host's own surface (its\n * `setProperty`, its tooling, a coverage driver) and never from the path an\n * outcome or effect takes. A foreign scope whose resolver has no `set` is\n * refused for everyone, host included - that is not a rule to bypass, it is\n * a game that gave no way to write. */\n set(scope: string, name: string, value: ScalarValue, opts?: { host?: boolean }): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n // Asked first, never caught afterwards: catching whatever the bag's write raised\n // reported a listener's own error (a game's hook) as a read-only refusal.\n if (!opts?.host && !e.bag.writable(name)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.bag.set(name, value, opts?.host ? { host: true } : undefined);\n return;\n }\n const n = e.norm(name);\n if (!e.resolver.set) throw new Error(`'@${scope}.${name}' is read-only`);\n if (!opts?.host && !this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string; owner?: string } & PropertyRow)[] {\n const out: ({ scope: string; owner?: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n const owner = e.owner !== undefined ? { owner: e.owner } : {};\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...owner, ...row });\n } else {\n for (const [n, d] of e.decls) {\n out.push({\n scope: token, ...owner,\n ...rowFor(d, e.resolver.get(n), this.foreignWritable(e, n), n, `${token}.`),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>, opts?: AliasOptions): EvalContext {\n const view = this.view(opts?.aliases);\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of view) scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders(view);\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => {\n const e = view.get(scope);\n return e ? qualities.get(scope)?.get(normOf(e)(name)) : undefined;\n },\n };\n }\n\n /**\n * The scopes an expression sees: every registered key under its own token,\n * then each alias token pointing at its key's entry (an alias shadows a key of\n * the same name). Keys an engine uses for instance bags (`engine/flow-2/...`)\n * are not valid expression tokens, so they are present but unreachable.\n */\n private view(aliases?: Record<string, string>): Map<string, Entry> {\n const out = new Map(this.scopes);\n for (const [token, key] of Object.entries(aliases ?? {})) {\n const e = this.scopes.get(key);\n if (!e) throw new Error(`alias '@${token}' names '${key}', which is not registered`);\n out.set(token, e);\n }\n return out;\n }\n\n /** Every quality declaration's ladder, keyed scope token then name (the\n * scope's own normalisation). */\n private qualityLadders(view: Map<string, Entry>): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of view) {\n for (const [n, d] of declsOf(e)) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(n, d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation. Aliases\n * apply as they do to `toEvalContext`, so a condition written against `@scene`\n * validates against the instance bag the engine names.\n */\n toSchema(opts?: AliasOptions): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.view(opts?.aliases)) {\n const decls = declsOf(e);\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const [n, d] of decls) m.set(n, {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes (foreign scopes are the game's, and the game\n * saves them), as bare bags keyed by token, plus any values still parked, so\n * a save taken before every engine has re-registered loses nothing. The\n * registry knows nothing about game saves: a game embeds this in its own. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n for (const [token, vals] of this.parked) out[token] = structuredClone(vals);\n return out;\n }\n\n /**\n * Restore from a `save` blob. An owned scope lays its section over its current\n * values (the bag's `load` rule). A section for a key nobody has registered\n * yet is PARKED and handed over when that key registers, so a game can load\n * its registry before its engines have reopened their flows or decks. A\n * section for a foreign scope is ignored: those values are the game's.\n *\n * A load replaces whatever was parked before it: it is a whole restore, and\n * residue from an earlier load must not leak into this one.\n *\n * Changed in 0.7.0: sections for unregistered keys used to be dropped.\n */\n load(blob: Record<string, Record<string, ScalarValue>>, opts?: LoadOptions): void {\n if (!opts?.keepParked) this.parked.clear();\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") e.bag.load(vals);\n else if (!e) this.parked.set(token, structuredClone(vals));\n }\n }\n\n /**\n * `save()` wrapped with a version stamp.\n *\n * @deprecated Versioning belongs to the save that embeds the values; no\n * engine ever called this. Embed `save()` in your own versioned save.\n * Removed at the next breaking release.\n */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /**\n * Restore from a versioned fragment; an unsupported version throws.\n *\n * @deprecated See `saveFragment`. Removed at the next breaking release.\n */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n /**\n * A token is taken once. There is no reserved-token list: a clash surfaces\n * here, the moment a game combines its engines, which is the only moment\n * anyone knows which engines are present. With owners recorded the error says\n * whose token it already is.\n */\n private assertFree(token: string, owner?: string): void {\n const e = this.scopes.get(token);\n if (!e) return;\n const by = e.owner !== undefined ? ` by ${e.owner}` : \"\";\n const wants = owner !== undefined ? ` (wanted by ${owner})` : \"\";\n throw new Error(`scope '@${token}' is already registered${by}${wants}`);\n }\n}\n\n/** A scope entry's declarations, keyed by its own normalisation. */\nfunction declsOf(e: Entry): [string, ScopeDeclaration][] {\n if (e.kind === \"foreign\") return [...e.decls.entries()];\n return e.bag.declarations().map((d) => [e.bag.normalise(d.name), d]);\n}\n\n/** A scope entry's name normalisation. */\nfunction normOf(e: Entry): (name: string) => string {\n return e.kind === \"foreign\" ? e.norm : (n) => e.bag.normalise(n);\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: Pick<ScopeDeclaration, \"type\" | \"default\" | \"values\" | \"stages\">): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle\n // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the\n // union does not have. Falling off the switch would seed `undefined`, which is not a\n // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the\n // guard and this one did not, which is the drift you only find by removing a duplicate.\n default: return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The state logger, which both product families had written twice each.\n// ---------------------------------------------------------------------------\nexport type {\n StateSnapshot, StateChange, LogMount, StateLoggerAdapter, StateLoggerOptions, StateLogger,\n} from \"./state-logger.js\";\nexport { createStateLogger, diffState } from \"./state-logger.js\";\n","// GENERATED - vendored from expr/family/engine-scopes.json by scripts/sync-conformance.mjs.\n// Do not edit here; edit the shared list and re-run the script.\n\n/** Every engine's game-wide scope token across the family. A compiler accepts every token here\n * that is not its own engine's, unchecked: the other engine owns those names and types. */\nexport interface EngineScope {\n token: string;\n engine: string;\n means: string;\n}\n\nexport const ENGINE_SCOPES: readonly EngineScope[] = [\n { token: \"patter\", engine: \"Patterplay\", means: \"Patter's shared globals\" },\n { token: \"story\", engine: \"Storylet Engine\", means: \"the Storylet Engine's shared @story properties\" },\n];\n","// ---------------------------------------------------------------------------\n// @patterkit/dialect - the Patter configuration of @wildwinter/expr.\n//\n// Provides the Patter `Dialect` (scopes + built-in functions) and a helper to\n// build an `ExpressionSchema` from Patter property declarations (for static\n// validation). Shared by the compiler (parse + validate) and the runtime\n// (evaluate), so it depends only on @wildwinter/expr at runtime (plus a\n// type-only import from @wildwinter/scoperegistry for the foreign-scope spec).\n//\n// Scope tokens: just two - `patter` (global, the default; bare `@name`) and\n// `scene` (scene-local). The default global token is `@patter` (decision in\n// design/scope-registry.md §10.1; renamed from the earlier provisional `@shared`).\n// SHARING is an orthogonal per-property axis (PropertyDecl.shared), NOT a scope\n// token: a flow-private global is `@patter` declared `shared:false`; a shared\n// scene prop is `@scene` declared `shared:true`. (This mirrors Storylet Studio's\n// `@world`/`@site` + shared-flag paradigm - the two tools share one model.)\n// FUTURE (design/scope-registry.md): this static scope list becomes a *registry*\n// (engine-owned + host/foreign tokens).\n// ---------------------------------------------------------------------------\n\nimport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport type { EngineScope } from \"./engine-scopes.js\";\nimport type {\n Dialect, EvalHelpers, ExprNode, ScalarValue,\n ExpressionSchema, PropertyType as ExprPropertyType,\n} from \"@wildwinter/expr\";\nimport { EvalError } from \"@wildwinter/expr\";\nimport type { ScopeRegistrySpec } from \"@wildwinter/scoperegistry\";\nimport type { ProjectFile, PropertyDecl, HostScopeRegistry } from \"@patterkit/model\";\n\ninterface PatterHost {\n /** Next float in [0, 1) from the seeded PRNG (for `random`). */\n nextRandom?: () => number;\n /** Times the current flow has entered a node (for `visits` / `seen`). */\n visits?: (id: string) => number;\n /** Times any flow has entered a node, world-wide (for `patter_visits` / `patter_seen`). */\n patterVisits?: (id: string) => number;\n}\n\nfunction host(h: EvalHelpers): PatterHost {\n return (h.ctx.host ?? {}) as PatterHost;\n}\n\n/** The Patter dialect: scopes patter/scene + built-in functions. */\nexport const patterDialect: Dialect = {\n defaultScope: \"patter\",\n scopes: [\n { token: \"patter\" }, // global / world state (graceful-false on miss); bare @name\n { token: \"scene\" }, // scene-local (per-flow or shared, per the property's `shared` flag)\n ],\n functions: {\n random: {\n minArgs: 2, maxArgs: 2, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n if (args.length !== 2) throw new EvalError(\"random(a, b) requires exactly 2 arguments\");\n const next = host(h).nextRandom;\n if (!next) throw new EvalError(\"random() called without a PRNG in context\");\n const a = h.evaluate(args[0]!), b = h.evaluate(args[1]!);\n if (typeof a !== \"number\" || typeof b !== \"number\") throw new EvalError(\"random(a, b) arguments must be numbers\");\n if (!Number.isInteger(a) || !Number.isInteger(b)) throw new EvalError(\"random(a, b) arguments must be integers\");\n const lo = Math.min(a, b), hi = Math.max(a, b);\n return Math.floor(next() * (hi - lo + 1)) + lo;\n },\n },\n check_flags: {\n minArgs: 1, returnType: \"boolean\", flagDeltaArgs: true,\n validate: flagsCall(\"check_flags\"),\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const flags = readFlags(args[0], h, \"check_flags\");\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") throw new EvalError(\"check_flags() flag args must be +flagName or -flagName\");\n if (arg.sign === \"+\" ? !flags.includes(arg.name) : flags.includes(arg.name)) return false;\n }\n return true;\n },\n },\n set_flags: {\n minArgs: 1, returnType: \"flags\", flagDeltaArgs: true,\n validate: flagsCall(\"set_flags\"),\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const result = [...readFlags(args[0], h, \"set_flags\")];\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") throw new EvalError(\"set_flags() flag args must be +flagName or -flagName\");\n if (arg.sign === \"+\") { if (!result.includes(arg.name)) result.push(arg.name); }\n else { const idx = result.indexOf(arg.name); if (idx >= 0) result.splice(idx, 1); }\n }\n return result;\n },\n },\n // Visit counts (spec §7): how many times a scene / block / node has been\n // *entered*. `visits` / `seen` are this flow's count (flow-local); the\n // `patter_` variants are the world-wide (@patter / shared) count. The arg is a\n // node id - the same id a jump targets.\n visits: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n validate: idArg(\"visits\"),\n eval: (args, h) => host(h).visits?.(nodeId(args, h, \"visits\")) ?? 0,\n },\n seen: {\n minArgs: 1, maxArgs: 1, returnType: \"boolean\",\n validate: idArg(\"seen\"),\n eval: (args, h) => (host(h).visits?.(nodeId(args, h, \"seen\")) ?? 0) > 0,\n },\n patter_visits: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n validate: idArg(\"patter_visits\"),\n eval: (args, h) => host(h).patterVisits?.(nodeId(args, h, \"patter_visits\")) ?? 0,\n },\n patter_seen: {\n minArgs: 1, maxArgs: 1, returnType: \"boolean\",\n validate: idArg(\"patter_seen\"),\n eval: (args, h) => (host(h).patterVisits?.(nodeId(args, h, \"patter_seen\")) ?? 0) > 0,\n },\n },\n};\n\n/**\n * Other engines' game-wide scopes a Patter expression may name, from the family's\n * shared list (expr/family/engine-scopes.json): `@story.act` reads the Storylet\n * Engine's shared @story in a game that runs both. On by default, with no project\n * setting, so combining the engines needs no wiring. They are opaque here (the\n * other engine owns their names and types), never self-backed, and the engine\n * reports at run time when the game has not registered one.\n */\nexport const EXTERNAL_SCOPES: readonly string[] = ENGINE_SCOPES\n .map((s) => s.token)\n .filter((t) => t !== \"patter\" && t !== \"scene\");\n\n/**\n * A foreign-scope spec with the family's other engines added as OPAQUE scopes\n * (no declarations), unless the spec already declares them: a project that\n * imported the Storylet Engine's spec keeps its checked declarations. For\n * compiling and validating only; a bundle's own scopeRegistry never carries\n * these, because the runtime self-backs what it lists.\n */\nexport function withEngineScopes(spec?: ScopeRegistrySpec): ScopeRegistrySpec {\n const present = new Set((spec?.scopes ?? []).map((s) => s.token));\n const extra = EXTERNAL_SCOPES.filter((t) => !present.has(t)).map((token) => ({ token }));\n return { version: spec?.version ?? 1, scopes: [...(spec?.scopes ?? []), ...extra] };\n}\n\n/**\n * A Patter dialect extended with FOREIGN scope tokens imported from another\n * owner's `scopeRegistrySpec` (e.g. a storylet's `@world` / `@player` / `@system`).\n * The parser needs every referenced scope token registered, so authoring tools\n * that allow cross-engine references must compile/validate with this dialect.\n * Foreign scopes use the default missing policy (graceful-false). With no spec\n * (or an empty one) this returns the base `patterDialect` unchanged.\n */\nexport function dialectWithForeignScopes(given?: ScopeRegistrySpec): Dialect {\n // The family's other engines are always in: a Patter line can name `@story`.\n const spec = withEngineScopes(given);\n if (spec.scopes.length === 0) return patterDialect;\n const known = new Set(patterDialect.scopes.map((s) => s.token));\n const extra = spec.scopes\n .filter((s) => !known.has(s.token))\n .map((s) => ({ token: s.token }));\n if (extra.length === 0) return patterDialect;\n return { ...patterDialect, scopes: [...patterDialect.scopes, ...extra] };\n}\n\n/**\n * Split a property ref (\"@name\" / \"@scope.name\") into scope + name - THE one\n * ref grammar, shared by the compiler's validators and the runtime so they\n * cannot drift. `isScope` says which tokens are scopes in the caller's context\n * (dialect tokens, plus any foreign tokens); anything else - including a bare\n * `@name` and a dotted name whose head is not a scope - is a `patter` property.\n */\nexport function splitRef(ref: string, isScope: (token: string) => boolean): { scope: string; name: string } {\n const parts = ref.replace(/^@/, \"\").split(\".\");\n if (parts.length === 2 && isScope(parts[0]!)) {\n return { scope: parts[0]!, name: parts[1]!.toLowerCase() };\n }\n return { scope: \"patter\", name: parts.join(\".\").toLowerCase() };\n}\n\n/** Evaluate a visit-function's single argument to the node id (a string). */\nfunction nodeId(args: ExprNode[], h: EvalHelpers, fn: string): string {\n const v = h.evaluate(args[0]!);\n if (typeof v !== \"string\") throw new EvalError(`${fn}(id) requires a string node id`);\n return v;\n}\n\n/** Validate that a visit function's argument is a string id literal. */\nfunction idArg(fnName: string) {\n return (args: ExprNode[], h: import(\"@wildwinter/expr\").ValidateHelpers): void => {\n const first = args[0];\n if (first && first.kind !== \"string\") {\n h.report({ path: [...h.path, \"args\", 0], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(id): the argument must be a string id literal (a scene / block / node id)` });\n }\n };\n}\n\nfunction readFlags(arg: ExprNode | undefined, h: EvalHelpers, fn: string): string[] {\n if (!arg) throw new EvalError(`${fn}() requires at least one argument (the flags variable)`);\n const v = h.evaluate(arg);\n if (Array.isArray(v)) return v as string[];\n if (v === false || v === null || v === undefined) return []; // empty flags\n throw new EvalError(`${fn}() first argument must be a flags property`);\n}\n\n// Validation for the flags functions (first arg a flags property; deltas declared).\nfunction flagsCall(fnName: string) {\n return (args: ExprNode[], h: import(\"@wildwinter/expr\").ValidateHelpers): void => {\n if (args.length === 0) return;\n const first = args[0]!;\n if (first.kind !== \"scopedvar\") {\n h.report({ path: [...h.path, \"args\", 0], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(): first argument must be a flags property reference (@name or @scope.name)` });\n return;\n }\n const meta = h.schema.properties.get(first.scope)?.get(first.name);\n if (meta && meta.type !== \"flags\") {\n const ref = first.scope === h.defaultScope ? first.name : `${first.scope}.${first.name}`;\n h.report({ path: [...h.path, \"args\", 0], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(): '@${ref}' is not a flags property (got ${meta.type})` });\n return;\n }\n // The +flag/-flag SHAPE check is independent of whether the property is\n // declared; only the flag-NAME check needs the declaration's value list.\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") {\n h.report({ path: [...h.path, \"args\", i], kind: \"wrong-arg-type\", severity: \"error\",\n message: `${fnName}(): argument ${i + 1} must be +flagName or -flagName` });\n } else if (meta?.type === \"flags\" && meta.enumValues && !meta.enumValues.includes(arg.name)) {\n h.report({ path: [...h.path, \"args\", i], kind: \"unknown-flag-name\", severity: \"error\",\n message: `${fnName}(): unknown flag '${arg.name}'`, reference: arg.name });\n }\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// ExpressionSchema from property declarations (provisional scope mapping).\n// ---------------------------------------------------------------------------\n\n/**\n * Project the model's host-scope registry (`@world`, ...) onto the\n * `scopeRegistrySpec` the compiler / validator consume. Patter's property types\n * are now the same vocabulary as expr's, so this is a structural pass-through\n * that drops authoring-only fields (`purpose`) and omits unset `writable`;\n * returns undefined for an absent registry so callers can pass it straight through.\n */\nexport function hostScopesToSpec(reg?: HostScopeRegistry): ScopeRegistrySpec | undefined {\n if (!reg) return undefined;\n return {\n version: reg.version,\n scopes: reg.scopes.map((s) => ({\n token: s.token,\n ...(s.writable === false ? { writable: false } : {}),\n ...(s.declarations\n ? {\n declarations: s.declarations.map((d) => ({\n name: d.name,\n type: d.type,\n ...(d.values ? { values: d.values } : {}),\n ...(d.stages ? { stages: d.stages } : {}),\n ...(d.default !== undefined ? { default: d.default } : {}),\n ...(d.writable === false ? { writable: false } : {}),\n })),\n }\n : {}),\n })),\n };\n}\n\n/**\n * Build an ExpressionSchema for validating a scene's expressions: global\n * properties (`@patter`) plus that scene's scene-local properties (`@scene`),\n * plus any FOREIGN scopes imported from another owner's `scopeRegistrySpec`.\n * Scope mapping:\n * global (project.properties) -> @patter\n * scene-local (scene.sceneProps)-> @scene\n * foreign (spec) -> its own token (e.g. @world)\n * The `shared` flag is orthogonal - it does not affect validation (both shared\n * and not-shared globals are `@patter`; both scene props are `@scene`). A foreign\n * scope with no declarations is left opaque (omitted) - matching `ScopeRegistry.toSchema`.\n */\nexport function buildSchema(\n project: ProjectFile,\n sceneProps: PropertyDecl[] = [],\n foreign?: ScopeRegistrySpec,\n): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: ExprPropertyType; enumValues?: string[]; stages?: string[] }>>();\n const put = (scope: string, decl: PropertyDecl): void => {\n let m = properties.get(scope);\n if (!m) { m = new Map(); properties.set(scope, m); }\n m.set(decl.name.toLowerCase(), { type: decl.type, enumValues: decl.values, stages: decl.stages });\n };\n for (const decl of project.properties ?? []) put(\"patter\", decl);\n for (const decl of sceneProps) put(\"scene\", decl);\n const known = new Set(patterDialect.scopes.map((s) => s.token));\n for (const scope of foreign?.scopes ?? []) {\n if (known.has(scope.token)) continue; // never let a foreign spec shadow patter/scene\n if (!scope.declarations?.length) continue; // opaque scope\n const m = new Map<string, { type: ExprPropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of scope.declarations) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values, stages: d.stages });\n properties.set(scope.token, m);\n }\n return { properties };\n}\n\n// ---------------------------------------------------------------------------\n// Inline interpolation (spec §16): `{@ref}` slots inside localised strings.\n//\n// Committed surface = a BARE property reference only - `{@name}`, `{@patter.x}`,\n// `{@scene.y}` - resolved to its value and rendered as text. Full expressions /\n// ICU-style formatting are deferred (the `{ ... }` delimiter leaves room). Only\n// `{ ... }` whose trimmed body starts with `@` is a slot, so JSON-ish `{foo}`\n// stays literal. Braces are escaped by doubling: `{{` -> literal `{` and `}}` ->\n// literal `}`, so `{{@name}}` renders the text `{@name}` rather than expanding.\n// ---------------------------------------------------------------------------\n\n/** A `{ ... }` candidate whose trimmed body starts with `@`. */\nexport interface Slot {\n /** The whole matched text including braces, e.g. \"{@gold}\". */\n raw: string;\n /** The trimmed inner body, e.g. \"@gold\" or (malformed) \"@gold + 1\". */\n inner: string;\n /** The bare property ref (\"@gold\") when well-formed; undefined if malformed. */\n ref?: string;\n}\n\nconst BARE_REF = /^@[A-Za-z0-9_.]+$/;\n\ntype Token =\n | { kind: \"text\"; value: string }\n | ({ kind: \"slot\" } & Slot);\n\n/**\n * Tokenise a localised string into literal text and `{@ref}` slots, applying\n * brace-doubling escapes (`{{` -> `{`, `}}` -> `}`). The single source of truth\n * for both `interpolate` (runtime) and `extractSlots` (validator), so they can't\n * disagree on what counts as a slot. A `{ ... }` whose trimmed body does not\n * start with `@` is literal (kept verbatim, braces included).\n */\nfunction* tokenise(text: string): Generator<Token> {\n let buf = \"\";\n let i = 0;\n while (i < text.length) {\n const c = text[i];\n if (c === \"{\" && text[i + 1] === \"{\") { buf += \"{\"; i += 2; continue; }\n if (c === \"}\" && text[i + 1] === \"}\") { buf += \"}\"; i += 2; continue; }\n if (c === \"{\") {\n const close = text.indexOf(\"}\", i + 1);\n if (close !== -1) {\n const raw = text.slice(i, close + 1);\n const inner = text.slice(i + 1, close).trim();\n if (inner.startsWith(\"@\")) {\n if (buf) { yield { kind: \"text\", value: buf }; buf = \"\"; }\n yield { kind: \"slot\", raw, inner, ref: BARE_REF.test(inner) ? inner : undefined };\n i = close + 1;\n continue;\n }\n buf += raw; i = close + 1; continue; // not a slot -> literal braces\n }\n }\n buf += c; i += 1; // ordinary char (incl. an unclosed `{`)\n }\n if (buf) yield { kind: \"text\", value: buf };\n}\n\n/**\n * Find interpolation slots in a localised string. Only `{ ... }` whose trimmed\n * body begins with `@` is a slot; a slot whose body is not a bare ref is returned\n * with `ref` undefined (so the validator can flag it - the committed surface is a\n * bare property reference only). Escaped `{{ }}` braces are not slots.\n */\nexport function extractSlots(text: string): Slot[] {\n const out: Slot[] = [];\n for (const tok of tokenise(text)) {\n if (tok.kind === \"slot\") out.push({ raw: tok.raw, inner: tok.inner, ref: tok.ref });\n }\n return out;\n}\n\n/** Render a resolved slot value as display text. */\nexport function renderSlotValue(v: ScalarValue): string {\n if (Array.isArray(v)) return v.join(\", \");\n if (typeof v === \"boolean\") return v ? \"true\" : \"false\";\n return String(v);\n}\n\n/** ASCII whitespace for caption-collapse - a FIXED set (space, tab, newline, CR, form-feed, vtab) so\n * every Patterplay runtime collapses identically (a regex `\\s` would drift on Unicode across languages). */\nfunction isCaptionWs(c: string): boolean {\n return c === \" \" || c === \"\\t\" || c === \"\\n\" || c === \"\\r\" || c === \"\\f\" || c === \"\\v\";\n}\n\n/** Collapse every run of ASCII whitespace to a single space and trim both ends. Manual (no regex) so it\n * ports byte-for-byte to C# / C++ / GDScript. */\nfunction collapseCaptionWs(s: string): string {\n let out = \"\";\n let pendingSpace = false;\n for (const c of s) {\n if (isCaptionWs(c)) { pendingSpace = true; continue; }\n if (pendingSpace && out.length > 0) out += \" \";\n pendingSpace = false;\n out += c;\n }\n return out;\n}\n\n/**\n * Closed-caption stripping (#214): with captions OFF, remove every `open`…`close` span (delimiters\n * included) from a dialogue line and collapse the surrounding whitespace -\n * `Oh dear. (sigh) What now?` -> `Oh dear. What now?`. A string that contains NO cue is returned\n * unchanged (its original whitespace preserved); only a string we actually edited is whitespace-\n * normalised. `open` and `close` may be the same token (e.g. `*…*`); an unclosed `open` keeps the\n * remainder verbatim. An empty `open` is a no-op. Identical across every Patterplay runtime - part of\n * the conformance contract.\n */\nexport function stripCaptions(text: string, open: string, close: string): string {\n if (open.length === 0 || text.indexOf(open) < 0) return text; // disabled / no cue: fast path, unchanged\n let out = \"\";\n let i = 0;\n let removed = false;\n while (i < text.length) {\n if (text.startsWith(open, i)) {\n const end = text.indexOf(close, i + open.length);\n if (end >= 0) { i = end + close.length; removed = true; continue; } // skip the whole span\n out += text.slice(i); // unclosed cue -> keep the rest literally\n break;\n }\n out += text[i];\n i += 1;\n }\n return removed ? collapseCaptionWs(out) : text;\n}\n\n/**\n * Expand `{@ref}` slots in a string using `resolve` (a property lookup).\n * Well-formed slots become their rendered value (undefined -> empty string);\n * malformed slots and non-slot braces are left verbatim; `{{`/`}}` unescape to\n * literal `{`/`}`.\n */\nexport function interpolate(text: string, resolve: (ref: string) => ScalarValue | undefined): string {\n if (text.indexOf(\"{\") < 0) return text; // fast path: no slot opener -> nothing to interpolate (the common case)\n let out = \"\";\n for (const tok of tokenise(text)) {\n if (tok.kind === \"text\") { out += tok.value; continue; }\n if (!tok.ref) { out += tok.raw; continue; } // malformed slot -> verbatim\n const v = resolve(tok.ref);\n out += v === undefined ? \"\" : renderSlotValue(v);\n }\n return out;\n}\n","// ---------------------------------------------------------------------------\n// @patterkit/model - Patter data-model types (the shape source-of-truth).\n//\n// Faithful to the on-disk schema spec. Covers the SOURCE format - the flow tree\n// (scene/block/group/snippet/beat/jump), the project file, locale files, the\n// authoring file - and, at the bottom, the EXPORT BUNDLE types (schema §10).\n// Save / runtime-state types (schema §9) live with the runtime.\n//\n// All conditions and effect expressions are stored as `src` strings here (no\n// AST in source); see @wildwinter/expr for the expression language.\n// ---------------------------------------------------------------------------\n\nimport type { AstNode } from \"@wildwinter/expr\";\n\nexport type ScalarValue = boolean | number | string | string[];\n\n/** Developer-defined host metadata (spec §17). Opaque to Patter. */\nexport type GameData = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Effects (spec §15) - state mutation at snippet seams. SET-ONLY: an effect is a property\n// mutation and nothing else. Host event emission is NOT an effect - it rides on gameData\n// (snippet- or beat-level), see spec §15 \"Host calls\".\n// ---------------------------------------------------------------------------\n\nexport type Effect =\n // property mutation: assign `target` (a ref \"@name\" / \"@scope.name\") the result of `value`.\n { kind: \"set\"; target: string; value: string };\n\n// ---------------------------------------------------------------------------\n// Jumps (spec §3) - a snippet's optional routing action.\n// ---------------------------------------------------------------------------\n\n/** A scene/block id, or the reserved \"END\". */\nexport type JumpTarget = string;\n\nexport interface Jump {\n to: JumpTarget;\n /** \"jump\" (one-way, default) or \"call\" (jump-and-return via the flow callstack). */\n mode?: \"jump\" | \"call\";\n}\n\n// ---------------------------------------------------------------------------\n// Beats (spec §2) - the atomic content units inside a snippet.\n// ---------------------------------------------------------------------------\n\n// A scene holds any mix of the three kinds (spec §2): spoken dialogue, prose\n// narration, and engine instructions.\n\nexport interface LineBeat {\n id: string;\n kind: \"line\";\n /** Speaker; must be a member of the project cast (validated). */\n character?: string;\n /** Performance direction, language-neutral (never localised). */\n direction?: string;\n gameData?: GameData;\n /** Author-defined freeform tags (#215): a cross-cutting label layer that travels to the runtime.\n * At runtime a beat's tags are the UNION of its own and every ancestor's (scene → block → group(s) →\n * snippet → beat). Each tag is letters/digits/symbols with NO comma and NO whitespace; deduped. */\n tags?: string[];\n}\n\n/**\n * Authorial voice / narration - speaker-less prose (spec §2). Never voiced; its\n * localised text always permits inline `{@name}` interpolation (spec §16). This\n * is the on-screen-text role (e.g. \"A door slams!\") - distinct from a game-event\n * beat, which is a pure engine instruction with no localised content.\n */\nexport interface TextBeat {\n id: string;\n kind: \"text\";\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n}\n\n/**\n * A GAME EVENT (spec §2): a pure engine instruction with no player-facing text - just `gameData` the host\n * reads when the beat plays (comments / docs attach via the authoring file by id). Named \"game event\"\n * rather than \"action\" because a screenplay's \"action\" is prose, which is our text beat. Player-facing\n * words are a line or text beat instead.\n */\nexport interface GameEventBeat {\n id: string;\n kind: \"gameEvent\";\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n}\n\nexport type Beat = LineBeat | TextBeat | GameEventBeat;\n\n// ---------------------------------------------------------------------------\n// Selectable nodes - Group and Snippet (spec §2, §4).\n// ---------------------------------------------------------------------------\n\nexport type Selector =\n | \"run\" | \"branch\" | \"sequence\" | \"choice\";\n\n/** How a `sequence` walks its children. `specificity` = **Best match**: pick the eligible\n * child whose condition most specifically fits the current state (the most atomic constraints\n * actively holding it true); equally-specific ties break by the seeded shuffle. A child with no\n * condition scores zero, so it acts as the filler that wins only when nothing more specific is\n * eligible. Composes with `exhaust`: `repeat` re-scores every visit (re-pickable, the Best-match\n * default), `once` uses each pick up so the group slides down to the filler (graceful degradation). */\nexport type SelectorOrder = \"sequential\" | \"shuffle\" | \"specificity\";\n/** What a `sequence` does after one full pass through its children. */\nexport type SelectorExhaust = \"once\" | \"repeat\" | \"stick\";\n\n/**\n * `sequence` selector config (spec §4). One stateful picker with two orthogonal\n * axes subsumes Ink's stopping / cycle / once / shuffle and their combinations.\n * Defaults: `order: \"sequential\"`, `exhaust: \"once\"`. `shuffle` draws without\n * replacement and never repeats a line back-to-back (built in).\n */\nexport interface SequenceOptions {\n order?: SelectorOrder;\n exhaust?: SelectorExhaust;\n}\n\nexport interface Snippet {\n id: string;\n type: \"snippet\";\n /** Eligibility (and the conditional-jump test). Expression src. */\n condition?: string;\n /** Zero or more beats; zero beats + a jump = a pure jump (spec §3). */\n beats?: Beat[];\n onEnter?: Effect[];\n onExit?: Effect[];\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n /** Optional routing action; fires after `beats`. */\n jump?: Jump;\n // Choice-option field (only meaningful when this snippet is a bare `choice` child,\n // the runtime-tolerance shape, spec §5): the option's prompt is its first content line.\n /** Omit this option entirely when its condition fails (default: show greyed). */\n secretUntilEligible?: boolean;\n /** Repeatable choice option (spec §5). `true`: always offered while its condition passes (Ink `+`).\n * Default `false`: once-only - after the player follows it once it is gone from `getChoices`\n * entirely (not delivered, not flagged unavailable - just absent; Ink `*`). */\n sticky?: boolean;\n /** The choice's fallback option (spec §5). Never delivered as a normal option; auto-followed the\n * moment it is the ONLY eligible option left (its own condition still applies). At most one per\n * choice. Default `false`. */\n fallback?: boolean;\n}\n\n/** An option's prompt beat (spec §5): a single line | text beat - the choice text. */\nexport type PromptBeat = LineBeat | TextBeat;\n\nexport interface Group {\n id: string;\n type: \"group\";\n condition?: string;\n /** How the group's children are walked. Default (omitted) = `\"run\"`: play them in order. */\n selector?: Selector;\n /**\n * For the memoried `sequence` selector (spec §7): is the selector's cursor\n * SHARED across all flows (one cursor world-wide - e.g. two NPCs never draw the\n * same shuffled line) or kept per-flow? Default `false` (per-flow). Orthogonal to\n * a property's `shared` flag; same name, same idea.\n */\n shared?: boolean;\n /** `sequence` config (order × exhaust, spec §4). */\n options?: SequenceOptions;\n children: Array<Group | Snippet>;\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n // Option-position fields (spec §5): valid ONLY when this group is a direct child\n // of a `choice` group - where it is an OPTION whose `children` are the option's\n // content run. Validator-enforced; never carried by a normal group.\n /** The choice text (spec §5): a single line | text beat, always present on an\n * authored option. IS the host's choice text - no derivation, no look-ahead. */\n prompt?: PromptBeat;\n /** Keep this option out of `getChoices()` while ineligible (secrecy). Default false. */\n secretUntilEligible?: boolean;\n /** Repeatable choice option (spec §5). `true`: always offered while its condition passes (Ink `+`).\n * Default `false`: once-only - after the player follows it once it is gone from `getChoices`\n * entirely (not delivered, not flagged unavailable - just absent; Ink `*`). */\n sticky?: boolean;\n /** The choice's fallback option (spec §5). Never delivered as a normal option; auto-followed the\n * moment it is the ONLY eligible option left (its own condition still applies). At most one per\n * choice. Default `false`. */\n fallback?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Addressable nodes - Block and Scene (spec §2).\n// ---------------------------------------------------------------------------\n\nexport interface Block {\n id: string;\n type: \"block\";\n /** Mandatory, author-editable (jump targets must show a readable destination). */\n name: string;\n /**\n * The author-editable, host-facing ADDRESS (spec §6) - a readable slug the runtime targets\n * (\"play this block\"), distinct from the opaque immutable `id` (the internal join key). Absent =\n * derived from `name` (`effectiveGameId`); present = author-pinned (survives renames). Unique\n * within its scene (scene-scoped addressing). Hyphen-slug form (see core `gameIdify`).\n */\n gameId?: string;\n children: Array<Group | Snippet>;\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n}\n\nexport interface Scene {\n id: string;\n type: \"scene\";\n name: string;\n /**\n * The author-editable, host-facing ADDRESS (spec §6) - a readable slug the runtime targets\n * (\"play this scene\"), distinct from the opaque immutable `id`. Absent = derived from `name`\n * (`effectiveGameId`); present = author-pinned. Unique project-wide. Hyphen-slug form (core `gameIdify`).\n */\n gameId?: string;\n /** Host metadata - e.g. `location` lives here, not as a core field. */\n gameData?: GameData;\n /** Author tags (#215). See LineBeat.tags. */\n tags?: string[];\n /** Entry behaviour / setup (spec §15). */\n onEntry?: Effect[];\n /**\n * Scene-scoped property declarations - `@scene` (spec §7). Each may be marked\n * `shared` (one value across all flows in the scene) or not (per-flow, the\n * default); the reference is `@scene.name` either way.\n */\n sceneProps?: PropertyDecl[];\n /** One or more blocks; the first is the default entry. */\n blocks: Block[];\n}\n\n/** A flow file (.patterflow) - one scene. */\nexport interface FlowFile {\n schema: string; // \"patter/flow@0\"\n scene: Scene;\n}\n\nexport type FlowNode = Scene | Block | Group | Snippet;\n\n/**\n * Visit every selectable node (group / snippet) under a children list,\n * depth-first in authored order. Generic over the source AND compiled trees\n * (both share the group/snippet shape) - THE one tree walk; validators,\n * indexers, and exporters all route through it so no hand-rolled walker can\n * forget a level (or a string-bearing field on one).\n */\nexport function walkNodes<N extends { type: string }>(\n nodes: ReadonlyArray<N>,\n visit: (node: N) => void,\n): void {\n for (const node of nodes) {\n visit(node);\n // Groups carry children; snippets do not. Accessed structurally so the one\n // walker serves both the source and compiled trees.\n const children = (node as { children?: ReadonlyArray<N> }).children;\n if (children) walkNodes(children, visit);\n }\n}\n\n/**\n * A beat with NO content worth keeping: an empty line / text bubble left behind - e.g. a snippet seeded\n * only so a jump could hang off it. Such a beat would render at runtime as a blank line that (lacking any\n * localised string) falls back to emitting its raw id, so the editor drops it on save. `hasText` = the\n * beat has a non-empty display string. A game-event beat is never contentless (it's a pure instruction),\n * and a beat carrying `gameData` or `tags` is meaningful even with no text. A jump-only snippet is then\n * just `{ jump }` with zero beats - which is valid.\n */\nexport function isContentlessBeat(beat: Beat, hasText: boolean): boolean {\n if (beat.kind === \"gameEvent\") return false;\n if (hasText) return false;\n if (beat.gameData && Object.keys(beat.gameData).length > 0) return false;\n if (beat.tags && beat.tags.length > 0) return false;\n return beat.kind === \"text\" || (!beat.character && !beat.direction);\n}\n\n// ---------------------------------------------------------------------------\n// Game IDs (spec §6) - the author-editable, host-facing ADDRESS for an\n// addressable node (scene / block): a hyphen-slug the runtime targets, distinct\n// from the opaque immutable `id` and the computed readable `handle`. These pure\n// helpers live in the shape layer so the runtime can compute effective addresses\n// without depending on @patterkit/core (which re-exports them).\n// ---------------------------------------------------------------------------\n\n/** Slugify a name into a hyphen-form game id: lowercase, drop apostrophes, runs of\n * other punctuation -> a single hyphen, collapse repeats, no leading / trailing hyphen. */\nexport function gameIdify(text: string): string {\n return text\n .toLowerCase()\n .replace(/['’]/g, \"\")\n .replace(/[^a-z0-9-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/** A valid authored game id: lowercase alphanumerics + hyphens, no leading / trailing hyphen. */\nexport function isValidGameId(gameId: string): boolean {\n return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);\n}\n\n// ---------------------------------------------------------------------------\n// Property names (design: `@wildwinter/app-shell` src/property-names.ts, which\n// holds the same rules as the shell's defaults and argues them).\n//\n// Not house style: the rule is what `@wildwinter/expr` can parse. Its lexer takes\n// an identifier as /[a-zA-Z_][a-zA-Z0-9_]*/ and folds it to lower case, so\n// `@patter.isNight` reaches a property called `isnight`, `@patter.9lives` and\n// `@patter.not` are parse errors, and `@patter.is-night` is not an error at all:\n// it compiles to `@patter.is` MINUS the string \"night\". That last one is why the\n// rule is worth enforcing rather than trusting - it is the only violation that\n// silently means something else.\n//\n// These live here, and not behind an import of the UI kit, because the compiler,\n// the CLI and the JS runtime embedded in game engines all resolve state by them.\n// `packages/model/test/property-name-parity.test.ts` holds them to the shell's.\n// ---------------------------------------------------------------------------\n\n/** The words `@wildwinter/expr` lexes as keywords, so no property may be called one.\n * `packages/model/test/property-name-grammar.test.ts` probes the real parser rather\n * than trusting this copy. */\n// A legal property NAME is a fact about the expression language, not about this\n// model: `not` is reserved because the tokeniser reads it as an operator. Both\n// families kept their own copy of the rule AND of the keyword list, a list\n// neither owned. @wildwinter/expr derives the list from its own tokeniser, so a\n// keyword added there cannot leave a stale copy here.\n//\n// Re-exported so nothing that imports them has to move.\nexport {\n propertyNameify, isValidPropertyName, isCaseOnlyPropertyName, RESERVED_PROPERTY_NAMES,\n} from \"@wildwinter/expr\";\n/** The effective address: the explicit `gameId` if pinned, else derived from `name`. */\nexport function effectiveGameId(entity: { gameId?: string; name: string }): string {\n const g = entity.gameId?.trim();\n return g ? g : gameIdify(entity.name);\n}\n\n// ---------------------------------------------------------------------------\n// Project file (.patterproj) - spec §14, schema §6.\n// ---------------------------------------------------------------------------\n\nexport type PropertyType = \"boolean\" | \"number\" | \"string\" | \"flags\" | \"enum\" | \"quality\";\n\nexport interface PropertyDecl {\n name: string;\n type: PropertyType;\n default?: ScalarValue;\n /**\n * The orthogonal *sharing* axis (spec §7): is this property's value shared across\n * all flows (one world value) or kept per-flow? It does NOT change the reference\n * syntax - sharing is set here, on the declaration, not by a different scope token.\n * The default depends on the scope it is declared in: a **global** property\n * (project `properties` -> `@patter`) defaults to **shared**; a **scene-local**\n * property (scene `sceneProps` -> `@scene`) defaults to **not shared** (per-flow).\n */\n shared?: boolean;\n /**\n * Persistence axis, for **scene-local (`@scene`) properties** (spec §7). Default\n * `false`: the value PERSISTS across scene re-entries (like every other property).\n * `true`: the value is **reseeded to its default on every scene entry** - \"fresh\n * each playthrough\" (Ink's `temp`). Orthogonal to `shared`. Ignored on global\n * (`@patter`) properties, which always persist for the life of the piece.\n */\n temporary?: boolean;\n /** For enum / flags. */\n values?: string[];\n /** For quality: the ORDERED ladder of stage names (the shared engine's quality type, expr 0.4.0).\n * Ordering operators compare by position in this list and `advance()` steps along it, so the order\n * is the meaning - unlike `values`, whose order is presentation only. Default seeds to the first\n * stage when no `default` is declared. */\n stages?: string[];\n /** Free-text author note documenting what this property is for (authoring only; shown as a hint). */\n purpose?: string;\n}\n\n/**\n * A property of a host / world scope (`@world`, `@game`, ...). The same shape the\n * `@wildwinter/scoperegistry` `scopeRegistrySpec` uses, declared structurally here\n * so the model stays free of a runtime dependency. `default` seeds the standalone\n * runtime's self-backed bag (see `HostScopeSpec`); `writable: false` makes the\n * property read-only to the story (validated at compile time).\n */\nexport interface HostScopeDecl {\n name: string;\n type: PropertyType;\n values?: string[];\n /** For quality: the ordered stage ladder (see `PropertyDecl.stages`). */\n stages?: string[];\n default?: ScalarValue;\n writable?: boolean;\n /** Free-text author note documenting the property (authoring only; shown as a hint). */\n purpose?: string;\n}\n\n/** One scope (`token`) in a project's host-scope registry. */\nexport interface HostScopeSpec {\n /** The scope token after `@` (e.g. `\"world\"`). Must not collide with Patter's own\n * `patter` / `scene` / `flow`. */\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: HostScopeDecl[];\n}\n\n/**\n * A project's host-scope registry: the `scopeRegistrySpec` it OWNS (spec\n * design/scope-registry.md §6). Makes `@world` (and any host scope) first-class\n * in a standalone project: the compiler validates references into it, the runtime\n * self-backs it from declaration defaults when no host resolver claims the token,\n * and coverage drives its values. Structurally identical to scoperegistry's\n * `ScopeRegistrySpec` so it threads straight through the compiler.\n */\nexport interface HostScopeRegistry {\n version: number;\n scopes: HostScopeSpec[];\n}\n\n/**\n * A coverage input driver (#159, design/scope-registry.md §7): during a coverage run the harness feeds a\n * host-scope property (`@world.x`) values from `values` so branches gated on external state get exercised.\n * @world is the game-engine seam: @patter / @scene are story-owned and covered for free, so are never driven.\n */\nexport interface CoverageDriver {\n /** The host-scope ref to drive, e.g. `\"@world.phase\"`. */\n ref: string;\n /** `\"initial\"`: set once when each playthrough starts. `\"recurring\"`: re-rolled at choice points so a\n * single run can pass through several world states. */\n kind: \"initial\" | \"recurring\";\n /** For a recurring driver: how often to re-roll at a choice point (default `\"sometimes\"`). */\n cadence?: \"rarely\" | \"sometimes\" | \"often\";\n /** The pool the harness picks from (uniform). Empty = the driver is inert. */\n values: ScalarValue[];\n}\n\n/** A character's grammatical gender, for localisation. Translators need it to inflect the speaker's own\n * lines in gendered languages (adjectives, participles, pronouns), which the source text alone often\n * cannot tell them. Absent means \"not specified\". Authoring-only: it never reaches the runtime bundle,\n * but it IS carried into the localisation handoff formats as translator context (spec §14).\n *\n * Free text, not a closed set: real languages need more than three genders (common/utrum, animate/\n * inanimate, and so on) and translators name them differently. `COMMON_GENDERS` seeds the editor's\n * auto-suggest so the everyday values stay spelled consistently; anything else is still valid. */\nexport type GrammaticalGender = string;\n\n/** The everyday grammatical genders the Cast editor offers as auto-suggest defaults. Not exhaustive and\n * not enforced - a project may use any string (see `GrammaticalGender`). */\nexport const COMMON_GENDERS = [\"male\", \"female\", \"neuter\"] as const;\n\nexport interface CastMember {\n /** Canonical speaker name (matched by a beat's `character`); language-neutral key. */\n name: string;\n /** Localised player-facing name - a localisation id (project-level strings). */\n displayName?: string;\n /** Grammatical gender for translators (see `GrammaticalGender`); absent = not specified. */\n gender?: GrammaticalGender;\n /** Free-text production notes about the character (casting, voice, intent) - authoring only. */\n notes?: string;\n /** The voice actor cast for this character, if known - surfaced in the VO script export (spec §16).\n * Authoring-only (not shipped in the runtime bundle). */\n actor?: string;\n gameData?: GameData;\n}\n\n/** The cast as it reaches a compiled bundle: the player-facing fields only. `notes`, `actor` and\n * `gender` are authoring / production / translation context, and the compiler drops them, so a shipped\n * game never carries a real person's name or a writer's private notes. The compiler copies the shipping\n * fields across explicitly (an allow-list), which is what actually keeps a new authoring field out of\n * the bundle; this type states the resulting contract for anyone reading a bundle. */\nexport type BundleCastMember = Omit<CastMember, \"notes\" | \"actor\" | \"gender\">;\n\n/** A node TYPE that can carry author-defined gameData fields (the gameData schema, project-level).\n * Beat kinds map straight through: dialogue = `line`, narration = `text`, game event = `gameEvent`. */\nexport type GameDataNodeKind = \"scene\" | \"block\" | \"snippet\" | \"line\" | \"text\" | \"gameEvent\";\n\n/** The value type of a gameData field - the property-type vocabulary plus a multiline-text variant.\n * Drives the inspector's editor widget (text / textarea / number / toggle / enum dropdown). */\nexport type GameDataFieldType = \"text\" | \"multiline\" | \"number\" | \"boolean\" | \"enum\";\n\n/** One author-defined custom field on a node type (host-integration metadata, NOT expression state). */\nexport interface GameDataField {\n /** Field key - also the key a node stores its value under in `gameData` (values are name-keyed). */\n name: string;\n type: GameDataFieldType;\n /** The value used when a node sets nothing. Storage is SPARSE - nodes hold only their overrides, and\n * a reader falls back to this default (so changing it here propagates to every node that didn't set it). */\n default?: ScalarValue;\n /** Allowed values when `type` is \"enum\". */\n values?: string[];\n /** Free-text description of what the field is for - shown as a rollover hint in the inspector. */\n purpose?: string;\n}\n\n/** Author-defined gameData fields, grouped by the node type they attach to (project-level schema). */\nexport type GameDataFields = Partial<Record<GameDataNodeKind, GameDataField[]>>;\n\n// ---------------------------------------------------------------------------\n// Status vocabularies (spec §13) - ordered not-done -> done, project-tailorable.\n// Tracked PER BEAT in the authoring file (`writing` / `recording`).\n// ---------------------------------------------------------------------------\n\nexport interface WritingStatusDecl {\n name: string;\n /**\n * Threshold marker: this status AND every later one classify as \"ready to\n * record\". Declared on exactly one status in the list.\n */\n readyToRecord?: boolean;\n /** Threshold marker: this status and every later one classify as \"ready to ship\". */\n readyToShip?: boolean;\n /** Theme character-palette slot (0-11) for the per-line status badge / inspector swatch (Patterpad #196).\n * A slot (not a hex) so it adapts to light/dark + the colour themes. Absent = no colour (neutral). */\n colour?: number;\n}\n\n/**\n * The default writing-status ladder (used when a project declares none). The\n * FIRST status is the level-0 / absence status: a beat with no recorded status\n * counts as `stub`. Nothing is inferred from content - tracking is opt-in, and a\n * project that ignores statuses reads as all-stub (so its burndown / script\n * export carry no weight, by design - spec §13).\n */\nexport const DEFAULT_WRITING_STATUSES: WritingStatusDecl[] = [\n { name: \"stub\", colour: 0 }, // red\n { name: \"draft 1\", colour: 1 }, // rust\n { name: \"draft 2\", colour: 2 }, // ochre\n { name: \"edited\", readyToRecord: true, colour: 4 }, // green\n { name: \"final\", readyToShip: true, colour: 9 }, // purple (violet)\n];\n\n/** A recording-status rung (#206): like a writing status but with no readiness markers - the recording\n * ladder is ordered missing -> final and carries a picked palette colour for the inspector chip / report. */\nexport interface RecordingStatusDecl {\n name: string;\n /** Theme character-palette slot (0-11) for the inspector chip / report bar. Absent = neutral. */\n colour?: number;\n}\n\n/** The default recording-status ladder (used when a project declares none). The FIRST rung is the\n * level-0 / absence status: a beat with no recorded status counts as `missing`. */\nexport const DEFAULT_RECORDING_STATUSES: RecordingStatusDecl[] = [\n { name: \"missing\", colour: 0 }, // red\n { name: \"scratch\", colour: 2 }, // ochre\n { name: \"recorded\", colour: 4 }, // green\n { name: \"final\", colour: 9 }, // purple\n];\n\n/** The reserved recording status a line takes when it is flagged **needs re-record** (#227): a recorded\n * take exists but is unusable (bad quality, wrong take), so it must be redone. It is NOT a ladder rung -\n * it is a regression flag that MASKS the derived / manual rung for the recording script, the production\n * report, and status browse, so a line \"recorded\" on disk still shows up as work to do. Reserved so it\n * can't collide with an author-declared rung; carries its own alert colour. */\nexport const RERECORD_STATUS = \"rerecord\";\nexport const RERECORD_STATUS_DECL: RecordingStatusDecl = { name: RERECORD_STATUS, colour: 1 }; // orange alert\n\n/** One recording rung mapped to the folder its audio files live in (Audio Folders mode). */\nexport interface RecordingFolder {\n name: string;\n /** Project-relative folder `<audioRoot>/<slug(name)>`, or undefined for the baseline \"not recorded\" rung. */\n folder?: string;\n}\n\n/**\n * Audio Folders (#206): map the recording ladder to its derived folders under a single audio root.\n * Each rung's folder is `<audioRoot>/<slug(name)>`; the FIRST (lowest) rung is the \"not recorded\"\n * baseline and gets NO folder. The single source of truth shared by the indexer, scratch-save, and the\n * manifest so they never disagree. Returns bare rungs (no folders) when `audioRoot` is empty.\n */\nexport function deriveRecordingFolders(\n audioRoot: string | undefined,\n statuses: RecordingStatusDecl[],\n): RecordingFolder[] {\n const root = audioRoot?.trim();\n return statuses.map((s, i) => {\n if (i === 0 || !root) return { name: s.name }; // baseline rung, or no root configured yet\n return { name: s.name, folder: `${root}/${gameIdify(s.name)}` };\n });\n}\n\n/**\n * A class of documentation note (spec §18) - the project-defined vocabulary plus\n * where each class is DELIVERED. The editor (Patterpad) always shows every class;\n * `deliver` governs EXPORTS only. A note inherits down the tree, so a class set\n * on a scene/block flows to its lines (outermost-first).\n */\nexport interface DocumentationClass {\n name: string;\n /**\n * Export channels this class flows to: a list of channel names, or `\"*\"` for\n * all. Omitted/empty = editor-only (e.g. \"writing\"). Built-in channels: `vo`\n * (voice-recording scripts), `loc` (localisation handoff), `editor` (the editable\n * script handed to an outside editor); studios add their own (e.g. `sfx`, `art`),\n * carried now and picked up when that export exists.\n */\n deliver?: string[] | \"*\";\n}\n\n/**\n * The default documentation classes (used when a project declares none). An\n * untyped note (no class) is editor-only by construction; these are the named\n * routes. The vocabulary is project-extensible - a studio replaces this list.\n */\nexport const DEFAULT_DOCUMENTATION_CLASSES: DocumentationClass[] = [\n { name: \"everyone\", deliver: \"*\" }, // every export, and (like all) the editor\n { name: \"vo\", deliver: [\"vo\"] }, // voice-recording scripts\n { name: \"loc\", deliver: [\"loc\"] }, // localisation handoff\n { name: \"editor\", deliver: [\"editor\"] }, // the editable script handed to an outside editor\n];\n// (An editor-only note still exists: leave a note UNTYPED - it surfaces in the editor but no export.)\n\n/** The version-control system a project is kept under (spec §12). Drives the lock-aware vs merge-based\n * write path + the emitted VCS config; chosen at create and switchable in Project Settings. */\nexport type VcsKind = \"git\" | \"perforce\" | \"plastic\" | \"svn\" | \"none\";\n\n/** Spell-check setup for a project (Patterpad #177). */\nexport interface ProjectDictionary {\n /** The active dictionary id - a built-in language (\"en-US\"/\"en-GB\") or an app-level imported Hunspell\n * pair. Absent = derive from the source locale. The imported pair itself lives per-machine (userData),\n * so only the id travels with the project. */\n language?: string;\n /** The project's custom word list - names, places, invented terms - always accepted. Travels with the\n * project (shared via VCS), unlike the per-machine imported dictionaries. */\n words?: string[];\n /** Words the author chose to IGNORE (right-click ▸ Ignore on a flagged word). Distinct from `words`:\n * these aren't vocabulary to add, just tokens to stop flagging in this project (a dialect spelling, a\n * code). Both suppress the squiggle; they differ in intent + where they surface. Travels with the project. */\n ignore?: string[];\n /** Spell-check on/off for this project (default on). */\n enabled?: boolean;\n}\n\n/**\n * Estimating (writing-burndown, spec §13): when a scene is still all guesswork - every status-tracked\n * beat at or below `thresholdStatus` (an unset beat counts as the lowest rung) - the production report\n * REPLACES its actual (placeholder) line count with an estimate, and shares that estimate across the\n * characters appearing in its placeholder lines. Off by default; when off, no estimate appears anywhere.\n * See design/proposals/estimating.md.\n */\nexport interface EstimatingConfig {\n /** Master on/off. Off (or absent) = the report shows pure actuals, no estimate anywhere. */\n enabled: boolean;\n /** Writing-ladder rung NAME: a scene is estimated only when EVERY status-tracked beat is at or below\n * this rung. Absent = the lowest rung. */\n thresholdStatus?: string;\n /** The per-scene estimate (written lines) used when no tag override matches. */\n defaultLines: number;\n /** Tag -> lines overrides. A scene carrying a mapped tag uses that number; if it carries several, the\n * LARGEST wins. */\n tagEstimates?: { tag: string; lines: number }[];\n}\n\nexport interface ProjectFile {\n schema: string; // \"patter/project@0\"\n root?: boolean;\n project: { id: string; name: string; roomKey?: string };\n locales: { default: string; all: string[] };\n /** The authored entry point: the scene (and optional block) a flow starts at when none is given,\n * used by `patter play`, Patterpad's Play, and coverage. Omitted = fall back to the first scene. */\n start?: { scene: string; block?: string };\n /** The authored scene order (scene ids) for navigation. Scenes not listed follow in file order;\n * listed ids that no longer exist are ignored. Omitted = plain file order. Presentation only:\n * it never affects play, which always starts from `start` / an explicit address. */\n sceneOrder?: string[];\n /** Version-control system (spec §12); omitted = unset / none. */\n vcs?: VcsKind;\n /** Project-wide VO mode (spec §16) - one boolean, no per-scene override. */\n voiced?: boolean;\n /** Track audio/recording status (#206): whether the recording-status ladder, Audio Folders, scratch, and\n * the inspector's Audio row + the report's recording breakdown are active. Only meaningful for a `voiced`\n * project (it gates on both). Authoring metadata, editor-only; never reaches the bundle. Omitted = OFF -\n * opt-in even for a voiced project (a voiced story may want voice scripts without tracking recording status). */\n trackAudioStatus?: boolean;\n /**\n * Inline text formatting. When true, authors can mark dialogue / narration / direction /\n * choice-prompt text bold, italic, or bold+italic; it is stored INSIDE the localised strings\n * (and the flow's `direction`) as `<b>…</b>`, `<i>…</i>`, `<bi>…</bi>`, with literal `<`, `>`,\n * `&` escaped to `<` / `>` / `&`. The runtime treats the string as opaque - it is the\n * GAME's job to parse the tags. Default ON (omitted === enabled); set `false` to disable it for a\n * game that renders plain strings and would otherwise show the tags literally. */\n formatting?: boolean;\n /** Autosave: periodically persist the edited scene without an explicit Save. Default ON (omitted ===\n * enabled); set `false` to require manual saves. */\n autosave?: boolean;\n /** Auto Rebuild: recompile the `.patterc` bundle automatically after edits (debounced), so the on-disk\n * build stays current without a manual Publish Bundle. Editor-only; never reaches the bundle. Default OFF\n * (omitted === off) - opt-in, since it writes the bundle on every real change (poor fit if you commit the\n * bundle to a lock-based VCS). The rebuild is deduped (skipped when the compiled bundle is unchanged) and\n * a mid-edit invalid project silently keeps the last good build. */\n autoRebuild?: boolean;\n /** Closed captions (#214): the delimiter pair that wraps non-spoken caption cues inside DIALOGUE\n * lines (e.g. `(sigh)` in `Oh dear. (sigh) What now?`). A game can turn captions off at runtime\n * (`setClosedCaptions(false)`), and the runtime then strips every `open…close` span - delimiters and\n * surrounding whitespace - from line text. Baked into the bundle so the runtime knows the pair;\n * omitted = the default `(` / `)`. Captions are ON by default (full text shown). */\n closedCaptions?: CaptionDelimiters;\n audio?: { scratchStore: string };\n layout?: { flow?: string; strings?: string; authoring?: string };\n /** `bundle`: the compiled `.patterc` output path (relative to the project root,\n * or absolute); default `../patter-dist/<project-file-stem>.patterc`, beside the project (spec §11).\n * `localisation`: how strings ship + are resolved (spec §11).\n * - \"embedded\" (default): every locale's strings live INSIDE the `.patterc`; the runtime resolves\n * them and can switch locale live (`setLocale`).\n * - \"ids\": the `.patterc` carries NO strings; the runtime emits the beat ID for each line and the\n * game looks it up in its own loc system (Export Localisation hands over the language files).\n * `sourceDebug` embeds the SOURCE language too, purely so the build can be played for debugging;\n * the bundle flags it so the runtime can warn it is not a shippable build. */\n export?: { targets?: string[]; bundle?: string; localisation?: { mode: \"embedded\" | \"ids\"; sourceDebug?: boolean } };\n properties?: PropertyDecl[];\n /** Host / world scopes the project declares (`@world`, `@game`, ...): makes them first-class so the\n * compiler validates references into them, the runtime self-backs them from defaults when no host\n * resolver is bound, and coverage drives their values. Omitted = no host scopes (`@world.x` is then a\n * compile error). See design/scope-registry.md §6. */\n scopeRegistry?: HostScopeRegistry;\n /** Where the game's shared scopes folder is, relative to this project file's folder\n * (`\"../../shared/game-scopes\"`), for a folder the walk-up would not find. Omitted = walk up from the\n * project to the first `game-scopes/`, stopping at the version-control root; none found = the project\n * works alone. A path that doesn't exist is a project error. See the Patter site's World properties\n * page and patterkit/design/shared-scopes.md. Editor and compiler only; never reaches the bundle. */\n gameScopes?: string;\n /** Coverage input drivers (#159): values to feed host scopes (`@world`) during a coverage run so\n * externally-gated branches get exercised. Authoring-only (never reaches the runtime bundle). */\n coverageDrivers?: CoverageDriver[];\n cast?: CastMember[];\n gameDataFields?: GameDataFields;\n /** Ordered writing-status ladder (not-done -> done); default `DEFAULT_WRITING_STATUSES`. */\n writingStatuses?: WritingStatusDecl[];\n /** Ordered recording-status ladder (not-done -> done); default `DEFAULT_RECORDING_STATUSES`. */\n recordingStatuses?: RecordingStatusDecl[];\n /** Audio Folders mode (#206): the single project-relative root under which each rung's audio lives, in\n * an auto-derived subfolder `<audioRoot>/<slug(statusName)>/` (see `deriveRecordingFolders`). Only\n * meaningful when `audioFolders` is on. Authoring metadata; never reaches the bundle. */\n audioRoot?: string;\n /** Audio Folders mode (#206): when true, a dialogue line's recording status is DERIVED from which\n * rung's derived folder (under `audioRoot`) holds its `<beatId>.wav|mp3` (top-down the ladder, implicit\n * \"missing\"), instead of being set manually. Authoring metadata; never reaches the bundle. Default off. */\n audioFolders?: boolean;\n /** Scratch recording (Patterpad #224): the recording-status rung whose folder is the source/dest for\n * in-app \"record scratch\" takes. When set (and `audioFolders` on), Patterpad offers to record a quick\n * scratch take into this rung's folder for any line at or below this rung. Authoring metadata, editor-\n * only; never reaches the bundle. Unset = scratch recording off. */\n scratchStatus?: string;\n /** Spell-check setup (Patterpad #177): the active dictionary language + the project's custom word list +\n * an on/off flag. Source-language-only, authoring metadata - it never reaches the runtime bundle. */\n dictionary?: ProjectDictionary;\n /**\n * Estimating (spec §13 writing burndown): replace a still-guesswork scene's actual (placeholder)\n * line count with an estimate in the production report. Off by default. See `EstimatingConfig` and\n * design/proposals/estimating.md.\n */\n estimating?: EstimatingConfig;\n /** Documentation-note classes + their export routing (spec §18); default `DEFAULT_DOCUMENTATION_CLASSES`. */\n documentationClasses?: DocumentationClass[];\n}\n\n// ---------------------------------------------------------------------------\n// Locale file (.patterloc) - schema §4. String text only.\n// ---------------------------------------------------------------------------\n\nexport interface LocaleFile {\n schema: string; // \"patter/strings@0\"\n /** Scene id this file's strings belong to (or a project-level marker). */\n scene: string;\n locale: string;\n default?: boolean;\n /** beatId -> text. */\n strings: Record<string, string>;\n}\n\n/** The `scene` marker for a project-level loc shard (`loc/<locale>/_project.patterloc`): strings that\n * aren't tied to a scene beat - currently cast display names, later project title / UI strings. */\nexport const PROJECT_LOCALE_SCENE = \"@project\";\n\n/** The project-level loc-string key for a cast member's player-facing name, e.g. `cast:BARKEEP`.\n * Namespaced so it can't collide with opaque beat ids. The default-locale value is seeded from the\n * CastMember's `displayName`; the runtime resolves a character's shown name through this key. */\nexport function castStringKey(name: string): string {\n return `cast:${name}`;\n}\n\n// ---------------------------------------------------------------------------\n// Authoring file (.patterx) - schema §5. All edit/production metadata.\n// ---------------------------------------------------------------------------\n\n/** Typed documentation annotation line (spec §18). */\nexport interface DocLine {\n /** The documentation CLASS (a `DocumentationClass.name`) - routes export\n * visibility. Omitted = editor-only (not delivered to any export). */\n type?: string;\n text: string;\n}\n\n/** One message in a threaded editor comment: who wrote it, when (ISO timestamp), and the text. */\nexport interface CommentMessage {\n author: string;\n ts: string;\n body: string;\n /** A TOMBSTONE: the words are gone, the turn in the conversation is not.\n *\n * Removing a reply outright would renumber the argument around it, so what is\n * left records who spoke and when, and that they withdrew it. The `body` is\n * EMPTIED rather than kept and hidden, because \"deleted\" has to mean gone from\n * a file that lives in version control.\n *\n * This is the one message allowed an empty body: `saveSceneComments` prunes\n * empty messages so a cancelled composer leaves nothing behind, and without\n * this flag a tombstone would be pruned on the way to disk. */\n deleted?: boolean;\n}\n\n/** A sub-text range a comment is pinned to, within its anchor node's say text (#148). Offsets are\n * character positions over the rendered (plain) source text - inline formatting is marks, not part of\n * the count. `quote` is the text the range covered when made: the editor re-anchors by FINDING it (the\n * offsets are just a hint), and a thread whose quote no longer exists is shown demoted, not lost. */\nexport interface CommentRange {\n from: number;\n to: number;\n quote: string;\n}\n\n/** Threaded editor comment (collaboration), anchored to a stable beat/node id. `messages[0]` is the\n * opener; replies follow in order (each carries its own author + timestamp, Word/Docs style).\n * `range` pins it to a span of the node's text (absent = the whole beat). `resolved` archives the\n * thread - hidden in the editor unless \"show resolved comments\" is on. */\nexport interface Comment {\n id: string;\n /** Anchored to a stable beat/node id. */\n anchor: string;\n /** A sub-text span within the anchor's text; absent = a whole-beat comment. */\n range?: CommentRange;\n resolved?: boolean;\n messages: CommentMessage[];\n}\n\n/** A \"suggest a rewrite\" proposal for a single say/prose beat or option prompt (review flow,\n * design/proposals/suggest-rewrite.md). Whole-beat anchored: `baseline` is the say text WHEN suggested\n * (the \"before\" + the drift detector - if it no longer matches the live text, the line changed since and\n * the suggestion is shown stale), `proposed` is the replacement. Accept overwrites the beat's say text;\n * both accept and reject set `resolved` (archived) + `outcome` (a light audit trail). Stored in the\n * authoring shard, never in the flow text, so downstream tools ignore it.\n *\n * Beyond the text, a suggestion may also propose a new speaker, a new direction, or cutting the beat\n * (the editable-script handoff, design/proposals/editable-script-handoff.md). Each such part carries its\n * own baseline, so each is stale-checked on its own; accept applies whichever parts are present. A\n * suggestion that only changes the speaker or direction, or only cuts, keeps `proposed` equal to\n * `baseline`. */\nexport interface Suggestion {\n id: string;\n /** The say/prose beat's stable id. */\n anchor: string;\n /** The say text at the moment this was suggested (the diff \"before\" + staleness check). */\n baseline: string;\n /** The proposed replacement say text. */\n proposed: string;\n /** The primary author: with several (`authors`), the one with the most changes. */\n author: string;\n /** Everyone whose changes this suggestion carries, when there is more than one (an editor's file with\n * several people's tracked changes on one line). `author` stays the primary, so a reader that only\n * knows `author` keeps working. */\n authors?: string[];\n ts: string;\n /** A proposed new speaker (a known cast member's name), and the speaker when suggested. */\n proposedCharacter?: string;\n baselineCharacter?: string;\n /** A proposed new direction (the parenthetical), and the direction when suggested (\"\" for none). */\n proposedDirection?: string;\n baselineDirection?: string;\n /** A proposal to cut the beat (an editor emptied its line). Accept marks it cut. */\n proposedCut?: boolean;\n /** Where the suggestion came from, when it arrived in an editable-script handoff: the handoff and the\n * line's marker in it. Drives the review's \"from handoff\" filter, and lets a re-import of the same\n * handoff replace its still-open suggestions instead of stacking duplicates. */\n handoff?: SuggestionSource;\n /** Accepted or rejected -> archived (hidden unless \"show resolved suggestions\" is on). */\n resolved?: boolean;\n outcome?: \"accepted\" | \"rejected\";\n}\n\n/** The handoff a suggestion arrived in (see `Suggestion.handoff`). */\nexport interface SuggestionSource {\n /** The handoff's id (\"H-7Q2K\"). */\n id: string;\n /** The line's marker code in that handoff (\"K7Q2M\"). */\n marker: string;\n}\n\nexport interface EditRecord {\n modifiedAt?: string;\n by?: string;\n /** Per-locale localisation date -> staleness when source modifiedAt is later. */\n localisedAt?: Record<string, string>;\n}\n\nexport interface AuthoringFile {\n schema: string; // \"patter/authoring@0\"\n comments?: Comment[];\n /** \"Suggest a rewrite\" review proposals (design/proposals/suggest-rewrite.md). */\n suggestions?: Suggestion[];\n /** Typed documentation, keyed by node/beat id (spec §18). */\n documentation?: Record<string, DocLine[]>;\n /**\n * Writing status, keyed by BEAT id - a value from the project's writing-status\n * enumeration (spec §13; `writingStatuses`, default `DEFAULT_WRITING_STATUSES`).\n * Tracked on the source language only (translation staleness is `localisedAt`).\n */\n writing?: Record<string, string>;\n /**\n * Recording status, keyed by BEAT id - a value from the project's recording-status\n * enumeration (spec §13/§16; `recordingStatuses`). Single-locale by design: games\n * recording VO in more than one language are rare (and absent at indie level); if\n * ever needed, a per-locale extension comes later.\n */\n recording?: Record<string, string>;\n /**\n * Audio-relationship metadata, keyed by BEAT id. Native (recording) language\n * only - like recording status, VO is single-language by design.\n */\n audio?: Record<string, unknown>;\n /** Author trail + edit/localisation dates, keyed by beat/node id. */\n edits?: Record<string, EditRecord>;\n /**\n * **Cut** content (spec §13): scene or beat ids removed from the production\n * but kept in source. Orthogonal to status (cut is not a degree of doneness);\n * reports exclude cut content from counts / estimates / coverage and surface\n * it as a separate \"cut: N\" figure so a removal is visible, not vanished.\n */\n cut?: Record<string, boolean>;\n /**\n * **Needs re-record** (#227): dialogue-line ids whose recorded take is unusable and must be redone\n * (bad quality, wrong take, misread). Orthogonal to the recording ladder - a flagged line keeps its\n * audio on disk, but its recording status is MASKED to the reserved `rerecord` status for the recording\n * script / report / status browse (see `RERECORD_STATUS`), so a \"recorded\" line still reads as work.\n * Authoring-only; never compiled into a bundle.\n */\n rerecord?: Record<string, boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Editable-script handoff record (design/proposals/editable-script-handoff.md §5.2).\n// One per export, at `handoffs/<id>.json` in the project: what was sent, to whom,\n// when, and each editable line's text AS SENT, which every check on reimport\n// compares against. Authoring-only; never compiled into a bundle.\n// ---------------------------------------------------------------------------\n\nexport const HANDOFF_SCHEMA = \"patter/handoff@0\";\n\nexport interface HandoffFile {\n schema: typeof HANDOFF_SCHEMA;\n /** \"H-7Q2K\": shown in the document's page header, so a returned file finds its record. */\n id: string;\n createdAt: string;\n createdBy: string;\n /** The file format sent. */\n format: \"docx\";\n /** Who it went to, as typed at export (optional). */\n recipient?: string;\n /** The scene ids exported, in document order. */\n range: { scenes: string[] };\n options: {\n /** Which notes rode along: a documentation channel name (\"editor\"), or \"all\". */\n notes: string;\n /** Each line's writing status shown as a tag. */\n status: boolean;\n /** A cast page before the script. */\n cast: boolean;\n };\n /** Every editable line, keyed by its marker code (\"K7Q2M\"). */\n lines: Record<string, HandoffLine>;\n /** The whole document in order: context rows tied to the node they describe, and each editable line\n * by marker. Lets the importer align what came back with what was sent, and anchor a comment made on\n * a context row to its node. */\n skeleton: HandoffRow[];\n /** One entry per reimport, appended. */\n imports?: HandoffImport[];\n /** Set when the handoff is finished with; a closed handoff no longer appears in review filters. */\n closed?: boolean;\n}\n\n/** An editable line as sent. */\nexport interface HandoffLine {\n /** The beat id (a line, a narration beat, or an option prompt). */\n id: string;\n kind: \"line\" | \"narration\" | \"option\";\n /** The speaker and direction as sent (spoken lines only). */\n character?: string;\n direction?: string;\n /** The text as sent: the baseline for every comparison on reimport. */\n baseline: string;\n}\n\n/** One row of the sent document, in order. */\nexport type HandoffRow =\n | { kind: \"box\"; marker: string }\n | { kind: \"scene\" | \"block\" | \"condition\" | \"group\" | \"else\" | \"jump\" | \"gameEvent\" | \"note\"; node: string; text: string };\n\n/** A record of one reimport. */\nexport interface HandoffImport {\n at: string;\n by: string;\n /** SHA-256 of the returned file, so a second import of the same file is recognisable. */\n fileHash: string;\n counts: { changed: number; unchanged: number; stale: number; comments: number; problems: number };\n}\n\n// ---------------------------------------------------------------------------\n// Export bundle (schema §10) - the compiled artefact the runtimes load.\n// Conditions/effects are pre-derived `{ src, ast }` envelopes (not src strings);\n// authoring is stripped; the project-wide voiced flag is carried; locales assembled.\n// ---------------------------------------------------------------------------\n\n/** A compiled expression envelope: canonical source + pre-derived tagged-tuple AST. */\nexport interface Expression {\n src: string;\n ast: AstNode;\n}\n\nexport type CompiledEffect =\n { kind: \"set\"; target: string; value: Expression };\n\nexport interface CompiledSnippet {\n id: string;\n type: \"snippet\";\n condition?: Expression;\n beats?: Beat[]; // beats carry no expressions\n onEnter?: CompiledEffect[];\n onExit?: CompiledEffect[];\n gameData?: GameData;\n tags?: string[]; // author tags (#215), accumulated down the tree at runtime\n jump?: Jump;\n secretUntilEligible?: boolean;\n /** Option-position: repeatable (spec §5). Default false = once-only. */\n sticky?: boolean;\n /** Option-position: the choice's fallback, auto-followed when last (spec §5). */\n fallback?: boolean;\n}\n\nexport interface CompiledGroup {\n id: string;\n type: \"group\";\n condition?: Expression;\n /** Default (omitted) = `\"run\"`. */\n selector?: Selector;\n /** Selector cursor shared across flows (default false = per-flow). */\n shared?: boolean;\n options?: SequenceOptions;\n children: Array<CompiledGroup | CompiledSnippet>;\n gameData?: GameData;\n tags?: string[]; // author tags (#215)\n /** Option-position fields (spec §5) - only when a direct child of a `choice`. */\n prompt?: PromptBeat;\n secretUntilEligible?: boolean;\n /** Repeatable (spec §5). Default false = once-only. */\n sticky?: boolean;\n /** The choice's fallback, auto-followed when last (spec §5). */\n fallback?: boolean;\n}\n\nexport interface CompiledBlock {\n id: string;\n type: \"block\";\n name: string;\n /** Host-facing address (spec §6); the runtime resolves it to `id`. Absent = derived from `name`. */\n gameId?: string;\n children: Array<CompiledGroup | CompiledSnippet>;\n gameData?: GameData;\n tags?: string[]; // author tags (#215)\n}\n\nexport interface CompiledScene {\n id: string;\n type: \"scene\";\n name: string;\n /** Host-facing address (spec §6); the runtime resolves it to `id`. Absent = derived from `name`. */\n gameId?: string;\n gameData?: GameData;\n tags?: string[]; // author tags (#215)\n onEntry?: CompiledEffect[];\n sceneProps?: PropertyDecl[];\n blocks: CompiledBlock[];\n}\n\nexport interface Bundle {\n schema: string; // \"patter/bundle@0\"\n /** `hash` fingerprints the WHOLE bundle (binds saves, gates staleness); `structureHash` is the same\n * fingerprint with the string tables left out, so same structureHash + a different hash = a\n * text-only edit, safe to hot-swap in place (live bundle refresh). */\n content: { project: string; version?: string; hash?: string; structureHash?: string };\n voiced: boolean; // project-wide VO mode (spec §16)\n locales: { default: string; included: string[] };\n /** Player-facing cast only: the compiler strips notes / actor / gender (see `BundleCastMember`). */\n cast?: BundleCastMember[];\n properties?: PropertyDecl[];\n /** Host / world scope declarations, baked from the project so the runtime can self-back a declared\n * scope (`@world`, ...) when no host resolver claims its token. Absent = no host scopes. */\n scopeRegistry?: HostScopeRegistry;\n /** Other engines' game-wide scopes the content names (`story`), sorted: the family's shared vocabulary,\n * opaque to the compiler and never self-backed. The engine reports when the game has not registered\n * one. Absent when none. */\n externalScopes?: string[];\n gameDataFields?: GameDataFields;\n scenes: Record<string, CompiledScene>;\n /** locale -> (beatId -> text). In \"embedded\" localisation this carries every included locale; in \"ids\"\n * it is EMPTY (the runtime emits beat IDs), unless `localisation.sourceDebug` embedded the source locale\n * for debug playback. `content.hash` is computed over the FULL strings regardless, so the staleness gate\n * is unaffected. */\n strings: Record<string, Record<string, string>>;\n /** How strings ship + resolve (spec §11). Absent = \"embedded\" (back-compat default): the runtime resolves\n * `strings` per locale. \"ids\": the runtime emits beat IDs and the game localises them itself; `sourceDebug`\n * means the source locale is embedded purely for debug playback and the runtime should flag the build as\n * not shippable. */\n localisation?: { mode: \"embedded\" | \"ids\"; sourceDebug?: boolean };\n /** Closed-caption delimiters baked from the project (#214). Absent = the default `(` / `)`; the\n * runtime strips spans between them from line text when a game disables captions. */\n closedCaptions?: CaptionDelimiters;\n}\n\n/** Closed-caption configuration (#214). `open`/`close` wrap a caption cue inside a dialogue line (both\n * non-empty; they MAY be the same token, e.g. `*…*`). `character` names a cast member whose lines are a\n * pure caption: when captions are off, ALL of that character's dialogue (and its speaker label) is\n * omitted - delimiters or not - leaving a silent line that still fires (so audio plays). Absent / empty\n * `character` resolves to the default `SFX` (you \"disable\" it simply by never using that speaker). */\nexport interface CaptionDelimiters {\n open: string;\n close: string;\n character?: string;\n}\n\n/** The default caption delimiters when a project pins none: square brackets, the closed-captioning\n * convention for non-speech cues. Round brackets are deliberately NOT the default: `(` at the start of a\n * line opens a performance direction in the editor, so it would shadow a caption cue there. */\nexport const DEFAULT_CAPTION_DELIMITERS: CaptionDelimiters = { open: \"[\", close: \"]\" };\n\n/** The default caption character: a cast member named `SFX` whose lines are pure captions (omitted when\n * captions are off). Applies even to a project that pins no `closedCaptions`. */\nexport const DEFAULT_CAPTION_CHARACTER = \"SFX\";\n\n// ---------------------------------------------------------------------------\n// Save format: `patter/save@0` - the FAMILY's contract, not any one runtime's.\n//\n// Every Patterplay runtime (JS, Unity, Unreal, Godot) writes exactly this shape and reads it, so a\n// save crosses engines: a game that saves from a web build loads in Godot, a Patterpad Play-window\n// save loads in Unity. The JS reference is canonical (its output IS this document), and the shape\n// lives HERE rather than in the runtime so that the runtime implements a type it does not get to\n// invent - the rule the Storylet Engine holds its four runtimes to, and the reason its saves crossed\n// first time while ours crossed nowhere: three ports, three key namings, and nothing that said which\n// was right (from-storylets/save-shape-across-engines, 2026-09-03; design/patter-schema.md 9).\n//\n// Keys are camelCase literals, values are bare scalars, and semantic parity is what the conformance\n// corpus pins - byte parity is not required, key-path parity is.\n//\n// Version 3 (the one-registry model): property values are the game's ScopeRegistry's, not the\n// engine's. A save holds only what is NOT a property (cursors, PRNGs, visits, selectors), plus, when\n// the engine made its own registry (a standalone game), that registry's values under `registry`. A\n// game that passed a registry saves it once itself. Version 2 saves still load on every runtime:\n// their property sections move into the registry under the keys below.\n//\n// Registry keys (identical on every runtime, since they are in the save): `patter` for the shared\n// globals; `patter/scene/<sceneId>` for a scene's shared `@scene` props; `patter/flow/<flowId>/patter`\n// and `patter/flow/<flowId>/scene/<sceneId>` for a flow's own. Ids escape `%` as `%25` and `/` as `%2F`.\n// A self-backed `@world` (no resolver bound) is a stored property too, under `world`.\n// ---------------------------------------------------------------------------\n\nexport const SAVE_SCHEMA = \"patter/save@0\";\n/** The `save.version` inside the envelope. Bumped only when a reader would MISREAD an older save. */\nexport const SAVE_VERSION = 3;\n\n/** The tagged envelope a host stores: `serializeState` writes it, `deserializeState` refuses anything else. */\nexport interface SaveEnvelope {\n schema: typeof SAVE_SCHEMA;\n save: SaveGame;\n}\n\n/** A registry's values: registry key -> property name -> value (`{ patter: { gold: 7 } }`). */\nexport type EngineSave = Record<string, Record<string, ScalarValue>>;\n\n/** Serialised `sequence` selector visit state for one group (spec §4 / §7). Every key is OPTIONAL and\n * present only once the selector has used it: `seq` after the first sequential pick, `bag` once a\n * shuffle has drawn, `last` once there is a no-immediate-repeat memory. A port that stores \"started\"\n * flags derives them from key presence rather than writing them. */\nexport interface SelectorSnapshot {\n seq?: number;\n bag?: string[];\n last?: string;\n}\n\n/** One entry on a flow's continuation stack: a position within a container's children. */\nexport interface StackFrame {\n sceneId: string;\n /** A block id or a run-group id (both are sequential containers). */\n containerId: string;\n index: number;\n /** SNAPSHOT-ONLY (never on a live frame): the id of the child at `index` when the save was taken, so a\n * restore against an EDITED bundle re-finds the position by id. Absent when the frame sat at its\n * container's end (no next child), or in an older save. */\n nextId?: string;\n}\n\n/** The prompt of a saved choice option: the choice text as a structured line/text beat. */\nexport interface SavedChoicePrompt {\n kind: \"line\" | \"text\";\n text: string;\n character?: string;\n characterName?: string;\n direction?: string;\n}\n\n/** One option of a pending choice, saved VERBATIM: re-deriving on load would re-evaluate conditions. */\nexport interface SavedChoiceOption {\n id: string;\n prompt?: SavedChoicePrompt;\n eligible: boolean;\n gameData?: GameData;\n}\n\n/** A pending choice as saved: the option set the player was shown, replayed on load (schema 9.3). */\nexport interface SavedChoice {\n groupId: string;\n options: SavedChoiceOption[];\n}\n\n/** The flow's execution position: where it is, and what it is waiting on. */\nexport interface FlowCursor {\n flowEnded: boolean;\n currentSceneId: string | null;\n /** The continuation stack (call frames + the active block run), root first. */\n stack: StackFrame[];\n activeSnippetId: string | null;\n beatIndex: number;\n /** Present (non-null) iff the flow is stopped at a choice. */\n pendingChoice: SavedChoice | null;\n /** The chosen option owning a prompt still to be replayed (a save taken between choose + advance).\n * Optional / null in older saves -> no pending prompt. */\n pendingPromptOwnerId?: string | null;\n /** The prompt still to be replayed, exactly as the choice SHOWED it (text and speaker fields, the same\n * shape as a saved option's `prompt`), so the replay after a load says what the player saw. Written only\n * alongside a `pendingPromptOwnerId` (the choose -> advance window, with `replayPromptOnChoose`), and\n * absent otherwise. A save without it (written before Patterplay 0.18.0) resolves the owner's prompt beat\n * when it is delivered, as before. */\n pendingPrompt?: SavedChoicePrompt;\n /** This flow's (non-shared) selector cursors, keyed by group id. */\n selectors: Record<string, SelectorSnapshot>;\n}\n\n/** The serialised cursor + PRNG + visits of a single flow. Its properties are the registry's. */\nexport interface FlowSnapshot {\n /** This flow's built-in PRNG position (mulberry32 state, uint32; older saves may carry it signed). */\n rngState: number;\n /** This flow's per-node entry counts (node id -> times entered by this flow). */\n visits: Record<string, number>;\n cursor: FlowCursor;\n}\n\n/** A full resumable save-game (version 3): everything that is not a property, plus the registry's\n * values when the engine made its own registry. */\nexport interface SaveGame {\n version: 3;\n /** The engine's own registry's values, keyed by registry key: present only when the engine made the\n * registry itself (a standalone game). A game that passed a registry saves it once, beside this. */\n registry?: EngineSave;\n /** World-wide per-node entry counts (node id -> times entered by any flow). */\n sharedVisits: Record<string, number>;\n /** Shared selector cursors (node id -> snapshot) for `shared` memoried selectors. */\n sharedSelectors: Record<string, SelectorSnapshot>;\n /** Each live flow's snapshot, keyed by flow id. */\n flows: Record<string, FlowSnapshot>;\n}\n\n/** A version 2 flow snapshot, which carried the flow's property values itself. Still read. */\nexport interface FlowSnapshotV2 extends FlowSnapshot {\n /** The NOT-shared `@patter` globals, under token \"patter\". */\n scopes: EngineSave;\n /** Per-scene NOT-shared `@scene` bags (scene id -> name -> value). */\n sceneBags: Record<string, Record<string, ScalarValue>>;\n}\n\n/** A version 2 save, from before the registry held the properties. Every runtime still reads it: its\n * property sections move into the registry (under the keys in the header above) as it loads. */\nexport interface SaveGameV2 {\n version: 2;\n /** Shared `@patter` globals (owned scope \"patter\"). */\n shared: EngineSave;\n sharedVisits: Record<string, number>;\n sharedSelectors: Record<string, SelectorSnapshot>;\n /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value). */\n stageBags: Record<string, Record<string, ScalarValue>>;\n flows: Record<string, FlowSnapshotV2>;\n}\n\n/** Where a project keeps its shards unless its `layout` says otherwise, relative to the project root. */\nexport const DEFAULT_LAYOUT = { flow: \"scenes/\", strings: \"loc/\", authoring: \"authoring/\" } as const;\n\n/** A project's layout with the defaults filled in: the one place they are written down (the loader,\n * validate, localisation, the scene scaffold, and Patterpad each had a copy). */\nexport function projectLayout(project: { layout?: { flow?: string; strings?: string; authoring?: string } }): { flow: string; strings: string; authoring: string } {\n return { ...DEFAULT_LAYOUT, ...project.layout };\n}\n\n/** The source shards' schema tags (spec §10), written by every tool that makes one. */\nexport const FLOW_SCHEMA = \"patter/flow@0\";\nexport const PROJECT_SCHEMA = \"patter/project@0\";\nexport const STRINGS_SCHEMA = \"patter/strings@0\";\nexport const AUTHORING_SCHEMA = \"patter/authoring@0\";\n","// ---------------------------------------------------------------------------\n// Author tags (#215): a cross-cutting label layer baked into the bundle.\n//\n// A node's *accumulated* tags are the union of its own and every ancestor's,\n// ordered outermost-first (scene → block → group(s) → snippet → beat) and\n// deduped. That accumulation is purely structural, it depends only on where a\n// node sits in the tree, not on play state, so it's precomputed ONCE at engine\n// load into a flat `id -> string[]` index and read back as O(1) lookups for both\n// the delivered step `tags` and the `tagsFor*` accessors.\n// ---------------------------------------------------------------------------\n\nimport type { Bundle, CompiledGroup, CompiledSnippet } from \"@patterkit/model\";\n\nfunction dedupe(tags: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const t of tags) if (!seen.has(t)) { seen.add(t); out.push(t); }\n return out;\n}\n\n/**\n * Map every node id (scene / block / group / snippet / beat, prompt beats included) to its accumulated\n * tags. Node ids are globally unique within a project (the validator enforces\n * it), so one flat map suffices. Nodes with no tags anywhere up the chain map to\n * an empty array.\n */\nexport function buildTagIndex(bundle: Bundle): Map<string, string[]> {\n const index = new Map<string, string[]>();\n\n const visit = (node: CompiledGroup | CompiledSnippet, inherited: string[]): void => {\n const acc = dedupe([...inherited, ...(node.tags ?? [])]);\n index.set(node.id, acc);\n // An option's prompt beat is a beat like any other: its own tags plus the option's. Left out, a\n // replayed prompt and the outline's prompt lost every tag (all four runtimes, until 2026-10).\n if (node.type === \"group\" && node.prompt) index.set(node.prompt.id, dedupe([...acc, ...(node.prompt.tags ?? [])]));\n if (node.type === \"group\") {\n for (const child of node.children) visit(child, acc);\n } else {\n for (const beat of node.beats ?? []) index.set(beat.id, dedupe([...acc, ...(beat.tags ?? [])]));\n }\n };\n\n for (const scene of Object.values(bundle.scenes)) {\n const sceneAcc = dedupe(scene.tags ?? []);\n index.set(scene.id, sceneAcc);\n for (const block of scene.blocks) {\n const blockAcc = dedupe([...sceneAcc, ...(block.tags ?? [])]);\n index.set(block.id, blockAcc);\n for (const child of block.children) visit(child, blockAcc);\n }\n }\n\n return index;\n}\n","// ---------------------------------------------------------------------------\n// @patterkit/runtime - Patterplay for JavaScript.\n//\n// An `Engine` is the world + flow manager: it owns the compiled Bundle, the\n// shared state (shared `@patter` globals + shared `@scene` props + host foreign\n// scopes), and a set of named **flows**. All *play* happens on a `Flow` handle\n// (`engine.openFlow(id, ...)`): a flow has its own execution cursor, its own PRNG,\n// and its own copy of the NOT-shared state (per-flow `@patter` globals + per-flow\n// `@scene` props). Multiple flows run concurrently and independently - a flow is\n// addressed explicitly (`alice.advance()`), so there is no ambient \"current flow\".\n//\n// Scopes are just two tokens - `@patter` (global; bare `@name`) and `@scene`\n// (scene-local) - with an orthogonal per-property `shared` flag (default: shared\n// for `@patter`, per-flow for `@scene`). So each token spans two storage areas:\n// the shared half lives on the engine, the per-flow half on the flow; a read/write\n// routes by the property's `shared` flag. (Mirrors Storylet Studio's @world/@site.)\n//\n// A flow plays the select-one-child model (spec §4): a block branches one\n// eligible child; a group runs its selector (branch / sequence / choice -\n// `sequence` covers order x exhaust); a `choice` group stops for the host; a snippet runs onEnter, delivers\n// beats, runs onExit, follows its jump. Falling off the end ends the flow.\n// Cross-flow jumps are not a thing - the host switches flows.\n//\n// Every property bag lives in ONE ScopeRegistry per game (the one-registry model): the\n// game hands the engine its registry (`options.registry`) or the engine makes its own\n// and acts as its own game. `@patter` is registered under `patter`; the per-flow and\n// per-scene bags under keys starting `patter/` (see `keys` below), which no expression\n// can name. `engine.saveGame()` / `loadGame()` snapshot + restore what is NOT a property:\n// cursors, PRNGs, visits, and selectors. The registry's values ride in `saveGame()` only\n// when the engine made the registry itself; otherwise the game saves the registry once.\n// ---------------------------------------------------------------------------\n\nimport { evaluate, deserialiseAst, makePrng, toUint32 } from \"@wildwinter/expr\";\nimport type { ScalarValue, EvalContext, ExprNode } from \"@wildwinter/expr\";\nimport { matchedSpecificity as scoreSpecificity, type EvalTruthy } from \"@wildwinter/expr-specificity\";\nimport { ScopeRegistry } from \"@wildwinter/scoperegistry\";\nimport { defaultFor, PropertyBag } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, PropertyRow, ScopeDeclaration, ScopeResolver } from \"@wildwinter/scoperegistry\";\nimport { patterDialect, interpolate, splitRef, stripCaptions } from \"@patterkit/dialect\";\nimport { walkNodes, effectiveGameId, castStringKey, DEFAULT_CAPTION_DELIMITERS, DEFAULT_CAPTION_CHARACTER } from \"@patterkit/model\";\nimport { buildTagIndex } from \"./tags.js\";\nimport { SAVE_VERSION } from \"@patterkit/model\";\nimport type {\n EngineSave, SelectorSnapshot, StackFrame, SavedChoice, SavedChoicePrompt, FlowSnapshot, SaveGame, SaveGameV2,\n Bundle, CompiledScene, CompiledBlock, CompiledGroup, CompiledSnippet,\n CompiledEffect, Beat, LineBeat, TextBeat, GameData, Expression, PropertyDecl, PropertyType, Jump, HostScopeDecl,\n} from \"@patterkit/model\";\n\ntype SelectableNode = CompiledGroup | CompiledSnippet;\n\n// Compiled expressions are immutable, so each one's AST is deserialised once -\n// per evaluation was the engine's hottest path (every condition / effect / slot).\nconst astCache = new WeakMap<Expression, ExprNode>();\n\n// The tag index depends only on the bundle's structure, so it is built once per bundle object rather than\n// once per engine. A tool that builds a fresh engine for every run over one bundle (coverage does, to keep\n// the runs independent) spent over a third of its time rebuilding it. A changed bundle is a new object\n// (hotSwap takes one; replaceStrings touches only strings), so the cache never answers for stale content.\nconst tagIndexCache = new WeakMap<Bundle, Map<string, string[]>>();\nfunction tagIndexFor(bundle: Bundle): Map<string, string[]> {\n let index = tagIndexCache.get(bundle);\n if (!index) { index = buildTagIndex(bundle); tagIndexCache.set(bundle, index); }\n return index;\n}\n/** A node's tags as a COPY: the index is shared by every engine on the bundle, so a host that edits an\n * array it was given (a step's tags, `tagsForBeat`) must not change the answer for all of them. */\nfunction tagsOf(host: { tagIndex: Map<string, string[]> }, id: string): string[] | undefined {\n return host.tagIndex.get(id)?.slice();\n}\n\n// The save shape is the FAMILY's contract and lives in @patterkit/model (`SaveGame` and friends, with\n// the reasoning). Re-exported here so `import type { SaveGame } from \"@patterkit/runtime\"` keeps\n// working; this engine's saveGame() output IS that document, and every port writes the same.\nexport type {\n EngineSave, SelectorSnapshot, StackFrame, SavedChoice, SavedChoiceOption, SavedChoicePrompt,\n FlowCursor, FlowSnapshot, SaveGame, SaveEnvelope, SaveGameV2, FlowSnapshotV2,\n} from \"@patterkit/model\";\n\n/** The owner label on everything this engine registers: named in a clash error and carried on\n * the registry's examiner rows, so one inspector can group a combined game by engine. */\nconst OWNER = \"Patter\";\n\n/** The registry keys this engine stores its instance bags under. An id is escaped (`%` and `/`)\n * so a flow named `npc/bob` cannot collide with another flow's scene. Every runtime writes the\n * same keys: they are in the save. */\nconst esc = (id: string): string => id.replace(/%/g, \"%25\").replace(/\\//g, \"%2F\");\n/** Internal: marks a hotSwap replacement of a standalone engine, which shares its predecessor's registry\n * but is still its own game (it self-backs host scopes and saves the registry's values). */\nconst OWNS_REGISTRY = Symbol(\"ownsRegistry\");\ntype InternalOptions = EngineOptions & { [OWNS_REGISTRY]?: boolean };\n\nconst keys = {\n /** A scene's SHARED `@scene` props (one bag per scene, every flow's). */\n stage: (sceneId: string): string => `patter/scene/${esc(sceneId)}`,\n /** Everything one flow registers starts with this. */\n flow: (flowId: string): string => `patter/flow/${esc(flowId)}/`,\n /** A flow's NOT-shared `@patter` globals. */\n flowGlobals: (flowId: string): string => `patter/flow/${esc(flowId)}/patter`,\n /** A flow's NOT-shared `@scene` props for one scene. */\n flowScene: (flowId: string, sceneId: string): string => `patter/flow/${esc(flowId)}/scene/${esc(sceneId)}`,\n};\n\n/** What `Flow.advance()` surfaces to the host at each stop. */\nexport type StepResult =\n | { type: \"line\"; id: string; text: string; character?: string; characterName?: string; direction?: string; gameData?: GameData; tags?: string[] }\n | { type: \"text\"; id: string; text: string; gameData?: GameData; tags?: string[] }\n | { type: \"gameEvent\"; id: string; gameData?: GameData; tags?: string[] }\n | { type: \"choice\"; groupId: string; options: ChoiceOption[] }\n | { type: \"end\" };\n\n// --- Static structure introspection (editor / dev tooling) -------------------\n// A read-only view of the AUTHORED tree (scenes -> blocks -> groups/snippets -> beats), for dev\n// tools that build against the writer's structure (e.g. an Unreal Sequencer of subsequences per\n// beat). Static: no flow, no play state. Per-beat data mirrors what a StepResult would carry\n// (source text, author gameData, accumulated tags), read at the default locale.\n\n/** One beat's static data - the same shape a delivered step carries, resolved at the source locale. */\nexport interface BeatInfo {\n id: string;\n kind: \"line\" | \"text\" | \"gameEvent\";\n /** Speaker token (line only). */\n character?: string;\n /** Resolved display name for `character` (source locale), if the cast declares one. */\n characterName?: string;\n /** Performance direction (line only). */\n direction?: string;\n /** Source text, un-interpolated (line / text). Omitted for gameEvent and IDs-only bundles. */\n text?: string;\n /** Author gameData overrides on this beat (raw, as the step carries them). Omitted when empty. */\n gameData?: GameData;\n /** Accumulated author tags (scene -> block -> group(s) -> snippet -> beat). Omitted when empty. */\n tags?: string[];\n}\n\n/** A node in the outline tree: a group (with its selector + children) or a snippet (with its beats). */\nexport interface OutlineNode {\n type: \"group\" | \"snippet\";\n id: string;\n tags?: string[];\n // group only\n selector?: string;\n /** A choice/option group's prompt beat, if any. */\n prompt?: BeatInfo;\n children?: OutlineNode[];\n // snippet only\n beats?: BeatInfo[];\n jumpTo?: string;\n jumpMode?: \"jump\" | \"call\";\n}\n\n/** A block in the outline tree. */\nexport interface OutlineBlock {\n id: string;\n gameId?: string;\n name: string;\n /** The block's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */\n gameData?: GameData;\n tags?: string[];\n children: OutlineNode[];\n}\n\n/** A scene in the outline tree. */\nexport interface OutlineScene {\n id: string;\n gameId?: string;\n name: string;\n /** The scene's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */\n gameData?: GameData;\n tags?: string[];\n blocks: OutlineBlock[];\n}\n\n/** One beat in document order, with the scene/block/snippet it lives in (the flat view). */\nexport interface FlatBeat {\n sceneId: string;\n blockId: string;\n snippetId: string;\n beat: BeatInfo;\n}\n\n/** What {@link Flow.advanceToStop} returns: the beats walked, and the choice / end that stopped it. */\nexport interface AdvanceToStopResult {\n /** The line / text / game-event beats played on the way to the stop (never a choice / end). */\n played: Array<Extract<StepResult, { type: \"line\" | \"text\" | \"gameEvent\" }>>;\n stop: Extract<StepResult, { type: \"choice\" | \"end\" }>;\n}\n\n/** The choice text of an option (spec §5): its `prompt` beat, resolved + interpolated. */\nexport interface ChoicePrompt {\n kind: \"line\" | \"text\";\n /** Display text (interpolated; may be empty - the host can render from gameData / an icon). */\n text: string;\n /** Speaker / direction - present only for a `line` prompt (the PC's spoken choice). */\n character?: string;\n /** The speaker's resolved player-facing name (locale-aware; absent when the character has none). */\n characterName?: string;\n direction?: string;\n}\n\n/** A single option of a pending `choice` group. */\nexport interface ChoiceOption {\n /** The option's id (an Option group, or a degenerate option snippet) - pass to `choose()`. */\n id: string;\n /**\n * The option's `prompt` (spec §5) - the choice text as a structured line/text beat. For the\n * degenerate bare-snippet tolerance, derived from the snippet's first content line. Undefined\n * only when even that is absent; internal ids are never leaked as display text.\n */\n prompt?: ChoicePrompt;\n /** False when the option's condition fails; still returned (greyed) unless hidden. */\n eligible: boolean;\n gameData?: GameData;\n}\n\n/**\n * A host scope's resolver: a `{ get, set? }` the game provides so the story can read (and, if you allow\n * it, write) values the game owns, `@world.*` among them, at runtime. Property metadata (types,\n * read-only) comes from the compiled bundle's declarations; the values themselves live in the host and\n * are never stored or saved by this engine.\n */\nexport type HostScope = ScopeResolver;\n\n/** @deprecated Use {@link HostScope}, the name every Patterplay runtime uses. */\nexport type WorldResolver = HostScope;\n\n/** What the engine DECIDED, as opposed to what it produced. A step tells you the line that\n * played; a trace event tells you why that line and not its siblings - which children were\n * eligible, where the selector's cursor stood, which choice options were live.\n *\n * The reasoning is IN the entry, not implied by it. `select` carries every child it looked at\n * with its own verdict, the way a deal in the Storylet Engine carries every card it considered.\n * A log that recorded only the winner would answer \"what happened\" and not \"why\", and \"why\"\n * is the question a dialogue author cannot otherwise answer: the line you expected is missing\n * and nothing anywhere says which condition dropped it.\n *\n * Patter's vocabulary, not the Storylet Engine's: there are no cards or hands here. The\n * structure is shared (an ordered retained log, `seq`, a flow-level and an engine-level tap);\n * the events are this engine's own. */\nexport type TraceEvent =\n | {\n type: \"select\";\n /** The group whose children were being chosen among. */\n group: string;\n /** `branch` | `sequence` | `run` | `choice`, and for `sequence` its order/exhaust policy. */\n selector: string;\n order?: string;\n exhaust?: string;\n /** Every child considered, in authored order, each with why it was or was not takeable. */\n children: { id: string; eligible: boolean }[];\n /** The child selected, or null when nothing was takeable. */\n picked: string | null;\n }\n | {\n type: \"choice\";\n group: string;\n /** Every option the group offered, with the ones a condition dropped marked. */\n options: { id: string; eligible: boolean }[];\n }\n /** A choice the player answered. `option` is what they took. */\n | { type: \"chose\"; group: string; option: string }\n /** A choice with no takeable option and no eligible fallback, which falls through\n * silently. Also reported through `onDryChoice`, which stays: a log is an audit read\n * afterwards, that callback is live feedback a host acts on. */\n | { type: \"dry\"; group: string }\n | { type: \"jump\"; to: string; mode: \"jump\" | \"call\" }\n /** One landed effect; `prev` is the value it replaced, so a reader can say \"0 -> 1\". */\n | { type: \"write\"; target: string; value: ScalarValue; prev?: ScalarValue }\n /** Content the engine could not evaluate and played through (see {@link PlayError}): a condition\n * counted as false, an effect skipped, or a Best-match part scored as false. */\n | { type: \"diagnostic\"; kind: PlayError[\"kind\"]; node: string; source?: string; message: string };\n\n/**\n * A content error the engine played through. Content can fail at run time in ways the compiler cannot\n * see: a division by zero, a host value of the wrong type, a story write to a read-only `@world` value.\n * The story never stops for one. A condition that fails counts as false; an effect that fails is skipped\n * and the rest of its list still runs; a part of a Best-match condition that fails scores as false. Each\n * is reported, through `EngineOptions.onError` and as a `diagnostic` entry in the decision log. The same\n * rule holds on all four runtimes, including the two whose languages cannot throw.\n */\nexport interface PlayError {\n /** The flow it happened in. */\n flow: string;\n /** What failed: a condition, an effect, or a part of a condition being scored for Best match. */\n kind: \"condition\" | \"effect\" | \"best-match\";\n /** The snippet, group, or option whose condition failed, or that owns the effect (a scene, for its onEntry). */\n node: string;\n /** The expression's source text, when the bundle carries it. */\n source?: string;\n message: string;\n}\n\n/** @deprecated Nothing takes one: a flow has no tap of its own, and `onTrace` takes an\n * {@link EngineTraceHandler}. Goes in a later release. */\nexport type TraceHandler = (event: TraceEvent) => void;\n/** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */\nexport type EngineTraceHandler = (flow: string, event: TraceEvent) => void;\n\n/** A retained entry: the event plus its place in flow time. `seq` is monotonic across the\n * flow and survives clearLog, so two reads of the log agree about order. */\nexport type LogEntry = TraceEvent & { seq: number; scene?: string };\n\n/** One entry on the ENGINE's log: the same event plus the flow it happened in. A run is\n * several flows over shared state, so \"what happened in this run\" is only answerable in one\n * ordered stream, and only if each line says who. */\nexport type EngineLogEntry = LogEntry & { flow: string };\n\nexport interface EngineOptions {\n /**\n * Custom float-in-[0,1) source for `random()` / shuffle, shared by all flows.\n * Overrides the built-in seeded PRNG - but its position is NOT captured by\n * `saveGame()`. For resumable runs, use the built-in per-flow seed instead.\n */\n rng?: () => number;\n /** Default seed for each flow's built-in (serialisable) PRNG; override per flow in `openFlow`. */\n seed?: number;\n /** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.\n * Ignored by an \"ids\" bundle, which emits beat IDs for the game to localise itself. */\n locale?: string;\n /** The host scopes the game binds, by token: for each, a resolver over values the game owns and the\n * story reads, such as `{ world: { get, set } }` for **World Properties** (`@world.*`). A declared\n * scope left unbound is self-backed from its defaults, as a property the registry stores and saves.\n * Shared by all flows. A game running several engines registers its scopes in its registry itself\n * instead, and binds none here. The same option on every runtime. */\n hostScopes?: Record<string, HostScope>;\n /** @deprecated Use `hostScopes: { world }`, the option every Patterplay runtime takes. Goes in a later\n * release. */\n world?: HostScope;\n /** The game's registry: ONE per game, holding every engine's properties except those the game keeps\n * itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,\n * its per-flow and per-scene bags under keys starting `patter/`, and each scope `hostScopes` binds),\n * reads every other scope from it, and `saveGame()` leaves the property values to the game. Host\n * scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should\n * store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as\n * its own game: it self-backs `@world`, and `saveGame()` carries the registry's values too. */\n registry?: ScopeRegistry;\n /**\n * Replay a chosen option's `prompt` as its first played beat (spec §5). Default `false`:\n * the prompt is a label only and `choose()` plays just the option's content. `true`: the\n * prompt beat is delivered first (the choice \"spoken back\"). A host decision, not authored.\n */\n replayPromptOnChoose?: boolean;\n /** Closed captions (#214): show non-spoken caption cues inside dialogue lines (the `[sigh]` in\n * `Oh dear. [sigh] What now?`). Default `true` (full text). `false` strips every cue + its delimiters\n * and collapses the whitespace - for a player who hears the audio and doesn't want the captions.\n * Toggle live with `engine.setClosedCaptions(...)`. */\n closedCaptions?: boolean;\n /** Diagnostics hook (opt-in, dev tooling only): fired with the choice's group id whenever a choice runs\n * DRY - no takeable option and no eligible fallback - so it falls through silently. The behaviour is\n * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices\n * that ran dry. Leave it unset in shipped games (zero cost). */\n onDryChoice?: (groupId: string) => void;\n /** Called with each content error the engine played through (see {@link PlayError}): a condition\n * that failed and counted as false, an effect that failed and was skipped. Unset, each is written\n * to `console.warn`, so a content bug is never silent. */\n onError?: (error: PlayError) => void;\n /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through\n * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a\n * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful\n * with the log off - it is live feedback, not an audit read afterwards. */\n log?: boolean;\n}\n\n// PropertyView is gone. It was the shared PropertyRow plus a `path`, and `path` moved onto\n// that row on 2026-09-02 - so the name was a synonym, and a synonym for a shared type is\n// how the two families drifted in the first place: the same row called PropertyView here,\n// ScopePropertyRow there, PropertyRow in the kernel. listProperties() returns PropertyRow.\n\n/** Options for opening a flow. */\nexport interface OpenFlowOptions {\n /** Scene to start at - its host-facing gameId (address) OR its internal id; defaults to the\n * bundle's first scene. */\n scene?: string;\n /** Block within the scene to start at - its gameId (scene-scoped address) OR its internal id. */\n block?: string;\n /** Seed for this flow's PRNG (defaults to the engine's `seed`). */\n seed?: number;\n}\n\ninterface ChoiceState {\n /** The choice group's id (saved alongside the verbatim option set - SavedChoice). */\n groupId: string;\n options: ChoiceOption[];\n byId: Map<string, SelectableNode>;\n}\n\ninterface SelectorState {\n seq?: number; // sequential cursor (visits taken)\n bag?: string[]; // shuffle: child ids still undrawn this pass (undefined = not started)\n last?: string; // last child id picked (no-immediate-repeat across reshuffles)\n}\n\n/** What an open checkpoint records: how to undo each change, newest last. Each piece of state records\n * itself when it changes (a value, a count, a cursor), never a copy of everything, so a checkpoint costs\n * what the steps inside it do, however much history the game has built up. */\nexport interface Journal {\n undo: Array<() => void>;\n /** Flows already snapshotted (or opened inside the checkpoint, so closed on rollback). */\n flows: Set<Flow>;\n /** Flows opened inside the checkpoint. */\n opened: Set<Flow>;\n /** Selector cursors already copied: shared ones by group id, a flow's own in `flowSelectors`. */\n selectors: Set<string>;\n flowSelectors: Map<Flow, Set<string>>;\n}\n\n/** An open checkpoint, from {@link Engine.checkpoint}. Opaque: hand it back to `rollback` or `commit`. */\nexport class Checkpoint {\n /** @internal */\n constructor(readonly journal: Journal) {}\n}\n\n/** Shared, read-mostly context the engine hands to every flow it owns. */\ninterface FlowHost {\n /** True when the run asked for a log. */\n logEnabled: boolean;\n /** Each declaration set's quality ladders, built on first use (see `ladders`). */\n qualityLadders: Map<string, Map<string, readonly string[]>>;\n /** True when anything takes the decisions: the log, or an {@link Engine.onTrace} handler. Flows skip\n * the work only a decision needs (a `run` walk's entry, a write's previous value) otherwise. */\n tracing: boolean;\n /** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */\n emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;\n bundle: Bundle;\n /** IDs-only build (`localisation.mode === \"ids\"`, no source-debug): the engine emits each beat's ID as\n * its text and omits character display names, leaving localisation to the game (use `flow.interpolate`\n * to apply `{@ref}` property replacement to a string the game looked up itself). */\n emitIds: boolean;\n strings: Record<string, string>;\n /** The DEFAULT locale's string table - fallback for a key the active locale is missing (notably the\n * cast display-name keys, seeded there from `displayName`). */\n defaultStrings: Record<string, string>;\n /** Cast canonical name -> authoring `displayName` (the unlocalised fallback when no loc string exists). */\n castDisplay: Map<string, string>;\n nodeIndex: Map<string, SelectableNode>;\n blockIndex: Map<string, { sceneId: string }>;\n blockById: Map<string, CompiledBlock>;\n /** Host-facing addresses (spec §6), shared with the engine: scene gameId -> internal id (project-wide),\n * and per-scene block gameId -> internal id. A flow needs them to resolve `goto` by address. */\n sceneGameIdToId: Map<string, string>;\n blockGameIdToId: Map<string, Map<string, string>>;\n /** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */\n tagIndex: Map<string, string[]>;\n /** The game's one registry: `@patter` (the SHARED globals), host scopes, every instance bag. */\n registry: ScopeRegistry;\n /** True when the engine made the registry (a standalone game): `saveGame()` then carries its values. */\n readonly ownsRegistry: boolean;\n /** The SHARED `@patter` globals' bag, registered under `patter`. */\n patterBag: PropertyBag;\n /** Host scopes this engine self-backed and registered (the game bound none, nobody else had). */\n hostScopes: string[];\n /** Decls for the shared `@patter` globals - (re)seed on `engine.reset()`. */\n patterSharedDecls: ScopeDeclaration[];\n /** Decls for the per-flow `@patter` globals - seed each flow's local registry. */\n patterLocalDecls: ScopeDeclaration[];\n /** Lowercase names of the SHARED globals (route a `@patter` ref to engine vs flow). */\n patterSharedNames: Set<string>;\n /** Per-scene set of SHARED `@scene` prop names (route a `@scene` ref to stage vs flow). */\n sceneSharedNames: Map<string, Set<string>>;\n /** World-wide per-node entry counts (node id -> times entered by any flow). */\n sharedVisits: Map<string, number>;\n /** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */\n sharedSelectors: Map<string, SelectorState>;\n /** Per-scene SHARED scene props, each registered under `keys.stage(sceneId)`. Made the first time any\n * flow needs the scene, so a bag loaded before then waits in the registry and is claimed here. */\n stageBags: Map<string, PropertyBag>;\n /** The open checkpoint's undo journal, or null when none is open (see {@link Engine.checkpoint}). */\n journal: Journal | null;\n customRng?: () => number;\n /** Play a chosen option's prompt as its first beat (spec §5); default false. */\n replayPromptOnChoose?: boolean;\n /** Closed captions (#214). `captionsOn`: show caption cues in dialogue lines (default true); when\n * false the engine strips `captionOpen`…`captionClose` spans from line text. Mutable via\n * `setClosedCaptions` (one toggle, all flows - like setLocale). */\n captionsOn: boolean;\n captionOpen: string;\n captionClose: string;\n /** A cast member whose lines are pure captions: when captions are off ALL of its dialogue + speaker is\n * omitted (a silent line), delimiters or not. Default `SFX`. Empty = no caption character. */\n captionCharacter: string;\n /** Diagnostics hook (opt-in, dev only): fired when a choice runs DRY - nothing takeable and no eligible\n * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage\n * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */\n onDryChoice?: (groupId: string) => void;\n /** Where a content error the engine played through is reported (see PlayError). */\n onError: (error: PlayError) => void;\n /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's\n * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */\n refSplitCache: Map<string, { scope: string; name: string }>;\n refSplitRevision: number;\n}\n\n// ---------------------------------------------------------------------------\n// Engine - the world + flow manager\n// ---------------------------------------------------------------------------\n\nexport class Engine {\n private readonly host: FlowHost;\n private readonly defaultSeed: number;\n private readonly flowsById = new Map<string, Flow>();\n /** Every locale's string table (the inline `bundle.strings`), kept so the active locale can be swapped\n * live (setLocale) without rebuilding the engine. Reassigned wholesale by `replaceStrings`\n * (live bundle refresh, tier 1), hence not readonly. */\n private allStrings: Record<string, Record<string, string>>;\n /** The currently active locale (string lookups + character names resolve in it). */\n private currentLocale: string;\n /** True for a source-only DEBUG build (`localisation: { mode: \"ids\", sourceDebug: true }`) - the strings\n * are the source language, embedded only so the build can be played; not a shippable localised build. */\n private readonly sourceDebug: boolean;\n /** Host-facing addresses (spec §6): scene gameId -> internal id (project-wide), and per-scene\n * block gameId -> internal id. The effective gameId falls back to the name slug when unpinned. */\n private readonly sceneGameIdToId = new Map<string, string>();\n private readonly blockGameIdToId = new Map<string, Map<string, string>>();\n\n /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement\n * engine keeps the same host scopes, custom RNG, and diagnostic hooks. */\n private readonly creationOptions: EngineOptions;\n /** The run's ordered stream: every flow's events, each naming its flow. Empty and\n * unwritten unless `options.log` asked for it. */\n private readonly engineLog: EngineLogEntry[] = [];\n /** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */\n private engineSeq = 0;\n private readonly engineTraceHandlers = new Set<EngineTraceHandler>();\n\n constructor(bundle: Bundle, options: EngineOptions = {}) {\n this.creationOptions = options;\n const locale = options.locale ?? bundle.locales.default;\n const allStrings = bundle.strings;\n this.allStrings = allStrings;\n this.currentLocale = locale;\n const strings = allStrings[locale] ?? {};\n const defaultStrings = allStrings[bundle.locales.default] ?? {};\n // Localisation mode (spec §11). \"ids\" + no source-debug = the engine emits beat IDs (the game localises\n // itself). A source-debug build still resolves its embedded source strings, but is flagged for a warning.\n const loc = bundle.localisation;\n const emitIds = loc?.mode === \"ids\" && !loc.sourceDebug;\n this.sourceDebug = loc?.mode === \"ids\" && !!loc.sourceDebug;\n if (this.sourceDebug && typeof console !== \"undefined\") {\n console.warn(\"[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.\");\n }\n // Cast name -> displayName: the unlocalised fallback for a character's shown name when neither the\n // active nor the default locale carries a `cast:<name>` string.\n const castDisplay = new Map<string, string>();\n for (const c of bundle.cast ?? []) if (c.displayName) castDisplay.set(c.name, c.displayName);\n this.defaultSeed = (options.seed ?? 0x9e3779b9) >>> 0;\n\n const nodeIndex = new Map<string, SelectableNode>();\n const blockIndex = new Map<string, { sceneId: string }>();\n const blockById = new Map<string, CompiledBlock>();\n for (const [sceneId, scene] of Object.entries(bundle.scenes)) {\n this.sceneGameIdToId.set(effectiveGameId(scene), sceneId);\n const blockAddrs = new Map<string, string>();\n for (const block of scene.blocks) {\n blockIndex.set(block.id, { sceneId });\n blockById.set(block.id, block);\n blockAddrs.set(effectiveGameId(block), block.id);\n walkNodes<SelectableNode>(block.children, (n) => nodeIndex.set(n.id, n));\n }\n this.blockGameIdToId.set(sceneId, blockAddrs);\n }\n\n // Globals (`@patter`) split by the `shared` flag (default shared): shared ones\n // live in the engine's owned scope, per-flow ones seed each flow's registry.\n const props = bundle.properties ?? [];\n const patterSharedDecls = props.filter((p) => p.shared ?? true).map(toDecl);\n const patterLocalDecls = props.filter((p) => !(p.shared ?? true)).map(toDecl);\n const patterSharedNames = new Set(patterSharedDecls.map((d) => d.name.toLowerCase()));\n\n // \"@patter.\" so a row addresses itself the way getProperty takes it. `@gold` also\n // resolves - splitRef defaults an unqualified name to the patter scope - but the\n // qualified form is the address, and the shorthand is a shorthand.\n const registry = options.registry ?? new ScopeRegistry();\n const ownsRegistry = !options.registry || (options as InternalOptions)[OWNS_REGISTRY] === true;\n const patterBag = new PropertyBag(patterSharedDecls, { pathPrefix: \"@patter.\" });\n const hostScopes: string[] = [];\n const registered: string[] = [];\n try {\n registry.mountOwned(\"patter\", patterBag, { owner: OWNER }); // claims values the game loaded first\n registered.push(\"patter\");\n // Each scope the game binds is EXTERNAL: the game keeps the values, the registry never saves them.\n // Its declarations (types, read-only) come from the compiled bundle.\n const hostBound = new Set<string>();\n for (const [token, resolver] of Object.entries(boundScopes(options))) {\n const spec = bundle.scopeRegistry?.scopes.find((s) => s.token === token);\n const decls = (spec?.declarations ?? []).map(toForeignDecl);\n registry.defineForeign(token, resolver, decls, { writable: spec?.writable ?? true, owner: OWNER });\n registered.push(token);\n hostBound.add(token);\n }\n // A declared host scope nobody bound. A standalone engine is its own game, so it self-backs the scope:\n // a property bag seeded from the declarations, stored and SAVED by the registry like any other, since\n // only a resolver the game binds is external. Given the GAME's registry, the engine registers nothing\n // here: those tokens are the game's to register, or another engine's (a bundle compiled against the\n // Storylet Engine's spec declares `@story`), and self-backing one would clash with its real owner\n // depending only on which engine was built first.\n for (const spec of ownsRegistry ? bundle.scopeRegistry?.scopes ?? [] : []) {\n if (hostBound.has(spec.token) || registry.has(spec.token)) continue;\n const decls = (spec.declarations ?? []).map((d) => selfBackedDecl(d, spec.writable));\n registry.defineOwned(spec.token, decls, { owner: OWNER });\n registered.push(spec.token);\n hostScopes.push(spec.token);\n }\n } catch (e) {\n for (const k of registered) registry.remove(k, { keep: true }); // a clash leaves the game's registry as it was\n throw e;\n }\n\n // Scene props (`@scene`) split by `shared` (default per-flow): record, per\n // scene, which names are shared so a `@scene` ref routes to stage vs flow.\n const sceneSharedNames = new Map<string, Set<string>>();\n for (const [sceneId, scene] of Object.entries(bundle.scenes)) {\n const names = new Set((scene.sceneProps ?? []).filter((p) => p.shared ?? false).map((p) => p.name.toLowerCase()));\n sceneSharedNames.set(sceneId, names);\n }\n\n this.host = {\n logEnabled: options.log ?? false,\n tracing: options.log ?? false,\n emitEngine: (flow, event, scene) => this.emitEngine(flow, event, scene),\n bundle, emitIds, strings, defaultStrings, castDisplay, nodeIndex, blockIndex, blockById,\n sceneGameIdToId: this.sceneGameIdToId, blockGameIdToId: this.blockGameIdToId, // same instances the engine resolves with\n tagIndex: tagIndexFor(bundle), registry, ownsRegistry, patterBag, hostScopes,\n patterSharedDecls, patterLocalDecls, patterSharedNames, sceneSharedNames,\n sharedVisits: new Map(), qualityLadders: new Map(),\n sharedSelectors: new Map(),\n stageBags: new Map(),\n journal: null,\n customRng: options.rng,\n onDryChoice: options.onDryChoice,\n onError: options.onError ?? ((e) => console.warn(`Patterplay: ${e.kind} on ${e.node} in flow '${e.flow}' failed, played through: ${e.message}`)),\n replayPromptOnChoose: options.replayPromptOnChoose ?? false,\n captionsOn: options.closedCaptions ?? true, // captions shown by default (full text)\n captionOpen: (bundle.closedCaptions ?? DEFAULT_CAPTION_DELIMITERS).open,\n captionClose: (bundle.closedCaptions ?? DEFAULT_CAPTION_DELIMITERS).close,\n captionCharacter: bundle.closedCaptions?.character || DEFAULT_CAPTION_CHARACTER, // absent/empty -> SFX\n refSplitCache: new Map(),\n refSplitRevision: registry.revision,\n };\n }\n\n /** The active locale (string + character-name lookups resolve in it). */\n get locale(): string { return this.currentLocale; }\n\n /** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),\n * not a shippable localised build. An IDs-only ship build is `false`. */\n get isSourceDebug(): boolean { return this.sourceDebug; }\n\n /** The bundle's build identity (its content hash), the one a debug link handshakes with, or an empty\n * string for a bundle compiled without one. */\n get buildId(): string { return this.host.bundle.content?.hash ?? \"\"; }\n\n /**\n * Switch the active locale LIVE - a real game's \"language\" setting can change mid-session. Subsequent\n * string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new\n * locale; everything else - flow position, `@patter`/`@scene` state, visit counts, the PRNG - is\n * untouched (already-emitted text isn't retro-translated; that's the host's call). A locale with no\n * table resolves every string via the `<Untranslated: {id}>` source fallback. All open flows share the\n * engine's string table, so the swap reaches every flow at once.\n */\n setLocale(locale: string): void {\n this.currentLocale = locale;\n this.host.strings = this.allStrings[locale] ?? {};\n }\n\n /**\n * Live bundle refresh, tier 1 (strings only): swap every locale's string table in place from a\n * freshly compiled bundle whose STRUCTURE is unchanged (same `content.structureHash`). Like\n * setLocale, nothing restarts and no flow is touched: the next delivered beat reads the new text,\n * `{@ref}` slots re-interpolate, and beats the host already received keep the words it saw. The\n * swap reaches every open flow at once and is not part of save state. Structural edits need the\n * full save/load hot swap instead (a structure change here simply won't show).\n */\n replaceStrings(bundle: Bundle): void {\n this.allStrings = bundle.strings;\n this.host.strings = this.allStrings[this.currentLocale] ?? {};\n this.host.defaultStrings = this.allStrings[this.host.bundle.locales.default] ?? {};\n }\n\n /**\n * Live bundle refresh, tier 2 (full swap): rebuild on an edited bundle with the whole run carried\n * over. Snapshot (`saveGame`), construct a fresh engine on `bundle` with THIS engine's original\n * options (same world resolver, RNG, hooks), restore (`loadGame`), and carry over the presentation\n * state that deliberately isn't save state (active locale, closed-captions toggle). The\n * content-drift policy (§9.8) resolves edits under the cursor: stack frames re-find their next\n * child by id, drifted options drop, a vanished snippet is skipped.\n *\n * Returns the REPLACEMENT engine, on the same registry. This one hands its bags over (each is\n * removed from the registry with its values kept, and the replacement claims them as it\n * registers), its flows are closed, and it should be discarded. Hosts re-bind their flow handles\n * via `next.getFlow(id)`. If the restore throws (defensive - §9.8 makes this unreachable for\n * ordinary edits), the swap falls back to a fresh engine with each saved flow restarted from the\n * top of the scene it was in; the shared properties carry over.\n */\n hotSwap(bundle: Bundle): Engine {\n this.refuseInCheckpoint(\"hotSwap\");\n const snapshot = this.saveGame();\n const carryOver = (next: Engine): Engine => {\n next.setLocale(this.currentLocale);\n next.setClosedCaptions(this.host.captionsOn);\n return next;\n };\n // The replacement registers on the SAME registry, and a standalone engine's replacement is still its\n // own game (so its saveGame keeps carrying the registry's values).\n const make = (): Engine => new Engine(bundle, {\n ...this.creationOptions, registry: this.host.registry, [OWNS_REGISTRY]: this.host.ownsRegistry,\n } as InternalOptions);\n this.release(true);\n const next = make();\n try {\n next.loadGame(snapshot);\n return carryOver(next);\n } catch {\n // A partial load may have mutated `next`: hand its bags back, fall back on a THIRD engine and\n // restart each flow at the top of the scene it was in (dropped when that scene is gone too).\n next.release(true);\n const fresh = make();\n for (const [id, f] of Object.entries(snapshot.flows)) {\n const sceneId = f.cursor.currentSceneId;\n try { fresh.openFlow(id, sceneId !== null ? { scene: sceneId } : {}); } catch { /* scene deleted: drop the flow */ }\n }\n return carryOver(fresh);\n }\n }\n\n /** Whether closed captions are currently shown (full dialogue text). */\n get closedCaptions(): boolean { return this.host.captionsOn; }\n\n /**\n * Turn closed captions on/off LIVE (#214). When OFF, subsequent dialogue lines have their caption\n * cues (`[sigh]` etc., between the project's delimiters) and the surrounding whitespace stripped;\n * narration, choice prompts, and everything else are untouched. Like setLocale this is a presentation\n * toggle - it reaches every open flow at once and isn't part of save state; already-emitted text is\n * not retro-edited. An IDs-only game applies the same rule itself via `flow.stripCaptions`.\n */\n setClosedCaptions(on: boolean): void {\n this.host.captionsOn = on;\n }\n\n /**\n * Open (and start) a named flow. Each flow has its own cursor, PRNG, and per-flow\n * half of the scopes (not-shared `@patter`/`@scene`); all flows share the shared\n * half.\n *\n * Re-opening an existing id REPLACES it with a fresh flow, and CLOSES the old one\n * ({@link Flow.close}) so a host still holding it cannot keep driving the shared world. Replacing is\n * therefore a reset: that name's cursor, visit counts, selector cursors (so any shuffle / once-each\n * position) and per-flow properties all start over.\n *\n * Contrast {@link runFlow}, which REUSES a flow of the same name instead of replacing it - that is the\n * call to reach for when you want a speaker's variation state to carry on.\n *\n * THROWS on an address that does not resolve (an unknown scene, or a block that is not in the named\n * scene), before anything changes: nothing is opened, and a flow already open under `id` carries on\n * exactly as it was.\n */\n openFlow(id: string, opts: OpenFlowOptions = {}): Flow {\n this.assertExternalScopes();\n const { sceneId, blockId } = this.resolveOpenAddress(opts.scene, opts.block);\n const prior = this.flowsById.get(id);\n const journal = this.host.journal;\n if (journal && prior && !prior.isClosed) throw new Error(`openFlow would replace the open flow '${id}', which can't be undone while a checkpoint is open`);\n prior?.close(); // finish the flow this name used to mean\n const flow = new Flow(id, this.host, opts.seed ?? this.defaultSeed);\n if (journal) {\n // Opened inside the checkpoint: rolling back closes it and puts back whatever the name meant.\n journal.flows.add(flow);\n journal.opened.add(flow);\n journal.undo.push(() => {\n flow.close();\n if (prior) this.flowsById.set(id, prior); else this.flowsById.delete(id);\n });\n }\n this.flowsById.set(id, flow);\n flow.begin(sceneId, blockId);\n return flow;\n }\n\n /**\n * \"Play this address and give me everything it produced\" - the one-call form of the bark / one-shot\n * pattern. The NAMED flow is reused if it already exists (moved with {@link Flow.goto}) and opened at\n * the address if not, then run to its next stop, returning every beat it played.\n *\n * Calling it again with the SAME NAME does NOT replace the flow - it reuses it, and that is the whole\n * point. A flow owns its selector cursors, visit counts and per-flow properties, so reusing one lets a\n * **shuffle keep its bag** and an **\"once each\" list keep its place**: successive calls give the next\n * variation instead of replaying the first forever. (A fresh flow each time would reset all of it,\n * unless every such group happened to be authored `shared`.) Use one name per independent speaker;\n * different names never share per-flow state.\n *\n * This is exactly where it differs from {@link openFlow}, which REPLACES a flow of the same name and\n * so resets that variation state. Never mix the two on one name unless you mean to start over.\n *\n * Returns the played beats in order - `[]` means the address had nothing left to give (an exhausted\n * variation list, say), which is the signal to fall back to other content. It THROWS on an address\n * that does not resolve: unlike `goto` (a navigation primitive, where probing is legitimate), naming a\n * location here asserts it exists, and keeping `[]` unambiguous is worth more than a soft failure.\n *\n * A run that stops at a CHOICE returns the beats up to it and leaves the choice pending on the flow -\n * fetch it with `engine.getFlow(name)?.getChoices()`.\n */\n runFlow(flow: string, scene: string, block?: string): AdvanceToStopResult[\"played\"] {\n const existing = this.flowsById.get(flow);\n if (!existing) return this.openFlow(flow, { scene, block }).advanceToStop().played; // openFlow throws on a bad address\n if (!existing.goto(scene, block)) {\n throw new Error(`runFlow: address not found: ${scene}${block === undefined ? \"\" : ` / ${block}`}`);\n }\n return existing.advanceToStop().played;\n }\n\n /** Content that names another engine's scope (`@story.act`) runs only where that engine is on this\n * registry: without it every read would answer false and every write fail, so the flow is refused as\n * it opens (or the save as it loads), before anything changes. By then a game has built all of its\n * engines, whatever order it built them in. The same message on every runtime. */\n private assertExternalScopes(): void {\n for (const token of this.host.bundle.externalScopes ?? []) {\n if (!this.host.registry.has(token)) {\n throw new Error(`this content names @${token}, which no engine on this registry registered: `\n + `give every engine the game's one registry`);\n }\n }\n }\n\n /**\n * `openFlow`'s address as internal ids, or a throw when it does not resolve. With a scene, the block is\n * scene-scoped exactly as {@link Flow.goto} resolves it (a gameId address in that scene, or the internal\n * id of a block in that scene), so a block from another scene does not resolve. With no scene, the block\n * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.\n */\n private resolveOpenAddress(scene?: string, block?: string): { sceneId?: string; blockId?: string } {\n if (scene !== undefined) {\n const sceneId = resolveScene(this.host, scene);\n if (sceneId === undefined) throw new Error(`unknown scene: ${scene}`);\n if (block === undefined) return { sceneId };\n const blockId = resolveBlock(this.host, sceneId, block);\n if (blockId === undefined) throw new Error(`unknown block: ${block}`);\n return { sceneId, blockId };\n }\n if (block !== undefined) {\n if (!this.host.blockIndex.has(block)) throw new Error(`unknown block: ${block}`);\n return { blockId: block };\n }\n if (Object.keys(this.host.bundle.scenes).length === 0) throw new Error(\"no scenes in bundle\");\n return {};\n }\n\n /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an\n * unknown one passes through, for the caller to find nothing. */\n private resolveSceneRef(ref?: string): string | undefined {\n if (ref == null) return undefined;\n return resolveScene(this.host, ref) ?? ref;\n }\n\n /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its\n * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,\n * since a block's gameId is only unique within its scene. */\n private resolveBlockRef(sceneId: string | undefined, ref?: string): string | undefined {\n if (ref == null) return undefined;\n if (sceneId == null) return ref;\n return resolveBlock(this.host, sceneId, ref);\n }\n\n /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.\n * The inverse of the resolve helpers - for a host that wants to display / log the address. */\n sceneAddress(sceneId: string): string | undefined {\n const scene = this.host.bundle.scenes[sceneId];\n return scene ? effectiveGameId(scene) : undefined;\n }\n blockAddress(blockId: string): string | undefined {\n const block = this.host.blockById.get(blockId);\n return block ? effectiveGameId(block) : undefined;\n }\n\n /**\n * Author tags (#215) accumulated for a beat by id: its own tags unioned with every ancestor's\n * (scene → block → group(s) → snippet → beat), deduped, outermost-first. The same value the beat's\n * delivered step carries. Empty array for an unknown id or a beat with no tags anywhere up the chain.\n */\n tagsForBeat(beatId: string): string[] {\n return tagsOf(this.host, beatId) ?? [];\n }\n /** A scene's own tags (by internal id or gameId address). Empty when none / unknown. */\n tagsForScene(sceneRef: string): string[] {\n const id = this.resolveSceneRef(sceneRef);\n return (id != null ? tagsOf(this.host, id) : undefined) ?? [];\n }\n /** A block's accumulated tags (scene + block), by scene + block ref (id or gameId). Empty when none / unknown. */\n tagsForBlock(sceneRef: string, blockRef: string): string[] {\n const sceneId = this.resolveSceneRef(sceneRef);\n const id = this.resolveBlockRef(sceneId, blockRef);\n return (id != null ? tagsOf(this.host, id) : undefined) ?? [];\n }\n\n /**\n * A scene's own author gameData (by internal id or gameId address): the RAW sparse overrides, exactly\n * as a beat's step carries its own, not merged with the project's declared field defaults (resolve\n * those with `effectiveGameData(gameDataFields(bundle, \"scene\"), ...)`). Not inherited by its blocks.\n * A fresh object each call; empty when the scene sets none or the ref is unknown.\n */\n gameDataForScene(sceneRef: string): GameData {\n const id = this.resolveSceneRef(sceneRef);\n const scene = id != null ? this.host.bundle.scenes[id] : undefined;\n return { ...scene?.gameData };\n }\n /** A block's own author gameData, by scene + block ref (id or gameId). Raw overrides, like\n * {@link gameDataForScene}; the scene's gameData is not folded in. Empty when none / unknown. */\n gameDataForBlock(sceneRef: string, blockRef: string): GameData {\n const id = this.resolveBlockRef(this.resolveSceneRef(sceneRef), blockRef);\n const block = id != null ? this.host.blockById.get(id) : undefined;\n return { ...block?.gameData };\n }\n\n /**\n * Every cast member the PROJECT declares, in authored order - the same list `describeBundle` counts.\n * A superset of any scene's cast: the validator holds a beat's `character` to a declared member, so\n * {@link castForScene} and {@link castForBlock} only ever return names that appear here.\n */\n getCast(): string[] {\n // `cast` is absent from a bundle whose project declares none (the compiler omits the key), and a\n // nameless member is junk from a hand-edited bundle: both give an empty answer, not a throw.\n const names: string[] = [];\n for (const c of this.host.bundle.cast ?? []) if (c?.name) names.push(c.name);\n return names;\n }\n\n /**\n * A scene's cast: the `character` token of every speaker with a line anywhere in it, deduped, in\n * first-appearance order. Static, like {@link getOutline}: it walks the authored structure, so a\n * speaker behind a condition, inside any group, or voicing a choice prompt counts - this is who CAN\n * speak in the scene, not who a given playthrough heard. Empty for an unknown ref, or a scene with no\n * dialogue. Tokens, not display names: resolve those through the delivered step (`characterName`),\n * which is what follows `setLocale`.\n */\n castForScene(sceneRef: string): string[] {\n const id = this.resolveSceneRef(sceneRef);\n const scene = id != null ? this.host.bundle.scenes[id] : undefined;\n if (!scene) return [];\n const out = new Set<string>();\n for (const block of scene.blocks) collectCast(block.children, out);\n return [...out];\n }\n\n /** One block's cast, by scene + block ref (id or gameId). {@link castForScene} scoped to a block. */\n castForBlock(sceneRef: string, blockRef: string): string[] {\n const sceneId = this.resolveSceneRef(sceneRef);\n const id = this.resolveBlockRef(sceneId, blockRef);\n const block = id != null ? this.host.blockById.get(id) : undefined;\n if (!block) return [];\n const out = new Set<string>();\n collectCast(block.children, out);\n return [...out];\n }\n\n /**\n * The authored structure as a nested tree: scenes -> blocks -> children (groups + snippets, groups\n * preserved) -> a snippet's beats. Static (no flow / play state); per-beat data is read at the source\n * locale. For dev tooling that builds against the writer's structure (see also {@link getBeatSequence}).\n */\n getOutline(): OutlineScene[] {\n return Object.values(this.host.bundle.scenes).map((scene) => ({\n id: scene.id,\n ...(effectiveGameId(scene) ? { gameId: effectiveGameId(scene) } : {}),\n name: scene.name,\n ...gameDataField(scene.gameData),\n ...this.tagsField(scene.id),\n blocks: scene.blocks.map((block) => ({\n id: block.id,\n ...(effectiveGameId(block) ? { gameId: effectiveGameId(block) } : {}),\n name: block.name,\n ...gameDataField(block.gameData),\n ...this.tagsField(block.id),\n children: block.children.map((n) => this.outlineNode(n)),\n })),\n }));\n }\n\n /**\n * Every beat in document order, flattened (through groups), each with the scene / block / snippet it\n * belongs to and its static data. The linear view of {@link getOutline} - hand it to a tool that lays\n * one item per beat (e.g. an Unreal Sequencer of subsequences).\n */\n getBeatSequence(): FlatBeat[] {\n const out: FlatBeat[] = [];\n for (const scene of Object.values(this.host.bundle.scenes)) {\n for (const block of scene.blocks) {\n walkNodes<SelectableNode>(block.children, (n) => {\n if (n.type !== \"snippet\") return;\n for (const beat of n.beats ?? []) {\n out.push({ sceneId: scene.id, blockId: block.id, snippetId: n.id, beat: this.beatInfo(beat) });\n }\n });\n }\n }\n return out;\n }\n\n /** A node's outline entry: a group (selector + prompt + children) or a snippet (beats + jump). */\n private outlineNode(n: SelectableNode): OutlineNode {\n if (n.type === \"group\") {\n return {\n type: \"group\",\n id: n.id,\n ...this.tagsField(n.id),\n ...(n.selector ? { selector: n.selector } : {}),\n ...(n.prompt ? { prompt: this.beatInfo(n.prompt) } : {}),\n children: n.children.map((c) => this.outlineNode(c)),\n };\n }\n return {\n type: \"snippet\",\n id: n.id,\n ...this.tagsField(n.id),\n beats: (n.beats ?? []).map((b) => this.beatInfo(b)),\n ...(n.jump ? { jumpTo: n.jump.to, ...(n.jump.mode ? { jumpMode: n.jump.mode } : {}) } : {}),\n };\n }\n\n /** One beat's static data (source locale), the same shape a delivered step carries. */\n private beatInfo(beat: Beat): BeatInfo {\n const tags = tagsOf(this.host, beat.id);\n const info: BeatInfo = { id: beat.id, kind: beat.kind };\n if (beat.kind === \"line\") {\n if (beat.character !== undefined) {\n info.character = beat.character;\n const name = this.host.defaultStrings[castStringKey(beat.character)] ?? this.host.castDisplay.get(beat.character);\n if (name !== undefined) info.characterName = name;\n }\n if (beat.direction !== undefined) info.direction = beat.direction;\n }\n if (beat.kind === \"line\" || beat.kind === \"text\") {\n const source = this.host.defaultStrings[beat.id]; // source-locale text, un-interpolated\n if (source !== undefined) info.text = source;\n }\n if (beat.gameData && Object.keys(beat.gameData).length) info.gameData = beat.gameData;\n if (tags && tags.length) info.tags = tags;\n return info;\n }\n\n /** A `{ tags }` fragment for an id, present only when the id has accumulated tags (keeps output tidy). */\n private tagsField(id: string): { tags?: string[] } {\n const tags = tagsOf(this.host, id);\n return tags && tags.length ? { tags } : {};\n }\n\n /** Retrieve an open flow by id (undefined if none / closed). */\n getFlow(id: string): Flow | undefined {\n return this.flowsById.get(id);\n }\n\n /** All currently-open flows. */\n flows(): Flow[] {\n return [...this.flowsById.values()];\n }\n\n /**\n * The SHARED kernel bags with the path each answers to in a log: the `@patter` globals,\n * and one per scene for the shared `@scene` props. Parity with the Storylet Engine's\n * listBags of the same name - it is what a state logger mounts.\n *\n * A stage bag's log path is `@scene:<sceneId>.`, not the bag's own `@scene.`: a property\n * is ADDRESSED relative to a flow's current scene, but a log spans scenes and has to say\n * which one. That is why a mount may override the bag's prefix.\n *\n * loadGame() replaces every bag, so re-enumerate after a load.\n */\n listBags(): LogMount[] {\n const mounts: LogMount[] = [{ bag: this.host.patterBag }];\n for (const [sceneId, bag] of this.host.stageBags) mounts.push({ bag, pathPrefix: `@scene:${sceneId}.` });\n return mounts;\n }\n\n /**\n * Open a checkpoint: from here until `rollback` or `commit`, the engine keeps what it needs to undo\n * every change the story makes. `rollback` puts the game back exactly as it was at the checkpoint:\n * property values (every scope, including a host's `@world`, which is written back through the\n * game), visit counts, shuffle and sequence positions, `@scene` bags made since, every flow's cursor\n * and PRNG, and any flow opened since (closed and forgotten). `commit` keeps everything and stops\n * recording.\n *\n * The use is asking \"would this say anything?\" without consequences: move a flow to an address,\n * advance it, and roll back if it had nothing to give, so its scene's `onEntry` effects, its visits,\n * and its shuffle draws never happened.\n *\n * One checkpoint at a time. Inside one, the calls that replace or drop flows or their bags wholesale\n * (`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` on the name of an open flow, and a flow's own\n * `start`, `reset`, and `restore`) throw. Not undone:\n * trace and log events already emitted, and draws from a custom `rng` the game supplied.\n */\n checkpoint(): Checkpoint {\n if (this.host.journal) throw new Error(\"a checkpoint is already open: roll it back or commit it first\");\n const journal: Journal = { undo: [], flows: new Set(), opened: new Set(), selectors: new Set(), flowSelectors: new Map() };\n this.host.journal = journal;\n return new Checkpoint(journal);\n }\n\n /** Undo everything since `checkpoint` (see there), and close it. */\n rollback(checkpoint: Checkpoint): void {\n this.endCheckpoint(checkpoint);\n const undo = checkpoint.journal.undo;\n for (let i = undo.length - 1; i >= 0; i--) undo[i]!();\n Engine.forget(checkpoint.journal);\n }\n\n /** Keep everything since `checkpoint`, and close it. */\n commit(checkpoint: Checkpoint): void {\n this.endCheckpoint(checkpoint);\n Engine.forget(checkpoint.journal);\n }\n\n /** True while a checkpoint is open. */\n get inCheckpoint(): boolean { return this.host.journal !== null; }\n\n private endCheckpoint(checkpoint: Checkpoint): void {\n if (this.host.journal !== checkpoint.journal) throw new Error(\"that checkpoint is not the open one\");\n this.host.journal = null;\n }\n\n /** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */\n private static forget(journal: Journal): void {\n journal.undo = [];\n journal.flows.clear();\n journal.opened.clear();\n journal.selectors.clear();\n journal.flowSelectors.clear();\n }\n\n private refuseInCheckpoint(what: string): void {\n if (this.host.journal) throw new Error(`${what} can't be undone, so it isn't allowed while a checkpoint is open`);\n }\n\n /** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still\n * holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */\n closeFlow(id: string): void {\n this.refuseInCheckpoint(\"closeFlow\");\n this.flowsById.get(id)?.close();\n this.flowsById.delete(id);\n }\n\n /**\n * Reset the whole game to its initial state: drop every flow, re-seed the shared\n * `@patter` globals to their declared defaults, and clear all shared state (shared\n * `@scene` bags, world visit counts). World properties are host-owned and untouched.\n * After reset, open fresh flows with `openFlow`.\n */\n reset(): void {\n this.refuseInCheckpoint(\"reset\");\n for (const flow of this.flowsById.values()) flow.close(); // finish them, don't just forget them\n this.flowsById.clear();\n this.host.patterBag.reseed(this.host.patterSharedDecls);\n this.host.sharedVisits.clear();\n this.host.sharedSelectors.clear();\n for (const s of this.host.stageBags.keys()) this.host.registry.remove(keys.stage(s));\n this.host.stageBags.clear();\n // Values loaded for bags nobody has claimed yet are the old game's too: a flow opened after the\n // reset must not pick them up. Other engines' parked values are theirs, and stay.\n this.host.registry.discardParked(\"patter/\");\n }\n\n /** Remove every bag this engine registered, keeping the values parked when `keep` (a live reload\n * handing its state to a replacement), and close its flows. The engine is inert afterwards. */\n private release(keep: boolean): void {\n for (const flow of this.flowsById.values()) { flow.releaseBags(keep); flow.close(); }\n this.flowsById.clear();\n const reg = this.host.registry;\n for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep });\n this.host.stageBags.clear();\n for (const t of [\"patter\", ...this.host.hostScopes]) if (reg.has(t)) reg.remove(t, { keep });\n for (const t of Object.keys(boundScopes(this.creationOptions))) if (reg.has(t)) reg.remove(t);\n }\n\n /** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */\n getProperty(ref: string): ScalarValue | undefined {\n const { scope, name } = this.splitShared(ref);\n return this.host.registry.get(scope, name);\n }\n\n /** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level).\n *\n * This is the GAME's surface, so it writes with host authority: a host-scope declaration's\n * `writable: false` is the story's promise not to write the value, never a lock on the game that\n * owns it. The story's own writes come through `Flow.runEffects`, which never claims it. (Ruled\n * across the family 2026-09-05, after a Storylets venue clock and this project's coverage driver\n * were each refused the one property they existed to move.) */\n setProperty(ref: string, value: ScalarValue): void {\n const { scope, name } = this.splitShared(ref);\n const prev = this.host.journal ? this.host.registry.get(scope, name) : undefined;\n this.host.registry.set(scope, name, value, { host: true });\n if (this.host.journal && prev !== undefined) this.host.journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));\n }\n\n /** The run's decisions, in order, each naming the flow it happened in. Empty unless the\n * run was opened with `log: true`. A flow's own log stays flow-local; this is the only\n * place a story spanning several flows reads as one sequence. */\n log(): readonly EngineLogEntry[] {\n return this.engineLog;\n }\n\n /** Drop the retained entries. `seq` does NOT restart: two reads of a log either side of a\n * clear still agree about what came first. */\n clearLog(): void {\n this.engineLog.length = 0;\n }\n\n /** Live tap on the run's decisions, for tooling that wants them as they happen rather than\n * retained. Returns its own unsubscribe. */\n onTrace(handler: EngineTraceHandler): () => void {\n this.engineTraceHandlers.add(handler);\n this.host.tracing = true;\n return () => {\n this.engineTraceHandlers.delete(handler);\n this.host.tracing = this.host.logEnabled || this.engineTraceHandlers.size > 0;\n };\n }\n\n private emitEngine(flow: string, event: TraceEvent, scene?: string): void {\n for (const h of this.engineTraceHandlers) h(flow, event);\n if (!this.host.logEnabled) return;\n this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...(scene ? { scene } : {}) });\n }\n\n /** How many times each node has been entered across every flow, by node id: the shared count. With a\n * flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */\n getVisitCounts(): Record<string, number> {\n return Object.fromEntries(this.host.sharedVisits);\n }\n\n /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current\n * value, declared default (for reset), and enum options. Every runtime has the same call. */\n listProperties(): PropertyRow[] {\n return this.host.patterSharedDecls.map((d) => ({\n name: d.name,\n // The qualified address, matching what the bag composes for every other scope.\n // `@gold` still resolves on input; it is the shorthand, not the address.\n path: `@patter.${d.name}`,\n type: d.type as PropertyType,\n values: d.values,\n stages: d.stages,\n value: this.getProperty(`@${d.name}`),\n default: defaultFor(d),\n // Part of the shared row. Always true here today: `toDecl` never sets it, because\n // Patter has no read-only shared property. The Storylet Engine does declare them\n // and its panels disable the editor accordingly, so the field is carried rather\n // than dropped - and the day a read-only @patter property exists, the row says so.\n writable: d.writable ?? true,\n }));\n }\n\n // @scene is scene-namespaced and needs a flow's current scene - silently\n // routing it into the shared bag (as a junk \"scene.x\" key) was a trap.\n private splitShared(ref: string): { scope: string; name: string } {\n const split = splitHostRef(this.host, ref);\n if (split.scope === \"scene\") {\n throw new Error(`'${ref}': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);\n }\n return split;\n }\n\n /** The shared `@patter` values alone, as `{ patter: {...} }`.\n * @deprecated The registry holds every property now: a game saves `registry.save()` once, and a\n * standalone engine's `saveGame()` carries its registry's values. Removed at the next breaking release. */\n save(): EngineSave {\n return { patter: this.host.patterBag.save() };\n }\n\n /** Lay a `{ patter: {...} }` section over the shared `@patter` values; anything else is ignored.\n * @deprecated See `save`. Removed at the next breaking release. */\n load(blob: EngineSave): void {\n if (blob.patter) this.host.patterBag.load(blob.patter);\n }\n\n /** Snapshot the whole game's NON-property state: visit counts, shared selector cursors, and every\n * live flow's cursor and PRNG. The property values are the registry's: a standalone engine (one that\n * made its own registry) carries them here under `registry`; a game that passed a registry saves it\n * once itself, beside each engine's `saveGame()`. */\n saveGame(): SaveGame {\n const flows: Record<string, FlowSnapshot> = {};\n for (const [id, flow] of this.flowsById) flows[id] = flow.snapshot();\n return {\n version: SAVE_VERSION,\n ...(this.host.ownsRegistry ? { registry: this.host.registry.save() } : {}),\n sharedVisits: Object.fromEntries(this.host.sharedVisits),\n sharedSelectors: serialiseSelectors(this.host.sharedSelectors),\n flows,\n };\n }\n\n /**\n * Restore a `saveGame()`: visit counts, shared selector cursors, and every flow. Property values come\n * from the registry. A save that carries them (a standalone engine's, or a version 2 save from before\n * the registry held them) has them moved into the registry here; otherwise the game loads its\n * registry itself, before or after this call. Either order works: this engine's bags are handed back\n * to the registry (values kept) and the restored flows claim them as they register.\n */\n loadGame(save: SaveGame | SaveGameV2): void {\n this.refuseInCheckpoint(\"loadGame\");\n const version: unknown = (save as { version?: unknown }).version;\n if (version !== 2 && version !== SAVE_VERSION) throw new Error(`unsupported save version: ${String(version)}`);\n // Everything a load reads is checked BEFORE anything changes: a save missing its flows used to close\n // every flow and load the registry, then fail, leaving the engine half-loaded.\n const flows: unknown = (save as { flows?: unknown }).flows;\n if (typeof flows !== \"object\" || flows === null || Array.isArray(flows)) throw new Error(\"malformed save: no flows\");\n this.assertExternalScopes();\n const reg = this.host.registry;\n // Flows the save does not have are over: their bags go. The rest are handed back with their values,\n // which is what a game that loaded its registry first has just laid the save's values over.\n for (const [id, flow] of this.flowsById) { flow.releaseBags(id in save.flows); flow.close(); }\n this.flowsById.clear();\n for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep: true });\n this.host.stageBags.clear();\n\n const values = save.version === 2 ? sectionsFromV2(save) : save.registry;\n if (values) {\n // The engine's own registry takes the save wholesale. A game's registry may hold values the game\n // loaded for other engines, still waiting to be claimed: add to those, never replace them.\n if (this.host.ownsRegistry) reg.load(values);\n else reg.load(values, { keepParked: true });\n }\n this.host.sharedVisits.clear();\n for (const [id, n] of Object.entries(save.sharedVisits ?? {})) this.host.sharedVisits.set(id, n);\n this.host.sharedSelectors.clear();\n for (const [id, st] of deserialiseSelectors(save.sharedSelectors)) this.host.sharedSelectors.set(id, st);\n for (const [id, snap] of Object.entries(save.flows)) {\n const flow = new Flow(id, this.host, this.defaultSeed);\n flow.restore(snap);\n this.flowsById.set(id, flow);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Flow - one playable flow (cursor + the per-flow half of @patter/@scene + PRNG)\n// ---------------------------------------------------------------------------\n\nexport class Flow {\n readonly id: string;\n private readonly host: FlowHost;\n private local: PropertyBag; // the NOT-shared `@patter` globals (this flow's copy), keys.flowGlobals\n /** The registry keys this flow has registered (its globals and each scene bag). */\n private readonly registered = new Set<string>();\n private rngState: number;\n\n // Execution cursor. The `stack` is the continuation stack: each frame is a\n // position within a block's children (the top frame is the active block run;\n // lower frames are pending call-returns). A snippet's beats deliver from\n // `activeSnippet`/`beatIndex`.\n private started = false;\n private flowEnded = false;\n /** Closed by the engine (see `close()`). Terminal, and distinct from `flowEnded`: an ENDED flow is\n * merely out of content and `goto` revives it; a CLOSED one is finished for good. */\n private closed = false;\n private currentSceneId: string | null = null;\n private stack: StackFrame[] = [];\n private activeSnippet: CompiledSnippet | null = null;\n private beatIndex = 0;\n private pendingChoice: ChoiceState | null = null;\n /** When `replayPromptOnChoose`, the chosen option's AUTHORED prompt to deliver before its content: its\n * beat (id, gameData, tags) and the prompt exactly as the choice showed it (text and speaker fields).\n * `shown` is missing only after loading a save written before saves carried it: the beat is then\n * resolved when it is delivered, as it always was. */\n private pendingPrompt: { beat: LineBeat | TextBeat; shown?: ChoicePrompt } | null = null;\n /** The chosen option that owns `pendingPrompt`, so a save taken between choose() and the next\n * advance() can find the prompt beat again on load (it isn't otherwise reachable by id). */\n private pendingPromptOwnerId: string | null = null;\n private selectors = new Map<string, SelectorState>();\n /** Per-node entry counts for this flow (node id -> times entered). */\n private visitCounts = new Map<string, number>();\n\n // Per-flow halves of the two scopes. The NOT-shared `@patter` globals live in\n // `local`; the NOT-shared `@scene` props live in `sceneBags` (namespaced per\n // scene; they PERSIST across re-entries, spec §7). The SHARED halves live on the\n // host (`host.patterBag` / `host.stageBags`). All of them are registered in the\n // game's registry. Each resolver presents one merged scope, routing each property\n // to its half by the declared `shared` flag: that split is why `@patter` and\n // `@scene` are composed here rather than aliased to a single registry key.\n /** This flow's per-scene LOCAL scene props; see FlowHost.stageBags. */\n private sceneBags = new Map<string, PropertyBag>();\n\n private readonly patterResolver: ScopeResolver = {\n get: (n) => (this.host.patterSharedNames.has(n) ? this.host.patterBag.get(n) : this.local.get(n)),\n set: (n, v) => {\n if (this.host.patterSharedNames.has(n)) this.host.registry.set(\"patter\", n, v);\n else this.local.set(n, v);\n },\n };\n\n private readonly sceneResolver: ScopeResolver = {\n get: (n) => {\n const s = this.currentSceneId;\n if (s === null) return undefined;\n return this.sceneBagFor(s, n)?.get(n);\n },\n set: (n, v) => {\n const s = this.currentSceneId;\n if (s === null) return;\n const bag = this.sceneBagFor(s, n);\n // NOT silent, which is the bag's rule rather than an accident: an engine write\n // notifies subscribers and is audited, where a HOST write (an inspector poking a\n // value) is silent but still audited. A scene-prop write during play is the\n // engine's own, so it notifies.\n if (bag) bag.set(n, v);\n },\n };\n\n // The eval context is built once and REFRESHED only when the registry's set of\n // scopes moves (its `revision`): every constituent resolves live state at call\n // time (bags mutate in place per scoperegistry's contract; patter/scene route\n // through this flow's resolvers, which read the current `local`/`sceneBags`/\n // `currentSceneId`; the host callbacks read current flow fields), but another\n // engine registering `@story` after this flow opened must still be readable.\n // Rebuilding it per evaluation was the engine's hottest allocation.\n private readonly evalCtx: EvalContext;\n private ctxRevision = -1;\n private registryQualities: EvalContext[\"qualities\"];\n private readonly flowLog: LogEntry[] = [];\n /** Monotonic across the flow's life; survives clearLog so two reads agree on order. */\n private flowSeq = 0;\n\n constructor(id: string, host: FlowHost, seed: number) {\n this.id = id;\n this.host = host;\n this.rngState = toUint32(seed);\n this.local = this.newLocal(); // registered by start() / restore()\n\n this.evalCtx = {\n scopes: {},\n host: {\n nextRandom: this.rng,\n visits: (id: string) => this.visitCounts.get(id) ?? 0,\n patterVisits: (id: string) => this.host.sharedVisits.get(id) ?? 0,\n },\n // The quality channel (expr 0.4.0): hands the evaluator a property's stage ladder, which is what\n // makes ordering compare by position and advance() step. Wired by hand because this context takes\n // only the registry's SCOPES (the patter/scene resolvers here are the flow's own merged views),\n // and because @scene declarations belong to whichever scene the flow is in RIGHT NOW.\n qualities: (scope, name) => this.stagesFor(scope, name),\n };\n }\n\n /** The eval context, its scopes refreshed if the registry's set of scopes has moved since. */\n private context(): EvalContext {\n const reg = this.host.registry;\n if (reg.revision !== this.ctxRevision) {\n const base = reg.toEvalContext();\n const scopes = this.evalCtx.scopes;\n for (const k of Object.keys(scopes)) delete scopes[k];\n Object.assign(scopes, base.scopes); // every registered scope: other engines' too\n scopes[\"patter\"] = this.patterResolver; // override with the merged shared+per-flow views\n scopes[\"scene\"] = this.sceneResolver;\n this.registryQualities = base.qualities;\n this.ctxRevision = reg.revision;\n }\n return this.evalCtx;\n }\n\n /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare\n * lowercase, as the compiler emits references (the selfBackedResolver lesson). */\n private stagesFor(scope: string, name: string): readonly string[] | undefined {\n const key = name.toLowerCase();\n if (scope === \"patter\") {\n return ladders(this.host, \"patter\", () => [...this.host.patterSharedDecls, ...this.host.patterLocalDecls]).get(key);\n }\n if (scope === \"scene\") {\n const sceneId = this.currentSceneId;\n if (sceneId == null) return undefined;\n return ladders(this.host, `scene/${sceneId}`, () => this.host.bundle.scenes[sceneId]?.sceneProps).get(key);\n }\n // Any other scope's ladder is the registry's (another engine's `@story`, the game's `@world`), with\n // the bundle's own host-scope declarations behind it for a game that registered `@world` undeclared.\n return this.registryQualities?.(scope, name)\n ?? ladders(this.host, `host/${scope}`, () => this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations).get(key);\n }\n\n // -- Host API -------------------------------------------------------------\n\n /** Begin this flow at a scene (and optionally a specific block within it). The engine's own entry point:\n * openFlow and a goto on an unstarted flow begin a flow here. A game calls {@link reset}.\n * @internal */\n begin(sceneId?: string, blockId?: string): void {\n // Starting resets this flow's property bags, which a rollback can't put back: only a flow opened\n // inside the checkpoint may start in one.\n if (this.host.journal && !this.host.journal.opened.has(this)) throw new Error(\"a flow can't be started or reset while a checkpoint is open\");\n // A start is a reset: this flow's bags go, and so does anything a load left waiting for them.\n this.releaseBags(false);\n this.host.registry.discardParked(keys.flow(this.id));\n this.mountLocal();\n this.selectors.clear();\n this.visitCounts.clear();\n this.stack = [];\n this.currentSceneId = null;\n this.flowEnded = false;\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.clearPending();\n this.started = true;\n\n if (blockId) {\n const loc = this.host.blockIndex.get(blockId);\n if (!loc) throw new Error(`unknown block: ${blockId}`);\n this.enterSceneSetup(loc.sceneId);\n this.stack = [{ sceneId: loc.sceneId, containerId: blockId, index: 0 }];\n this.enter(blockId);\n } else {\n const id = sceneId ?? Object.keys(this.host.bundle.scenes)[0];\n const scene = id ? this.host.bundle.scenes[id] : undefined;\n if (!scene) throw new Error(id ? `unknown scene: ${id}` : \"no scenes in bundle\");\n this.enterSceneSetup(id!);\n const first = scene.blocks[0];\n if (first) { this.stack = [{ sceneId: id!, containerId: first.id, index: 0 }]; this.enter(first.id); }\n }\n this.settle();\n }\n\n /**\n * Forget everything in this flow and begin again - its per-flow state (not-shared\n * `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and\n * visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is\n * untouched. The one public way to begin a flow again (openFlow begins a new one).\n */\n reset(sceneId?: string, blockId?: string): void {\n this.begin(sceneId, blockId);\n }\n\n /** @deprecated Use {@link reset}, the one public name for beginning a flow again on every runtime. `start`\n * was the same call under a second name, and goes in a later release. */\n start(sceneId?: string, blockId?: string): void {\n this.reset(sceneId, blockId);\n }\n\n /**\n * Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's\n * `onEntry` effects run, entering counts as a visit, and the callstack is REPLACED - any pending\n * `call` returns are discarded, just as a goto inside a call does.\n *\n * `scene` and `block` are host-facing gameIds (spec §6) or internal ids; `block` is scene-scoped, so\n * it is looked up within `scene`. `\"END\"` ends the flow. To move within the current scene, pass the\n * current scene's address again (`flow.currentScene` -> `engine.sceneAddress`).\n *\n * This is HOST navigation, not authoring, and it takes effect IMMEDIATELY: any beats left in the\n * snippet being delivered are abandoned, and a pending choice is dropped. The format stops an AUTHOR\n * writing a divert into the middle of a snippet; a host teleport is out-of-band, like `reset()` or\n * `loadGame()`. A flow that never started starts here; one that already ended resumes here.\n *\n * Returns false - leaving the cursor exactly where it was - if the address does not resolve. Per-flow\n * state (properties, visit counts, selector cursors) is untouched either way: this MOVES, never resets.\n */\n goto(scene: string, block?: string): boolean {\n if (this.closed) return false; // closed is terminal: unlike \"ended\", a goto cannot revive it\n this.touch();\n if (scene === \"END\") {\n this.started = true; this.clearPending();\n this.activeSnippet = null; this.beatIndex = 0;\n this.flowEnded = true; this.stack = [];\n return true;\n }\n // Resolve BOTH addresses before touching any state, so a bad one is a no-op rather than a half-move.\n const sceneId = resolveScene(this.host, scene);\n if (sceneId === undefined) return false;\n let blockId: string | undefined;\n if (block !== undefined) {\n blockId = resolveBlock(this.host, sceneId, block);\n if (blockId === undefined) return false; // a block address is scene-scoped: unknown HERE is unknown\n }\n // Never started: start() does the same landing plus the one-time per-flow setup.\n if (!this.started) { this.begin(sceneId, blockId); return true; }\n\n this.clearPending();\n this.activeSnippet = null; this.beatIndex = 0; // abandon the rest of the snippet being delivered\n this.flowEnded = false; // an ended flow resumes at the target\n this.enterTarget(blockId ?? sceneId, \"jump\"); // \"jump\" = replace the stack, exactly like an authored goto\n this.settle();\n return true;\n }\n\n /**\n * Finish this flow for good. Engine-managed: `engine.closeFlow(id)`, `engine.reset()`, and re-opening\n * a name with `engine.openFlow` all call it on the flow being dropped.\n *\n * A dropped flow used to stay fully live: unregistered and invisible to `engine.flows()`, but a host\n * still holding the object could keep advancing it, and every scene `onEntry`, shared property, world\n * visit count and shared selector cursor it touched still landed on the engine. Closing makes that\n * stale reference inert - `advance()` reports the end and `goto()` refuses - so a forgotten reference\n * cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.\n */\n close(): void {\n this.releaseBags(false);\n this.closed = true;\n this.flowEnded = true;\n this.stack = [];\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.clearPending();\n }\n\n /**\n * Drop everything waiting to be delivered: an open choice, and a chosen option's prompt still to be\n * replayed. Every move that abandons the flow's place (start and reset, goto, close) does this, so none\n * leaves a stale prompt behind. start() cleared only the choice, so a reset between choose() and the\n * next advance() replayed the abandoned run's prompt as the new run's first beat.\n */\n private clearPending(): void {\n this.pendingChoice = null;\n this.pendingPrompt = null;\n this.pendingPromptOwnerId = null;\n }\n\n /** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can\n * put them back: a handful of fields and a copy of the call stack (a few frames), never its history.\n * Its visits, selector cursors, and property values are recorded change by change as they happen. */\n private touch(): void {\n const journal = this.host.journal;\n if (!journal || journal.flows.has(this)) return;\n journal.flows.add(this);\n const { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPrompt, pendingPromptOwnerId } = this;\n const stack = this.stack.map((f) => ({ ...f })); // frames advance in place, so copy them\n journal.undo.push(() => {\n Object.assign(this, { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPrompt, pendingPromptOwnerId, stack });\n });\n }\n\n /** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */\n get isClosed(): boolean { return this.closed; }\n\n /** The scene the cursor is currently in - set on entry and whenever a jump crosses scenes. Read\n * right after `advance()` to know which scene the just-played beat lives in (tooling that mirrors\n * the playhead, e.g. an editor following a cross-scene jump). `null` before the flow has started. */\n get currentScene(): string | null { return this.currentSceneId; }\n\n /** Run until the next line, game event, choice, or the end of the flow. */\n advance(): StepResult {\n if (this.closed) return { type: \"end\" }; // a stale reference to a closed flow drives nothing\n if (!this.started) throw new Error(\"flow has not been started\");\n this.touch();\n // A replayed prompt (replayPromptOnChoose) is delivered first, before the option's content.\n if (this.pendingPrompt) { const p = this.pendingPrompt; this.pendingPrompt = null; this.pendingPromptOwnerId = null; return p.shown ? this.promptResult(p.beat, p.shown) : this.beatResult(p.beat); }\n this.settle();\n if (this.flowEnded) return { type: \"end\" };\n if (this.pendingChoice) return { type: \"choice\", groupId: this.pendingChoice.groupId, options: this.pendingChoice.options };\n if (!this.activeSnippet) { this.flowEnded = true; return { type: \"end\" }; }\n return this.beatResult(this.activeSnippet.beats![this.beatIndex++]!);\n }\n\n /**\n * Advance repeatedly, collecting every played beat, until a choice or the end - the \"play to the\n * next stop\" a host's play UI / tooling wants. The terminal `choice` / `end` is returned as `stop`;\n * `played` holds the line / text / game-event results walked on the way to it. Termination is guaranteed\n * (each `advance()` makes progress or `settle()` throws on a contentless jump cycle).\n */\n advanceToStop(): AdvanceToStopResult {\n const played: AdvanceToStopResult[\"played\"] = [];\n for (;;) {\n const r = this.advance();\n if (r.type === \"choice\" || r.type === \"end\") return { played, stop: r };\n played.push(r); // narrowed to line / text / game-event by the guard above\n }\n }\n\n /**\n * Drive the cursor to the next *deliverable* stop: a beat ready on the active\n * snippet, a pending choice, or the end. Runs onExit/jump seams and walks the\n * block run (sequentially, skipping ineligible children); a finished block pops\n * to its caller (call-return) or ends the flow.\n */\n private settle(): void {\n let transitions = 0;\n for (;;) {\n // Static validation cannot rule out jump cycles (conditions gate them),\n // so a content bug like two pure jumps jumping at each other must be an\n // error, not a hang.\n if (++transitions > 10_000) {\n throw new Error(\"flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content\");\n }\n if (this.flowEnded || this.pendingChoice) return;\n\n if (this.activeSnippet) {\n if (this.beatIndex < (this.activeSnippet.beats?.length ?? 0)) return; // a beat is ready\n this.runEffects(this.activeSnippet.onExit, this.activeSnippet.id);\n const jump = this.activeSnippet.jump;\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.resolveJump(jump);\n continue;\n }\n\n const frame = this.stack[this.stack.length - 1];\n if (!frame) { this.flowEnded = true; return; }\n if (frame.sceneId !== this.currentSceneId) this.currentSceneId = frame.sceneId; // resumed scene (no reseed)\n const children = this.childrenOf(frame.containerId);\n if (!children) { this.stack.pop(); continue; } // drifted container -> skip the frame\n // A `run` container walks its children in order, skipping the ones whose condition\n // does not hold. That skip IS the decision an author asks about - \"why did my line\n // not appear\" - so the trace records the ones it walked past on the way, not only\n // the one it entered. selectChild covers branch/sequence; this covers the default,\n // which is what a block is.\n const from = frame.index;\n while (frame.index < children.length && !this.eligible(children[frame.index]!)) frame.index++;\n if (this.host.tracing && frame.index !== from) {\n this.emit({\n type: \"select\", group: frame.containerId, selector: \"run\",\n children: children.slice(from, frame.index + 1)\n .map((c, i) => ({ id: c.id, eligible: from + i === frame.index })),\n picked: children[frame.index]?.id ?? null,\n });\n }\n if (frame.index >= children.length) { this.stack.pop(); continue; } // run exhausted -> resume caller\n this.enterChild(children[frame.index++]!); // advance past it: that's the gather/return point\n }\n }\n\n /**\n * THIS flow's own kernel bags: its not-shared `@patter` half and its per-scene `@scene`\n * props, each prefixed with the flow id so one path space holds every flow. The shared\n * halves are the Engine's listBags.\n */\n listBags(): LogMount[] {\n const mounts: LogMount[] = [{ bag: this.local, pathPrefix: `${this.id}/@patter.` }];\n for (const [sceneId, bag] of this.sceneBags) mounts.push({ bag, pathPrefix: `${this.id}/@scene:${sceneId}.` });\n return mounts;\n }\n\n /** This flow's decisions, in order. Empty unless the run was opened with `log: true`.\n * The engine's log carries the same events tagged with the flow; this one is what a\n * single conversation reads as. */\n log(): readonly LogEntry[] {\n return this.flowLog;\n }\n\n /** Drop the retained entries. `seq` keeps counting, so order survives a clear. */\n clearLog(): void {\n this.flowLog.length = 0;\n }\n\n /** Record one decision, on this flow's log and the engine's, and hand it to any `onTrace`. Each call\n * site checks `host.tracing` first, so with nothing logging or tracing the entry is never built. */\n private emit(event: TraceEvent): void {\n const scene = this.currentSceneId ?? undefined;\n this.host.emitEngine(this.id, event, scene);\n if (!this.host.logEnabled) return;\n this.flowLog.push({ ...event, seq: this.flowSeq++, ...(scene ? { scene } : {}) });\n }\n\n /** How many times this flow has entered each node, by node id. */\n getVisitCounts(): Record<string, number> {\n return Object.fromEntries(this.visitCounts);\n }\n\n /** The options of a pending choice (empty when not at a choice point). */\n getChoices(): ChoiceOption[] {\n return this.pendingChoice?.options ?? [];\n }\n\n /** Pick an eligible option by id; the next `advance()` runs it. */\n choose(id: string): void {\n const choice = this.pendingChoice;\n if (!choice) throw new Error(\"no choice is pending\");\n const option = choice.options.find((o) => o.id === id);\n if (!option) throw new Error(`unknown choice option: ${id}`);\n if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);\n this.touch();\n const node = choice.byId.get(id)!;\n if (this.host.tracing) this.emit({ type: \"chose\", group: choice.groupId, option: id });\n this.pendingChoice = null;\n // Optionally speak the chosen option's prompt back as its first beat (spec §5): only an AUTHORED\n // prompt, and exactly as the choice showed it. A prompt borrowed from the option's own first content\n // line is not replayed, since that line is about to play as content anyway.\n const authored = this.host.replayPromptOnChoose ? this.authoredPromptOf(node) : undefined;\n this.pendingPrompt = authored && option.prompt ? { beat: authored, shown: { ...option.prompt } } : null;\n this.pendingPromptOwnerId = this.pendingPrompt ? node.id : null;\n // The block frame is already advanced past the choice group (the gather point),\n // so when the chosen option finishes without a jump, the flow continues there.\n this.enterChild(node);\n }\n\n isEnded(): boolean {\n return this.flowEnded;\n }\n\n /** Read a property by ref - `@patter` / `@scene` (each routed by its `shared` flag) or foreign. */\n getProperty(ref: string): ScalarValue | undefined {\n const { scope, name } = this.splitRef(ref);\n if (scope === \"patter\") return this.patterResolver.get(name);\n if (scope === \"scene\") return this.sceneResolver.get(name);\n return this.host.registry.get(scope, name); // host scopes, other engines' scopes\n }\n\n /** Write a property by ref (routed by scope, then by the property's `shared` flag). The GAME's\n * surface: it writes with host authority, so a `writable: false` host declaration does not refuse\n * it (see `Engine.setProperty`). The story's writes go through `writeProperty` instead. */\n setProperty(ref: string, value: ScalarValue): void {\n this.writeProperty(ref, value, true);\n }\n\n /** The write itself. `host` says WHO is writing, which is the only thing `writable: false` cares\n * about: the story is refused, the game is not. */\n private writeProperty(ref: string, value: ScalarValue, host: boolean): void {\n const { scope, name } = this.splitRef(ref);\n const journal = this.host.journal;\n if (scope === \"patter\") {\n // Inside a checkpoint, each write records how to put the old value back, against the bag it\n // actually landed in (a `@scene` write's bag depends on the scene the flow is in at the time).\n if (journal) {\n const shared = this.host.patterSharedNames.has(name);\n const prev = shared ? this.host.patterBag.get(name) : this.local.get(name);\n const bag = this.local;\n if (prev !== undefined) journal.undo.push(shared ? () => this.host.registry.set(\"patter\", name, prev) : () => bag.set(name, prev));\n }\n this.patterResolver.set!(name, value);\n } else if (scope === \"scene\") {\n // The resolver stays graceful for expression evaluation, but a host write\n // with nowhere to land must error, not silently vanish.\n if (this.currentSceneId === null) throw new Error(`'${ref}': the flow has not entered a scene yet`);\n if (journal) {\n const bag = this.sceneBagFor(this.currentSceneId, name);\n const prev = bag?.get(name);\n if (bag && prev !== undefined) journal.undo.push(() => bag.set(name, prev));\n }\n this.sceneResolver.set!(name, value);\n } else {\n const prev = journal ? this.host.registry.get(scope, name) : undefined;\n this.host.registry.set(scope, name, value, host ? { host: true } : undefined); // host / other scopes\n // Recorded after the write: one a read-only host scope refused never happened, so has nothing to undo.\n if (journal && prev !== undefined) journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));\n }\n }\n\n // -- Save / restore (engine-driven) --------------------------------------\n\n /** @internal Snapshot this flow's cursor + PRNG + visits. Its properties are the registry's. */\n snapshot(): FlowSnapshot {\n return {\n rngState: this.rngState,\n visits: Object.fromEntries(this.visitCounts),\n cursor: {\n flowEnded: this.flowEnded,\n currentSceneId: this.currentSceneId,\n // Stamp each frame with the id of the child it would run next (nextId), so a restore against\n // an EDITED bundle re-finds the position by id instead of trusting the raw index (§9.8 /\n // live bundle refresh). A frame saved at its container's end has no next child - no stamp.\n stack: this.stack.map((f) => {\n const next = this.childrenOf(f.containerId)?.[f.index];\n return next ? { ...f, nextId: next.id } : { ...f };\n }),\n activeSnippetId: this.activeSnippet?.id ?? null,\n beatIndex: this.beatIndex,\n pendingChoice: this.pendingChoice\n ? { groupId: this.pendingChoice.groupId, options: this.pendingChoice.options.map((o) => ({ ...o })) }\n : null,\n pendingPromptOwnerId: this.pendingPromptOwnerId,\n // The prompt still to be replayed, as the choice showed it: present only in the choose -> advance window.\n ...(this.pendingPrompt?.shown ? { pendingPrompt: savedPrompt(this.pendingPrompt.shown) } : {}),\n selectors: serialiseSelectors(this.selectors),\n },\n };\n }\n\n /** @internal Restore this flow from a snapshot: its cursor, PRNG, visits, and selector cursors. Its\n * property values are the registry's and stay as they are (a load puts the saved ones there first). */\n restore(snap: FlowSnapshot): void {\n if (this.host.journal) throw new Error(\"a flow can't be restored while a checkpoint is open\");\n this.restoreCursor(snap);\n // Register this flow's bags: each claims the values the registry holds for it (loaded by the game, by\n // loadGame from the save, or handed back by the engine this one replaces), laid over fresh defaults.\n // Released KEEPING their values, so a live flow restored in place keeps its property values (a fresh\n // one, made by a load, is claiming the loaded values either way). The scenes the cursor stands in are\n // registered now; any other scene's bag is claimed on entry.\n this.releaseBags(true);\n this.mountLocal();\n for (const s of new Set([this.currentSceneId, ...this.stack.map((f) => f.sceneId)])) {\n if (s !== null && this.host.bundle.scenes[s]) this.ensureSceneBags(s);\n }\n }\n\n /** The cursor half of `restore`, without touching the bags (a rollback has already put those back). */\n private restoreCursor(snap: FlowSnapshot): void {\n this.rngState = toUint32(snap.rngState);\n this.visitCounts = new Map(Object.entries(snap.visits ?? {}));\n const c = snap.cursor;\n this.started = true;\n this.flowEnded = c.flowEnded;\n this.beatIndex = c.beatIndex;\n this.currentSceneId = c.currentSceneId;\n // Re-bind each frame to the CURRENT bundle: prefer the saved next-child id (survives siblings\n // inserted / removed / reordered before the cursor); fall back to the raw index when the id is\n // absent (an older save) or its node drifted out of the bundle (§9.8 best-effort).\n this.stack = c.stack.map((f) => {\n const { nextId, ...frame } = f;\n if (nextId !== undefined) {\n const at = this.childrenOf(frame.containerId)?.findIndex((ch) => ch.id === nextId) ?? -1;\n if (at >= 0) return { ...frame, index: at };\n }\n return { ...frame };\n });\n\n // Content-drift policy (§9.8): if a saved position points at content deleted\n // since the save, resume best-effort rather than throwing - the missing\n // snippet / choice is dropped and play continues from the surviving stack.\n this.activeSnippet = null;\n if (c.activeSnippetId !== null) {\n const node = this.host.nodeIndex.get(c.activeSnippetId);\n if (node && node.type === \"snippet\") this.activeSnippet = node;\n }\n\n this.selectors = deserialiseSelectors(c.selectors); // this flow's (non-shared) selector cursors\n\n // Replay the saved option set VERBATIM (schema 9.3) - re-deriving would\n // re-evaluate conditions (double-consuming PRNG draws) and could change the\n // choice under the player. Options whose nodes drifted out of the bundle\n // are dropped; a choice with no surviving options dissolves (9.8).\n this.pendingChoice = null;\n if (c.pendingChoice !== null) {\n const byId = new Map<string, SelectableNode>();\n const options: ChoiceOption[] = [];\n for (const o of c.pendingChoice.options) {\n const node = this.host.nodeIndex.get(o.id);\n if (!node) continue;\n byId.set(o.id, node);\n options.push({ ...o });\n }\n if (options.length > 0) this.pendingChoice = { groupId: c.pendingChoice.groupId, options, byId };\n }\n\n // A save taken between choose() and the next advance() left a prompt still to be replayed\n // (replayPromptOnChoose): the chosen option's authored prompt beat, found again by its owner, and the\n // prompt as the choice showed it, carried by the save. A save from before `pendingPrompt` existed has\n // only the owner, and its beat is resolved when delivered, as it was then. Dropped if the option\n // drifted out of the bundle or has no authored prompt (§9.8): the live choose() would replay nothing.\n this.pendingPrompt = null;\n this.pendingPromptOwnerId = c.pendingPromptOwnerId ?? null;\n if (this.pendingPromptOwnerId) {\n const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);\n const beat = owner ? this.authoredPromptOf(owner) : undefined;\n if (beat) this.pendingPrompt = { beat, ...(c.pendingPrompt ? { shown: { ...c.pendingPrompt } } : {}) };\n else this.pendingPromptOwnerId = null;\n }\n }\n\n // -- Scene / block / node entry ------------------------------------------\n\n /** Set the current scene, reset its scene-local props, run onEntry. */\n private enterSceneSetup(sceneId: string): void {\n const scene = this.host.bundle.scenes[sceneId];\n if (!scene) throw new Error(`unknown scene: ${sceneId}`);\n this.currentSceneId = sceneId;\n this.enter(sceneId);\n this.seedScene(scene); // seeds @scene defaults (per-flow on first entry; shared once globally)\n this.runEffects(scene.onEntry, scene.id); // on-entry effects still fire every entry (spec §4)\n }\n\n /**\n * Play one child of the active run. A snippet begins delivering. A group is\n * walked by its selector: the default `run` pushes a nested run (its children\n * play in order, gathering back); `choice` stops for the host; a select-one\n * selector (branch, or a `sequence` in any order x exhaust mode) picks ONE child (recursing\n * to a leaf) - selecting nothing contributes no content and the run continues.\n */\n private enterChild(node: SelectableNode): void {\n this.enter(node.id);\n if (node.type === \"snippet\") { this.beginSnippet(node); return; }\n const selector = node.selector ?? \"run\";\n if (selector === \"run\") {\n this.stack.push({ sceneId: this.currentSceneId!, containerId: node.id, index: 0 });\n return;\n }\n if (selector === \"choice\") { this.setupChoice(node); return; }\n const pick = this.selectChild(node);\n if (pick) this.enterChild(pick);\n }\n\n /** A container's children, whether it's a block or a run-group; undefined if the id is gone. */\n private childrenOf(containerId: string): SelectableNode[] | undefined {\n const block = this.host.blockById.get(containerId);\n if (block) return block.children;\n const node = this.host.nodeIndex.get(containerId);\n if (node && node.type === \"group\") return node.children;\n return undefined; // content drift: the container was deleted since the save\n }\n\n private beginSnippet(snippet: CompiledSnippet): void {\n this.runEffects(snippet.onEnter, snippet.id);\n this.activeSnippet = snippet;\n this.beatIndex = 0;\n }\n\n private setupChoice(group: CompiledGroup): void {\n const options: ChoiceOption[] = [];\n const byId = new Map<string, SelectableNode>();\n const fallbacks: SelectableNode[] = [];\n for (const child of group.children) {\n // An option is an Option group (its content runs + gathers back) or - the\n // degenerate shape - a single snippet. prompt / sticky / fallback / secretUntilEligible\n // live on whichever (spec §5).\n if (child.fallback === true) { fallbacks.push(child); continue; } // never a normal option; auto-followed when last\n // Once-only (default): once the player has followed it, it is GONE from the choice entirely -\n // not delivered, not flagged unavailable, simply absent. A `sticky` option is never consumed,\n // so it stays available as long as its condition passes. Consumption is the existing per-flow\n // visit count, so it persists through save/restore for free.\n if (child.sticky !== true && (this.visitCounts.get(child.id) ?? 0) >= 1) continue;\n const eligible = this.eligible(child);\n const hidden = child.secretUntilEligible === true;\n if (!eligible && hidden) continue; // secret while ineligible; otherwise an ineligible option shows greyed\n options.push({ id: child.id, prompt: this.promptFor(child), eligible, gameData: child.gameData });\n byId.set(child.id, child);\n }\n // A choice is offered only when the player can take something. One whose every remaining option is\n // greyed out left the player stuck in front of it, so it runs dry instead, as a choice with no\n // options does: the fallback follows if there is one, otherwise the flow moves on.\n if (options.some((o) => o.eligible)) {\n // The offered set, including options a condition left ineligible: an author asking\n // \"why is that option greyed?\" is asking about this moment, and a log of the taken\n // option alone cannot answer it. Options hidden by secretUntilEligible are absent\n // here too, exactly as the player sees them.\n if (this.host.tracing) this.emit({ type: \"choice\", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });\n this.pendingChoice = { groupId: group.id, options, byId };\n return;\n }\n // No normal option can be taken. Auto-follow the fallback if it is eligible (its own condition still\n // applies); otherwise the choice GATHERS - it contributes nothing and the run continues past it\n // (a dry choice falls through rather than deadlocking; the validator warns about choices that can\n // run dry with no fallback).\n const fallback = fallbacks.find((f) => this.eligible(f));\n if (fallback) { this.enterChild(fallback); return; }\n // Nothing takeable and no eligible fallback: the choice runs dry and the flow walks past it. The\n // behaviour is unchanged; the opt-in diagnostics hook makes this silent fall-through observable.\n if (this.host.tracing) this.emit({ type: \"dry\", group: group.id });\n // The callback stays beside the log deliberately. They are not the same thing: this is\n // live feedback a host acts on the moment it happens, the log is read afterwards, and a\n // shipped game runs with the log off and this hook still wired.\n this.host.onDryChoice?.(group.id);\n }\n\n // -- Jumps (jump / call-return) ----------------------------------------\n\n private resolveJump(jump: Jump | undefined): void {\n // No jump: gather - the snippet falls through and the block run continues\n // (settle's frame walk picks the next child, or pops to a caller).\n if (!jump) return;\n this.enterTarget(jump.to, jump.mode === \"call\" ? \"call\" : \"jump\");\n }\n\n /**\n * Route to a target (scene / block / `END`). `call` PUSHES a return frame (the\n * caller's block run, already advanced to its next child, stays below); `jump`\n * is absolute - it REPLACES the whole stack, discarding pending returns. `END`\n * hard-ends the flow regardless of the callstack.\n */\n private enterTarget(to: string, mode: \"call\" | \"jump\"): void {\n if (this.host.tracing) this.emit({ type: \"jump\", to, mode });\n if (to === \"END\") { this.flowEnded = true; this.stack = []; return; }\n\n let sceneId: string;\n let containerId: string;\n const scene = this.host.bundle.scenes[to];\n if (scene) {\n this.enterSceneSetup(to);\n const first = scene.blocks[0];\n if (!first) { if (mode === \"jump\") this.stack = []; return; } // empty scene\n sceneId = to; containerId = first.id;\n } else {\n const loc = this.host.blockIndex.get(to);\n if (!loc) throw new Error(`jump target not found: ${to}`);\n if (loc.sceneId !== this.currentSceneId) this.enterSceneSetup(loc.sceneId);\n sceneId = loc.sceneId; containerId = to;\n }\n\n this.enter(containerId); // count the entered block\n const frame: StackFrame = { sceneId, containerId, index: 0 };\n if (mode === \"call\") this.stack.push(frame);\n else this.stack = [frame];\n }\n\n // -- Selectors ------------------------------------------------------------\n\n private selectChild(group: CompiledGroup): SelectableNode | null {\n // Each condition is evaluated ONCE: the verdicts and the eligible list come from the same pass. Twice\n // cost double, and a condition calling random() drew twice, so its verdict and the pick could disagree.\n const verdicts = group.children.map((c) => ({ id: c.id, eligible: this.eligible(c) }));\n const eligible = group.children.filter((_, i) => verdicts[i]!.eligible);\n const order = group.options?.order ?? \"sequential\";\n const exhaust = group.options?.exhaust ?? \"once\";\n // The reasoning goes in the entry, not just the outcome: every child this looked at,\n // with why it was or was not takeable. \"The line I expected is missing\" is only\n // answerable if the log says which sibling was dropped and that it was dropped here.\n const trace = (picked: SelectableNode | null): SelectableNode | null => {\n if (this.host.tracing) this.emit({\n type: \"select\", group: group.id, selector: group.selector ?? \"default\",\n ...(group.selector === \"sequence\" ? { order, exhaust } : {}),\n children: verdicts, picked: picked?.id ?? null,\n });\n return picked;\n };\n\n if (eligible.length === 0) return trace(null);\n const st = this.selectorState(group);\n\n switch (group.selector) {\n case \"branch\":\n return trace(eligible[0]!);\n\n case \"sequence\":\n return trace(order === \"shuffle\" ? this.pickShuffle(eligible, exhaust, st)\n : order === \"specificity\" ? this.pickSpecificity(eligible, exhaust, st)\n : this.pickSequential(eligible, exhaust, st));\n\n case \"run\":\n case \"choice\":\n default:\n return null; // run / choice / default are handled in enterChild, not here\n }\n }\n\n /** `sequence` with `order: \"sequential\"` - walk children in authored order. */\n private pickSequential(eligible: SelectableNode[], exhaust: string, st: SelectorState): SelectableNode | null {\n const len = eligible.length;\n const n = st.seq ?? 0;\n st.seq = n + 1;\n if (exhaust === \"repeat\") return eligible[n % len]!; // cycle\n if (n < len) return eligible[n]!; // still in the first pass\n if (exhaust === \"stick\") return eligible[len - 1]!; // hold the last forever (stopping)\n return null; // once: nothing after the pass\n }\n\n /**\n * `sequence` with `order: \"shuffle\"` - draw WITHOUT replacement (a bag), never\n * repeating the immediately-previous pick across a reshuffle (no line twice in a\n * row when >=2 are eligible). `stick` holds out the last authored child as the\n * permanent terminal; `once` stops after one pass; `repeat` reshuffles.\n */\n private pickShuffle(eligible: SelectableNode[], exhaust: string, st: SelectorState): SelectableNode | null {\n const len = eligible.length;\n const stick = exhaust === \"stick\";\n const fill = (): string[] => (stick ? eligible.slice(0, len - 1) : eligible).map((c) => c.id);\n const eligibleIds = new Set(eligible.map((c) => c.id));\n\n if (st.bag === undefined) st.bag = fill();\n // The bag was filled from the children eligible THEN. Draw only from those still eligible now, so a\n // child whose condition has since gone false is never drawn (it used to be drawn, and the group then\n // played nothing). If none of the bag is drawable, the pass is over, exactly as when the bag is empty.\n if (!st.bag.some((id) => eligibleIds.has(id))) {\n if (exhaust === \"once\") return null;\n if (stick) { const last = eligible[len - 1]!; st.last = last.id; return last; }\n st.bag = fill(); // repeat: reshuffle\n if (st.bag.length === 0) return null;\n }\n\n // Draw without replacement, never repeating the immediately-previous pick when another is drawable.\n const pool = st.bag.filter((id) => eligibleIds.has(id));\n const p = st.last !== undefined && pool.length > 1 ? pool.indexOf(st.last) : -1;\n let i = Math.floor(this.rng() * (p >= 0 ? pool.length - 1 : pool.length));\n if (p >= 0 && i >= p) i++;\n const id = pool[i]!;\n st.bag.splice(st.bag.indexOf(id), 1);\n st.last = id;\n return eligible.find((c) => c.id === id)!;\n }\n\n\n /**\n * `sequence` with `order: \"specificity\"` - **Best match**: score every eligible child by how\n * specifically its condition fits the CURRENT state (`matchedSpec`), keep the top-scoring tier,\n * and break ties with the seeded shuffle (no immediate repeat). A child with no condition scores\n * 0, so it is the filler that wins only when nothing more specific is eligible.\n *\n * `exhaust` composes as it does for the other orders: `repeat` re-scores the full eligible set\n * every draw (re-pickable - the character keeps preferring the on-topic line); `once` uses each\n * pick up (a bag of remaining ids), so as specific lines are consumed the group slides down to\n * less-specific ones and finally the filler, then yields null; `stick` degrades like `once` but\n * holds the final pick forever instead of drying up.\n */\n private pickSpecificity(eligible: SelectableNode[], exhaust: string, st: SelectorState): SelectableNode | null {\n let pool = eligible;\n if (exhaust !== \"repeat\") { // once / stick: draw without replacement\n if (st.bag === undefined) st.bag = eligible.map((c) => c.id);\n const remaining = new Set(st.bag);\n pool = eligible.filter((c) => remaining.has(c.id));\n if (pool.length === 0) { // every child used up\n return exhaust === \"stick\" && st.last !== undefined\n ? eligible.find((c) => c.id === st.last) ?? null // hold the last pick if still eligible\n : null;\n }\n }\n\n // Top specificity tier among the drawable pool.\n let best = -1;\n const scored = pool.map((c) => { const s = this.specScore(c); if (s > best) best = s; return { c, s }; });\n const tier = scored.filter((x) => x.s === best).map((x) => x.c);\n\n // Tie-break by the seeded PRNG, never repeating the immediately-previous pick (matches shuffle).\n // A lone top-tier child is returned WITHOUT drawing, so a clear winner consumes no randomness.\n let pick: SelectableNode;\n if (tier.length === 1) {\n pick = tier[0]!;\n } else {\n const p = st.last !== undefined ? tier.findIndex((c) => c.id === st.last) : -1;\n let i = Math.floor(this.rng() * (p >= 0 ? tier.length - 1 : tier.length));\n if (p >= 0 && i >= p) i++;\n pick = tier[i]!;\n }\n\n if (exhaust !== \"repeat\") st.bag = st.bag!.filter((id) => id !== pick.id);\n st.last = pick.id;\n return pick;\n }\n\n /** A child's Best-match score against the current state: 0 when it has no condition (the filler\n * tier), else the specificity of its (already-passing) condition. */\n private specScore(node: SelectableNode): number {\n return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true, node) : 0;\n }\n\n /**\n * The **matched-specificity** metric (parity contract): how many atomic constraints are actively\n * holding this condition TRUE against the live state. Evaluation-aware, not a static clause count -\n * it walks the tree with a De-Morgan polarity flag so `or` and `not` score the branch that is\n * actually carrying the truth. `want` = \"does this subtree need to be true for the whole condition\n * to hold?\" (true at the root). Only `and`/`or`/`not`/`check_flags` are structural; every other\n * node (comparisons, scoped vars, literals, other calls) is an atom, evaluated whole.\n */\n private matchedSpec(node: ExprNode, want: boolean, owner: SelectableNode): number {\n // Delegates to the shared @wildwinter/expr-specificity scorer (same walk,\n // shared with Storylet Studio). We supply Patter's truthiness rule and keep\n // check_flags counting via the package's default counting call. The scorer walks every part,\n // including an `or` branch eligibility never evaluated, so a part can fail here that did not fail\n // there: it scores as false (see PlayError), as on the other three runtimes.\n const evalTruthy: EvalTruthy = (n) => {\n try { return truthy(evaluate(n, this.context(), patterDialect)); }\n catch (err) { this.reportError(\"best-match\", owner.id, owner.condition, err); return false; }\n };\n return scoreSpecificity(node, evalTruthy, { want });\n }\n\n /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */\n private selectorState(group: CompiledGroup): SelectorState {\n const map = group.shared ? this.host.sharedSelectors : this.selectors;\n // A cursor is copied the first time a checkpoint sees it (shared ones once for every flow).\n const journal = this.host.journal;\n let seen: Set<string> | undefined;\n if (journal) {\n seen = group.shared ? journal.selectors : journal.flowSelectors.get(this);\n if (!seen) { seen = new Set(); journal.flowSelectors.set(this, seen); }\n }\n if (journal && seen && !seen.has(group.id)) {\n seen.add(group.id);\n const was = map.get(group.id);\n const copy = was ? { ...was, ...(was.bag ? { bag: [...was.bag] } : {}) } : undefined;\n journal.undo.push(() => { if (copy) map.set(group.id, copy); else map.delete(group.id); });\n }\n let st = map.get(group.id);\n if (!st) { st = {}; map.set(group.id, st); }\n return st;\n }\n\n // -- Effects + expressions ------------------------------------------------\n\n /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */\n private runEffects(effects: CompiledEffect[] | undefined, owner: string): void {\n // SET-ONLY (spec §15): an effect mutates a property. Host events ride on gameData, not effects.\n for (const e of effects ?? []) {\n let value: ScalarValue;\n try {\n value = this.evalExpr(e.value);\n // `prev` read before the write, so a reader can say \"0 -> 1\" without a second pass.\n // Only paid for when the run asked for a log.\n const prev = this.host.tracing ? this.getProperty(e.target) : undefined;\n this.writeProperty(e.target, value, false); // the STORY writes: a read-only host property refuses it\n if (this.host.tracing) this.emit({ type: \"write\", target: e.target, value, ...(prev !== undefined ? { prev } : {}) });\n } catch (err) {\n // Skipped, reported, and the rest of the list still runs (see PlayError).\n this.reportError(\"effect\", owner, e.value, err);\n }\n }\n }\n\n /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */\n private eligible(node: SelectableNode): boolean {\n if (!node.condition) return true;\n try { return truthy(this.evalExpr(node.condition)); }\n catch (err) { this.reportError(\"condition\", node.id, node.condition, err); return false; }\n }\n\n /** Report a content error the engine is playing through: to the host's onError, and to the log. */\n private reportError(kind: PlayError[\"kind\"], node: string, expr: Expression | undefined, err: unknown): void {\n const message = err instanceof Error ? err.message : String(err);\n const source = expr?.src;\n this.host.onError({ flow: this.id, kind, node, ...(source ? { source } : {}), message });\n if (this.host.tracing) this.emit({ type: \"diagnostic\", kind, node, ...(source ? { source } : {}), message });\n }\n\n private evalExpr(expr: Expression): ScalarValue {\n return evaluate(this.conditionAst(expr), this.context(), patterDialect);\n }\n\n /** The deserialised (in-memory) AST for an expression, cached per Expression. Shared by the\n * evaluator and the Best-match specificity walker so both work off one parse. */\n private conditionAst(expr: Expression): ExprNode {\n let ast = astCache.get(expr);\n if (!ast) { ast = deserialiseAst(expr.ast); astCache.set(expr, ast); }\n return ast;\n }\n\n /** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */\n private enter(id: string): void {\n const own = this.visitCounts;\n const ownBefore = own.get(id);\n own.set(id, (ownBefore ?? 0) + 1);\n const shared = this.host.sharedVisits;\n const before = shared.get(id);\n shared.set(id, (before ?? 0) + 1);\n if (this.host.journal) this.host.journal.undo.push(() => {\n if (ownBefore === undefined) own.delete(id); else own.set(id, ownBefore);\n if (before === undefined) shared.delete(id); else shared.set(id, before);\n });\n }\n\n /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32.\n *\n * The mixing is @wildwinter/expr's makePrng, not a copy inline here. It is a\n * fixed published algorithm that both product families need and neither\n * owns, and it existed thirteen times across the two of them. `rngState` is\n * still the serialisable position, so saves are unaffected: a generator is\n * made from it, drawn once, and the new state written back. */\n private readonly rng = (): number => {\n if (this.host.customRng) return this.host.customRng();\n const prng = makePrng(this.rngState);\n const draw = prng.next();\n this.rngState = prng.state();\n return draw;\n };\n\n // -- Strings / beats ------------------------------------------------------\n\n private beatResult(beat: Beat): StepResult {\n // Accumulated author tags (#215): the beat's own tags unioned with every\n // ancestor's. Omitted from the step when empty (parity with `gameData`).\n const tags = tagsOf(this.host, beat.id);\n const withTags = tags && tags.length ? { tags } : {};\n // Inline `{@ref}` interpolation (spec §16): text beats always interpolate;\n // line beats interpolate only in a non-voiced project (voiced lines are\n // static). Game-event beats carry no localised content.\n switch (beat.kind) {\n case \"gameEvent\":\n return { type: \"gameEvent\", id: beat.id, gameData: beat.gameData, ...withTags };\n case \"text\":\n return { type: \"text\", id: beat.id, text: this.interpolate(this.resolveString(beat.id)), gameData: beat.gameData, ...withTags };\n case \"line\": {\n const raw = this.resolveString(beat.id);\n // Closed captions (#214) apply to DIALOGUE lines only: strip cues when captions are off. Two ways a\n // line goes SILENT (off only): the caption CHARACTER speaks it (whole line is a caption - omit all\n // dialogue, delimiters or not), or stripping cues leaves it empty. A silent line still FIRES (audio\n // plays + visits count) but carries no text + no speaker, so no caption shows.\n const off = !this.host.captionsOn;\n const captionChar = off && beat.character === this.host.captionCharacter; // captionCharacter is always set (defaults SFX)\n const text = captionChar ? \"\" : this.captionLine(this.host.bundle.voiced ? raw : this.interpolate(raw));\n const silent = off && text.length === 0;\n return {\n type: \"line\",\n id: beat.id,\n text,\n character: silent ? undefined : beat.character,\n characterName: silent ? undefined : this.resolveCharacterName(beat.character),\n direction: silent ? undefined : beat.direction,\n gameData: beat.gameData,\n ...withTags,\n };\n }\n }\n }\n\n /**\n * Expand inline `{@ref}` slots (spec §16) against this flow's CURRENT property state. Public so an\n * IDs-only game can apply the same property replacement to a string it looked up in its own loc system:\n * the engine handed it the beat ID, the game fetched its translation, then calls `flow.interpolate(...)`.\n */\n interpolate(raw: string): string {\n return interpolate(raw, (ref) => this.getProperty(ref));\n }\n\n /**\n * Apply the project's caption rule to a string UNCONDITIONALLY (#214): remove every cue span between\n * the project's delimiters and collapse the whitespace. Public so an IDs-only game - which looks up\n * its own strings - can match the embedded runtime: `flow.stripCaptions(flow.interpolate(text))` when\n * its own captions setting is off. (Embedded play does this automatically for dialogue lines.)\n */\n stripCaptions(raw: string): string {\n return stripCaptions(raw, this.host.captionOpen, this.host.captionClose);\n }\n\n /** Caption-strip a dialogue line ONLY when captions are off; otherwise pass the text through. The\n * internal gate the engine applies to every `line` beat / line-kind prompt. */\n private captionLine(text: string): string {\n return this.host.captionsOn ? text : this.stripCaptions(text);\n }\n\n /**\n * An option's prompt (spec §5): the Option group's `prompt` beat, resolved + interpolated\n * (choice labels are on-screen text, so they interpolate, spec §16). For the degenerate\n * bare-snippet tolerance - or an Option group authored without a prompt - it falls back to the\n * option's first content line. NO look-ahead. Undefined only when even that is absent.\n */\n private promptFor(node: SelectableNode): ChoicePrompt | undefined {\n const beat = this.promptBeatOf(node);\n if (!beat) return undefined;\n const text = this.interpolate(this.resolveString(beat.id));\n // A line-kind prompt is dialogue, so captions apply to it; a text-kind prompt is left as-is.\n return beat.kind === \"line\"\n ? { kind: \"line\", text: this.captionLine(text), character: beat.character, characterName: this.resolveCharacterName(beat.character), direction: beat.direction }\n : { kind: \"text\", text };\n }\n\n /** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */\n private authoredPromptOf(node: SelectableNode): LineBeat | TextBeat | undefined {\n return node.type === \"group\" ? node.prompt : undefined;\n }\n\n /** A replayed prompt as a step: the beat's id, gameData, and tags, and the text and speaker fields the\n * choice showed. */\n private promptResult(beat: LineBeat | TextBeat, shown: ChoicePrompt): StepResult {\n const tags = tagsOf(this.host, beat.id);\n const withTags = tags && tags.length ? { tags } : {};\n if (shown.kind === \"text\") return { type: \"text\", id: beat.id, text: shown.text, gameData: beat.gameData, ...withTags };\n return {\n type: \"line\", id: beat.id, text: shown.text,\n character: shown.character, characterName: shown.characterName, direction: shown.direction,\n gameData: beat.gameData, ...withTags,\n };\n }\n\n /** The prompt BEAT of an option: the Option group's `prompt`, else (tolerance) its first content line. */\n private promptBeatOf(node: SelectableNode): LineBeat | TextBeat | undefined {\n if (node.type === \"group\" && node.prompt) return node.prompt;\n const snippet = node.type === \"snippet\" ? node : this.firstTextSnippetIn(node.children);\n return (snippet?.beats ?? []).find((b): b is LineBeat | TextBeat => b.kind === \"line\" || b.kind === \"text\");\n }\n\n /** The first snippet with a line/text beat within a child list, depth-first in authored order. */\n private firstTextSnippetIn(children: SelectableNode[]): CompiledSnippet | undefined {\n let found: CompiledSnippet | undefined;\n walkNodes<SelectableNode>(children, (n) => {\n if (!found && n.type === \"snippet\" && (n.beats ?? []).some((b) => b.kind === \"line\" || b.kind === \"text\")) {\n found = n;\n }\n });\n return found;\n }\n\n private resolveString(id: string): string {\n if (this.host.emitIds) return id; // IDs-only build: the game resolves text from this id itself\n const active = this.host.strings[id];\n if (active !== undefined) return active;\n // A key the active locale is missing falls back to the default-locale (source) text, but is flagged\n // LOUDLY: an untranslated string is a hard fail authors must notice, not silently paper over. Only a\n // key absent from the default locale too (never extracted) degrades to its bare id.\n const source = this.host.defaultStrings[id];\n return source !== undefined ? `<Untranslated: ${id}> ${source}` : id;\n }\n\n /** A character's player-facing name: the `cast:<name>` string in the active locale, else the default\n * locale, else the authoring `displayName`. Undefined when the character has no display name at all\n * (the host falls back to the `character` token itself). */\n private resolveCharacterName(character: string | undefined): string | undefined {\n if (character === undefined) return undefined;\n if (this.host.emitIds) return undefined; // IDs-only: omit the display name; the game maps the `character` token\n const key = castStringKey(character);\n return this.host.strings[key] ?? this.host.defaultStrings[key] ?? this.host.castDisplay.get(character);\n }\n\n /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */\n private splitRef(ref: string): { scope: string; name: string } {\n return splitHostRef(this.host, ref);\n }\n\n /** A fresh, unregistered bag for the NOT-shared `@patter` globals (the shared ones live on the host). */\n private newLocal(): PropertyBag {\n return new PropertyBag(this.host.patterLocalDecls, { pathPrefix: \"@patter.\" });\n }\n\n /** Register a fresh globals bag under this flow's key; it claims any values waiting there. */\n private mountLocal(): void {\n this.local = this.newLocal();\n const key = keys.flowGlobals(this.id);\n this.host.registry.mountOwned(key, this.local, { owner: OWNER });\n this.registered.add(key);\n }\n\n /** @internal Remove every bag this flow registered; with `keep`, their values wait in the registry for\n * the flow that replaces this one. Engine-driven (close, loadGame, hotSwap). */\n releaseBags(keep: boolean): void {\n for (const key of this.registered) this.host.registry.remove(key, { keep });\n this.registered.clear();\n this.sceneBags.clear();\n }\n\n /** The bag a `@scene` property of scene `s` lives in (stage or this flow's), made if missing. */\n private sceneBagFor(s: string, name: string): PropertyBag | undefined {\n if (!this.host.bundle.scenes[s]) return undefined;\n this.ensureSceneBags(s);\n return this.host.sceneSharedNames.get(s)?.has(name.toLowerCase()) ? this.host.stageBags.get(s) : this.sceneBags.get(s);\n }\n\n /** Make (and register) scene `s`'s stage bag and this flow's bag for it, if not made yet. A bag made\n * here claims whatever values the registry holds for its key: that is how a loaded save reaches it. */\n private ensureSceneBags(s: string): void {\n const shared = this.host.sceneSharedNames.get(s) ?? new Set<string>();\n // The bag's constructor seeds each declared default (the type's when none), normalises the name,\n // and clones the default so two bags from one declaration set never share a mutable flags array.\n const props = this.host.bundle.scenes[s]?.sceneProps ?? [];\n const journal = this.host.journal;\n if (!this.sceneBags.has(s)) {\n const bag = new PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())) as never, { pathPrefix: \"@scene.\" });\n const key = keys.flowScene(this.id, s);\n const claimed = mountClaims(this.host.registry, key, bag, journal !== null);\n this.registered.add(key);\n this.sceneBags.set(s, bag);\n // Made inside a checkpoint: a rollback unmakes it, so the scene seeds afresh on its next real entry,\n // unless the mount claimed values a load had parked, which go back to the registry rather than away.\n if (journal) journal.undo.push(() => {\n if (this.sceneBags.get(s) !== bag) return; // already released with the flow\n this.host.registry.remove(key, claimed ? { keep: true } : undefined);\n this.registered.delete(key);\n this.sceneBags.delete(s);\n });\n }\n if (!this.host.stageBags.has(s)) {\n const bag = new PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())) as never, { pathPrefix: \"@scene.\" });\n const claimed = mountClaims(this.host.registry, keys.stage(s), bag, journal !== null);\n this.host.stageBags.set(s, bag);\n if (journal) journal.undo.push(() => {\n if (this.host.stageBags.get(s) !== bag) return;\n this.host.registry.remove(keys.stage(s), claimed ? { keep: true } : undefined);\n this.host.stageBags.delete(s);\n });\n }\n }\n\n /**\n * Seed a scene's `@scene` props (spec §7). The not-shared props seed THIS flow's\n * bag the first time it enters (persist across re-entries thereafter); the shared\n * props seed the host's stage bag the first time ANY flow enters the scene (shared\n * and persistent thereafter - a later flow finds it present and leaves it).\n * `temporary` props are the exception: reseeded to their default on every entry.\n */\n private seedScene(scene: CompiledScene): void {\n const shared = this.host.sceneSharedNames.get(scene.id) ?? new Set<string>();\n this.ensureSceneBags(scene.id);\n\n // `temporary` props are reseeded to their default on EVERY entry (\"fresh each\n // playthrough\"), rather than persisting across re-entries like the rest.\n for (const decl of scene.sceneProps ?? []) {\n if (!decl.temporary) continue;\n const name = decl.name.toLowerCase();\n const bag = shared.has(name) ? this.host.stageBags.get(scene.id) : this.sceneBags.get(scene.id);\n if (!bag) continue;\n const prev = this.host.journal ? bag.get(name) : undefined;\n if (prev !== undefined) this.host.journal!.undo.push(() => bag.set(name, prev));\n bag.set(name, defaultFor(decl));\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers.\n// ---------------------------------------------------------------------------\n\n/** Collect the speakers under a run of nodes into `out`, in document order (insertion order IS the\n * result order). Groups contribute their option prompt's speaker - a prompt is a line | text beat, so\n * a line prompt is spoken by someone - and `walkNodes` carries the recursion into nested groups. */\nfunction collectCast(nodes: Array<CompiledGroup | CompiledSnippet>, out: Set<string>): void {\n walkNodes<SelectableNode>(nodes, (n) => {\n if (n.type === \"group\") {\n if (n.prompt?.kind === \"line\" && n.prompt.character) out.add(n.prompt.character);\n return;\n }\n for (const beat of n.beats ?? []) if (beat.kind === \"line\" && beat.character) out.add(beat.character);\n });\n}\n\n/** A `{ gameData }` outline fragment, present only when the node sets any (parity with a beat's). */\nfunction gameDataField(gameData: GameData | undefined): { gameData?: GameData } {\n return gameData && Object.keys(gameData).length ? { gameData } : {};\n}\n\n/** Serialise a `sequence` selector-cursor map to plain snapshots. */\n/** A prompt as a save carries it: the set fields only (a field set to \"\" included), never an `undefined`. */\nfunction savedPrompt(p: ChoicePrompt): SavedChoicePrompt {\n const out: SavedChoicePrompt = { kind: p.kind, text: p.text };\n if (p.character !== undefined) out.character = p.character;\n if (p.characterName !== undefined) out.characterName = p.characterName;\n if (p.direction !== undefined) out.direction = p.direction;\n return out;\n}\n\nfunction serialiseSelectors(map: Map<string, SelectorState>): Record<string, SelectorSnapshot> {\n const out: Record<string, SelectorSnapshot> = {};\n for (const [id, st] of map) {\n const v: SelectorSnapshot = {};\n if (st.seq !== undefined) v.seq = st.seq;\n if (st.bag) v.bag = [...st.bag];\n if (st.last !== undefined) v.last = st.last;\n out[id] = v;\n }\n return out;\n}\n\n/** Rebuild a `sequence` selector-cursor map from snapshots. */\nfunction deserialiseSelectors(rec: Record<string, SelectorSnapshot> | undefined): Map<string, SelectorState> {\n const map = new Map<string, SelectorState>();\n for (const [id, v] of Object.entries(rec ?? {})) {\n const st: SelectorState = {};\n if (v.seq !== undefined) st.seq = v.seq;\n if (v.bag) st.bag = [...v.bag];\n if (v.last !== undefined) st.last = v.last;\n map.set(id, st);\n }\n return map;\n}\n\n/** Adapt a Patter `PropertyDecl` to a registry `ScopeDeclaration` (same type vocabulary). */\n/**\n * Mount `bag` at `key` and say whether the mount CLAIMED values a load had parked there. Only asked when a\n * checkpoint is open (`ask`), since only a rollback needs to know: it must hand claimed values back to the\n * registry, or a scene first entered inside the checkpoint loses its saved values. A fresh bag holds only\n * its defaults, so a change across the mount is a claim. Parked values that equal the defaults are not told\n * apart, and need not be: dropping them changes nothing a story can read.\n */\nfunction mountClaims(registry: ScopeRegistry, key: string, bag: PropertyBag, ask: boolean): boolean {\n const before = ask ? JSON.stringify(bag.save()) : \"\";\n registry.mountOwned(key, bag, { owner: OWNER });\n return ask && JSON.stringify(bag.save()) !== before;\n}\n\nfunction toDecl(decl: PropertyDecl): ScopeDeclaration {\n return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };\n}\n\n\n/** The scopes the game binds: `hostScopes`, and the deprecated `world` as the scope of that name. */\nfunction boundScopes(options: EngineOptions): Record<string, HostScope> {\n return { ...(options.world ? { world: options.world } : {}), ...options.hostScopes };\n}\n\n/** The stage ladder of each declared quality in one set of declarations, by lowercased name (names compare\n * lowercase, as the compiler emits references; the first declaration of a name wins). Built once per set\n * and kept on the host: a comparison asks for a ladder every time it runs, and a scan of the declarations\n * each time cost a pass and a lowercasing per declaration per comparison. */\nfunction ladders(host: FlowHost, cacheKey: string, decls: () => ReadonlyArray<{ name: string; type: string; stages?: string[] }> | undefined): Map<string, readonly string[]> {\n let found = host.qualityLadders.get(cacheKey);\n if (!found) {\n found = new Map();\n for (const d of decls() ?? []) {\n const k = d.name.toLowerCase();\n if (d.type === \"quality\" && d.stages && !found.has(k)) found.set(k, d.stages);\n }\n host.qualityLadders.set(cacheKey, found);\n }\n return found;\n}\n\n/** A host-scope declaration (`@world.x`) → registry declaration. */\nfunction toForeignDecl(decl: HostScopeDecl): ScopeDeclaration {\n return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default, writable: decl.writable };\n}\n\n\n/** A self-backed host scope's declaration (the standalone `@world`): the scope's own `writable` default\n * folded in, since an owned bag reads writability per declaration. Names fold to lower case in the bag,\n * as the compiler emits every reference (`isNight` is read as `isnight`). */\nfunction selfBackedDecl(decl: HostScopeDecl, scopeWritable: boolean | undefined): ScopeDeclaration {\n const d = toForeignDecl(decl);\n const writable = decl.writable ?? scopeWritable;\n return writable === undefined ? d : { ...d, writable };\n}\n\n/** Split a ref into scope + name against the registry's current tokens (`@scene` is always Patter's).\n * Memoised per ref; the memo is dropped when the registry's set of scopes moves. */\n/**\n * The ONE rule for resolving a scene address, used by openFlow, goto, and every engine lookup: its gameId\n * (the host-facing address, spec §6) first, then its internal id. openFlow used to try the internal id\n * first and goto the gameId, so the two could land in different scenes when one scene's id was another's\n * gameId.\n */\nfunction resolveScene(host: FlowHost, ref: string): string | undefined {\n return host.sceneGameIdToId.get(ref) ?? (host.bundle.scenes[ref] ? ref : undefined);\n}\n\n/** The one rule for a block address, always WITHIN a scene: its gameId there first, then the internal id\n * of a block in that scene. A real block of another scene does not resolve. */\nfunction resolveBlock(host: FlowHost, sceneId: string, ref: string): string | undefined {\n return host.blockGameIdToId.get(sceneId)?.get(ref) ?? (host.blockIndex.get(ref)?.sceneId === sceneId ? ref : undefined);\n}\n\nfunction splitHostRef(host: FlowHost, ref: string): { scope: string; name: string } {\n if (host.refSplitRevision !== host.registry.revision) {\n host.refSplitCache.clear();\n host.refSplitRevision = host.registry.revision;\n }\n let hit = host.refSplitCache.get(ref);\n // Another engine's scope the content names (`@story.act`) is a scope even before that engine\n // registers it: a write then fails naming it, where it would otherwise land in @patter as `story.act`.\n if (!hit) {\n hit = splitRef(ref, (t) => t === \"scene\" || host.registry.has(t) || (host.bundle.externalScopes?.includes(t) ?? false));\n host.refSplitCache.set(ref, hit);\n }\n return hit;\n}\n\n/** A version 2 save's property values, as registry sections under this engine's keys. */\nfunction sectionsFromV2(save: SaveGameV2): EngineSave {\n const out: EngineSave = {};\n if (save.shared?.patter) out[\"patter\"] = save.shared.patter;\n for (const [s, v] of Object.entries(save.stageBags ?? {})) out[keys.stage(s)] = v;\n for (const [id, f] of Object.entries(save.flows ?? {})) {\n if (f.scopes?.patter) out[keys.flowGlobals(id)] = f.scopes.patter;\n for (const [s, v] of Object.entries(f.sceneBags ?? {})) out[keys.flowScene(id, s)] = v;\n }\n return out;\n}\n\n\nfunction truthy(v: ScalarValue): boolean {\n if (typeof v === \"boolean\") return v;\n if (typeof v === \"number\") return v !== 0;\n if (typeof v === \"string\") return v !== \"\";\n return v.length > 0; // string[]\n}\n","// ---------------------------------------------------------------------------\n// describeBundle - the bundle inspector's runtime half.\n//\n// A BUNDLE-level function, deliberately NOT an Engine method. It answers the\n// integrator's question from the imported asset alone, with no engine, no state\n// and nothing running:\n//\n// I dropped a .patterc into my project. What may my game code call, and is\n// this the bundle I think it is?\n//\n// That is a different question from the one the property examiner answers. The\n// examiner (Engine.listProperties + the per-engine state panels) watches and\n// edits a LIVE game. This is static: it is the API boundary made visible, read\n// off the asset in an editor inspector before anything runs.\n//\n// Three readers. The integrator reads the addresses and the host scopes: those\n// are what game code may call and what it must supply. The writer reads them\n// when nothing happens on `runFlow(\"x\", \"some_scene\")` - the address they typed\n// is not in the list. The designer reads the identity and counts to confirm\n// what actually shipped.\n//\n// Everything is in BUNDLE ORDER, never sorted: two runtimes must render the\n// same rows in the same sequence, and bundle order is the only order all four\n// ports can agree on without importing a collation rule.\n//\n// Cheap by construction. Scenes, blocks and declarations are walked once;\n// nothing here parses an expression, resolves a string table, or touches the\n// per-locale text. `beats` is the one count that requires descending to the\n// leaves, and it is a running total taken during the same single walk.\n// ---------------------------------------------------------------------------\n\nimport { effectiveGameId } from \"@patterkit/model\";\nimport type {\n Bundle, CompiledBlock, CompiledGroup, CompiledSnippet, GameDataField,\n GameDataNodeKind, PropertyDecl, PropertyType, ScalarValue,\n} from \"@patterkit/model\";\n\n/** Which bundle this is: identity, staleness fingerprints, and how it ships. */\nexport interface BundleIdentity {\n /** The bundle schema tag (\"patter/bundle@0\"). */\n schema: string;\n /** The project name. A save must agree with this. */\n project: string;\n /** The authored bundle version, if the project stamps one. */\n version?: string;\n /** Fingerprint over the WHOLE bundle: what binds saves and gates staleness. */\n hash?: string;\n /** The same fingerprint with the string tables left out. Equal structureHash\n * plus a different hash means a TEXT-ONLY edit, which is what makes a live\n * hot-swap safe. Showing both lets an integrator tell those apart at sight. */\n structureHash?: string;\n /** Project-wide VO mode. */\n voiced: boolean;\n defaultLocale: string;\n locales: string[];\n /** How strings ship: \"embedded\" (the runtime resolves text) or \"ids\" (the\n * runtime emits beat IDs and the game localises them itself). */\n localisation: \"embedded\" | \"ids\";\n /** True when the source locale was embedded purely for debug playback. Such a\n * build is NOT shippable, which is worth saying loudly in an inspector. */\n sourceDebug: boolean;\n}\n\n/** One scene, and the addresses game code may aim at inside it. */\nexport interface AddressSummary {\n /** The host-facing scene address: what `runFlow` / `goto` take. Derived from\n * the name when the author set no explicit gameId, exactly as the runtime\n * resolves it, so this list is the truth rather than an approximation. */\n gameId: string;\n /** The authored scene name, for recognising the row. */\n name: string;\n /** Block addresses within this scene. A block address is SCENE-SCOPED: the\n * pair is the address, which is why these are nested rather than flattened. */\n blocks: { gameId: string; name: string }[];\n}\n\n/** One author-defined gameData field: part of the host-facing data surface. */\nexport interface GameDataFieldSummary {\n name: string;\n type: string;\n /** Whether the schema carries a fallback. Sparse storage means a node that\n * sets nothing reads this, so a field with no default can arrive absent. */\n hasDefault: boolean;\n /** Allowed values for an enum field: the set host code switches on. */\n values?: string[];\n purpose?: string;\n}\n\n/** The gameData fields declared for one kind of node. */\nexport interface GameDataSummary {\n kind: GameDataNodeKind;\n fields: GameDataFieldSummary[];\n}\n\n/** One declared property. `hasDefault` rather than the value itself: an\n * inspector wants to know whether the host MUST supply something. */\nexport interface PropertySummary {\n name: string;\n type: PropertyType;\n hasDefault: boolean;\n default?: ScalarValue;\n /** Shared across all flows, or kept per-flow. Defaults differ by scope\n * (`@patter` shared, `@scene` per-flow), so it is resolved here. */\n shared: boolean;\n}\n\n/** A host scope (`@world` and friends): what the GAME must supply.\n *\n * The highest-value section of the whole description. Today an integrator\n * discovers a missing world property when a condition silently reads a\n * self-backed default and a branch never fires. */\nexport interface HostScopeSummary {\n /** The token after `@`, e.g. \"world\". */\n token: string;\n /** Scope-level read/write default for its declarations. */\n writable: boolean;\n /** An OPAQUE scope declares no names: any name is accepted, unchecked. The\n * host contract is then \"anything\", which is worth showing as such rather\n * than as an empty property list. */\n opaque: boolean;\n properties: PropertySummary[];\n}\n\n/** Story-owned declarations, for orientation rather than for calling. */\nexport interface OwnedProperties {\n /** Project-level (`@patter`). */\n patter: PropertySummary[];\n /** Per scene (`@scene`), keyed by the scene's host address. */\n scene: { gameId: string; properties: PropertySummary[] }[];\n}\n\n/** \"Is this the right build?\" at a glance. */\nexport interface BundleCounts {\n scenes: number;\n blocks: number;\n groups: number;\n snippets: number;\n /** Snippet beats. This is the SAME population `Engine.getBeatSequence` walks, deliberately, so a\n * tool that lists beats and an inspector that counts them never disagree. Choice prompts are not\n * in it - see `prompts`. */\n beats: number;\n /** Choice-option prompts: beats that live on a group rather than in a snippet.\n *\n * Counted separately rather than folded into `beats` because folding them in would make this\n * number disagree with `getBeatSequence`, and leaving them out entirely would understate a\n * choice-heavy story - a branching script could report a handful of beats and look like the wrong\n * build. Neither silence nor a redefinition; a second row. */\n prompts: number;\n /** Beats that fire a game event rather than producing player-facing words. */\n gameEvents: number;\n /** Cast members the bundle carries (player-facing only; the compiler strips\n * the authoring fields). */\n cast: number;\n}\n\nexport interface BundleDescription {\n identity: BundleIdentity;\n /** Everything game code may aim at, in bundle order. */\n addresses: AddressSummary[];\n /** What the host must supply. */\n hostScopes: HostScopeSummary[];\n /** What the story owns. */\n properties: OwnedProperties;\n /** The author-defined data surface, grouped by node kind. */\n gameData: GameDataSummary[];\n counts: BundleCounts;\n}\n\n/** Resolve a declaration's sharing default, which differs by the scope it sits\n * in: a project-level property is shared, a scene-local one is per-flow. */\nconst isShared = (d: PropertyDecl, scopeDefault: boolean): boolean =>\n d.shared ?? scopeDefault;\n\nfunction summariseProperty(d: PropertyDecl, scopeDefault: boolean): PropertySummary {\n return {\n name: d.name,\n type: d.type,\n hasDefault: d.default !== undefined,\n ...(d.default !== undefined ? { default: d.default } : {}),\n shared: isShared(d, scopeDefault),\n };\n}\n\nfunction summariseField(f: GameDataField): GameDataFieldSummary {\n return {\n name: f.name,\n type: f.type,\n hasDefault: f.default !== undefined,\n ...(f.values ? { values: [...f.values] } : {}),\n ...(f.purpose ? { purpose: f.purpose } : {}),\n };\n}\n\n/** One pass over a block's tree, accumulating counts. Iterative rather than\n * recursive: a deeply nested choice tree should not put an inspector's stack\n * at risk, and the traversal order does not matter for a count. */\nfunction countBlock(block: CompiledBlock, counts: BundleCounts): void {\n counts.blocks++;\n const stack: Array<CompiledGroup | CompiledSnippet> = [...block.children];\n while (stack.length) {\n const node = stack.pop()!;\n if (node.type === \"group\") {\n counts.groups++;\n if (node.prompt) counts.prompts++;\n stack.push(...node.children);\n continue;\n }\n counts.snippets++;\n for (const beat of node.beats ?? []) {\n counts.beats++;\n if (beat.kind === \"gameEvent\") counts.gameEvents++;\n }\n }\n}\n\n/**\n * Describe a compiled bundle: what it is, and what a game may call on it.\n *\n * Pure and allocation-light. Safe to call from an editor inspector on every\n * selection, though a details panel should still build its rows once rather\n * than per repaint.\n */\nexport function describeBundle(bundle: Bundle): BundleDescription {\n const counts: BundleCounts = {\n scenes: 0, blocks: 0, groups: 0, snippets: 0, beats: 0, prompts: 0, gameEvents: 0,\n cast: bundle.cast?.length ?? 0,\n };\n\n const addresses: AddressSummary[] = [];\n const sceneProps: OwnedProperties[\"scene\"] = [];\n for (const scene of Object.values(bundle.scenes)) {\n counts.scenes++;\n const gameId = effectiveGameId(scene);\n addresses.push({\n gameId,\n name: scene.name,\n blocks: scene.blocks.map((b) => ({ gameId: effectiveGameId(b), name: b.name })),\n });\n for (const block of scene.blocks) countBlock(block, counts);\n // Scene-local declarations default to PER-FLOW, unlike project-level ones.\n if (scene.sceneProps?.length) {\n sceneProps.push({ gameId, properties: scene.sceneProps.map((d) => summariseProperty(d, false)) });\n }\n }\n\n const hostScopes: HostScopeSummary[] = (bundle.scopeRegistry?.scopes ?? []).map((s) => ({\n token: s.token,\n writable: s.writable ?? true,\n opaque: s.declarations === undefined,\n // A host scope's values live outside the story, so \"shared\" is not a choice\n // its declarations make; they are world-wide by nature.\n properties: (s.declarations ?? []).map((d) => summariseProperty(d as PropertyDecl, true)),\n }));\n\n const gameData: GameDataSummary[] = Object.entries(bundle.gameDataFields ?? {})\n .filter(([, fields]) => (fields?.length ?? 0) > 0)\n .map(([kind, fields]) => ({\n kind: kind as GameDataNodeKind,\n fields: (fields ?? []).map(summariseField),\n }));\n\n return {\n identity: {\n schema: bundle.schema,\n project: bundle.content.project,\n ...(bundle.content.version !== undefined ? { version: bundle.content.version } : {}),\n ...(bundle.content.hash !== undefined ? { hash: bundle.content.hash } : {}),\n ...(bundle.content.structureHash !== undefined ? { structureHash: bundle.content.structureHash } : {}),\n voiced: bundle.voiced,\n defaultLocale: bundle.locales.default,\n locales: [...bundle.locales.included],\n // Absent means \"embedded\": the back-compat default a bundle written before\n // the field existed relies on.\n localisation: bundle.localisation?.mode ?? \"embedded\",\n sourceDebug: bundle.localisation?.sourceDebug ?? false,\n },\n addresses,\n hostScopes,\n properties: {\n patter: (bundle.properties ?? []).map((d) => summariseProperty(d, true)),\n scene: sceneProps,\n },\n gameData,\n counts,\n };\n}\n","// gameData read helpers (spec: author-defined custom fields per node type). The published bundle\n// carries the field SCHEMA per node type (`bundle.gameDataFields`, each field with its default) plus\n// each node's SPARSE overrides (`node.gameData`). Storage is sparse + merge-at-read: a node holds only\n// the values it overrides, and a reader falls back to the field's default. These pure helpers do that\n// resolution so a host doesn't re-implement it.\n\nimport type { Bundle, GameData, GameDataField, GameDataNodeKind } from \"@patterkit/model\";\n\n/** The author-defined gameData fields declared for a node TYPE in a bundle (empty when none). */\nexport function gameDataFields(bundle: Bundle, kind: GameDataNodeKind): GameDataField[] {\n return bundle.gameDataFields?.[kind] ?? [];\n}\n\n/** One node's effective value for a field: its sparse OVERRIDE if present, else the field's declared\n * default (undefined if neither is set). `fields` is the schema for the node's type. */\nexport function gameDataValue(fields: GameDataField[], node: GameData | undefined, name: string): unknown {\n if (node && Object.prototype.hasOwnProperty.call(node, name)) return node[name];\n return fields.find((f) => f.name === name)?.default;\n}\n\n/** A node's FULL effective gameData: every declared field resolved (override or default), plus any\n * override keys with no matching field (orphans, kept verbatim). Fields left with no value are omitted. */\nexport function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData {\n const out: GameData = {};\n for (const f of fields) {\n const v = gameDataValue(fields, node, f.name);\n if (v !== undefined) out[f.name] = v;\n }\n for (const [k, v] of Object.entries(node ?? {})) if (!(k in out)) out[k] = v;\n return out;\n}\n","// ---------------------------------------------------------------------------\n// Save / load: wrap the Engine's whole-game snapshot in a tagged, versioned\n// envelope so a host can drop it into localStorage / a file and restore it\n// safely (a foreign blob throws instead of corrupting a run).\n// ---------------------------------------------------------------------------\n\nimport type { Engine } from \"@patterkit/runtime\";\nimport { SAVE_SCHEMA } from \"@patterkit/model\";\nimport type { SaveEnvelope } from \"@patterkit/model\";\n\n// The envelope (`schema` + `save`) and the save shape inside it are the FAMILY's contract, defined in\n// @patterkit/model and written identically by every Patterplay runtime. Re-exported so existing\n// imports keep working.\nexport { SAVE_SCHEMA };\nexport type { SaveEnvelope };\n\n/** Capture the whole game as a tagged envelope (wraps `engine.saveGame()`). */\nexport function saveState(engine: Engine): SaveEnvelope {\n return { schema: SAVE_SCHEMA, save: engine.saveGame() };\n}\n\n/** Restore a {@link saveState} envelope into an engine (fresh or live). Throws on a foreign/blank blob. */\nexport function loadState(engine: Engine, env: SaveEnvelope): void {\n if (!env || env.schema !== SAVE_SCHEMA || !env.save) {\n throw new Error(`loadState: not a ${SAVE_SCHEMA} envelope`);\n }\n engine.loadGame(env.save);\n}\n\n/** Serialise the whole game to a JSON string (envelope + save-game) - drop into localStorage or a file. */\nexport function serializeState(engine: Engine): string {\n return JSON.stringify(saveState(engine));\n}\n\n/** Parse + restore a {@link serializeState} string. Throws on malformed JSON or a foreign envelope. */\nexport function deserializeState(engine: Engine, json: string): void {\n loadState(engine, JSON.parse(json) as SaveEnvelope);\n}\n","// ---------------------------------------------------------------------------\n// Runtime property setters: read / write `@patter` globals, `@scene` props, or\n// a wired foreign scope at runtime - e.g. the game pushing inventory into the\n// dialogue, or reading a flag the dialogue set. Thin pass-throughs, plus a batch\n// setter for convenience.\n//\n// They take an Engine or a Flow. Game-wide properties (`@hp`, `@world.x`) go\n// through either; `@scene` props belong to a flow, since each flow has its own,\n// so they need the Flow: the Engine refuses an `@scene` ref.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@patterkit/runtime\";\n\n/** A property value the engine accepts (a Patter scalar: number / boolean / string / string[] flags). */\nexport type PropertyValue = Parameters<Engine[\"setProperty\"]>[1];\n\n/** Where properties are read and written: the Engine (game-wide refs), or a Flow (those plus `@scene`). */\nexport type PropertyHolder = Engine | Flow;\n\n/** Read a runtime property (`@hp`, a foreign `@world.x`, or with a Flow `@scene.locked`). Undefined when unset. */\nexport function getProperty(holder: PropertyHolder, ref: string): PropertyValue | undefined {\n return holder.getProperty(ref);\n}\n\n/** Set one runtime property. Mirrors `setProperty` on the Engine or Flow, exported for discoverability + symmetry. */\nexport function setProperty(holder: PropertyHolder, ref: string, value: PropertyValue): void {\n holder.setProperty(ref, value);\n}\n\n/** Set many at once, e.g. `setProperties(engine, { \"@hp\": 10 })` or `setProperties(flow, { \"@scene.locked\": false })`. */\nexport function setProperties(holder: PropertyHolder, values: Record<string, PropertyValue>): void {\n for (const [ref, value] of Object.entries(values)) holder.setProperty(ref, value);\n}\n","// ---------------------------------------------------------------------------\n// The Patterplay state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry, plus `logStep`, which is this product's own.\n//\n// It used to diff whole `engine.saveGame()` snapshots. That could only ever say\n// what changed BETWEEN captures, which means it could not see a value that\n// changed and changed back, could not name the reason a host attached to a\n// write, and reported a write only once something asked. The kernel logs a\n// property write as it LANDS, on the PropertyBag audit hook, and keeps the diff\n// for state that has no hook - here, the visit counts.\n//\n// The Storylet Engine's logger was already this shape; its core is what moved\n// into the kernel. Same core, two adapters.\n//\n// Paths, unchanged:\n// @patter.x the shared globals\n// @scene:<sceneId>.x the shared scene props\n// visit:<nodeId> shared visit counts\n// <flowId>/... the same three, per flow (its not-shared halves)\n// ---------------------------------------------------------------------------\n\nimport type { Engine, StepResult } from \"@patterkit/runtime\";\nimport { createStateLogger as createKernelStateLogger, diffState } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, ScalarValue, StateChange, StateSnapshot } from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @patterkit/play-helpers should not have to care that they moved.\nexport { diffState };\nexport type { StateChange, StateSnapshot };\n\n/** A flattened runtime-state value: a Patter scalar. The kernel calls this `ScalarValue`;\n * the alias stays because it is exported API and reads in Patter's own vocabulary. */\nexport type StateValue = ScalarValue;\n\n/** Flatten the engine's whole-game state into a path -> value map (shared scopes + every live\n * flow). The logger does not diff this - it mounts the bags directly - but it stays as the public\n * \"what is the state right now\" call, and it reads the same mounts, so the two agree on the path\n * space. A bag loaded into the registry but not yet claimed (a scene no flow has re-entered since\n * a load) appears once it is. */\nexport function snapshotState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const m of allMounts(engine)) {\n const prefix = m.pathPrefix ?? m.bag.pathPrefix;\n for (const [name, v] of Object.entries(m.bag.save())) out[`${prefix}${name}`] = v;\n }\n Object.assign(out, visitState(engine));\n return out;\n}\n\n/** Every bag the engine and its flows hold, with the path each answers to in a log. */\nfunction allMounts(engine: Engine): LogMount[] {\n return [...engine.listBags(), ...engine.flows().flatMap((f) => f.listBags())];\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to `console.log`. */\n sink?: (line: string) => void;\n /** Prefix tag for every line, e.g. the flow / save-slot name. */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state (no logging). */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the property writes already logged as they landed,\n * plus the visit counts, diffed and re-baselined. */\n capture(): StateChange[];\n /** Trace one played step (line / text / game-event / choice / end), including any `gameData`. */\n logStep(step: StepResult): void;\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\nfunction describeStep(step: StepResult): string {\n switch (step.type) {\n case \"line\": return `line ${step.character ?? \"?\"}: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"text\": return `text: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"gameEvent\": return `game event ${step.id}${gd(step.gameData)}`;\n case \"choice\": return `choice (${step.options.length} option${step.options.length === 1 ? \"\" : \"s\"})`;\n case \"end\": return \"end\";\n }\n}\nconst gd = (data: unknown): string => (data ? ` gameData=${JSON.stringify(data)}` : \"\");\n\n/** The visit counts, which live in no bag and so have no audit hook: the kernel diffs\n * these on capture, exactly as the whole logger used to work. */\nfunction visitState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const [id, n] of Object.entries(engine.getVisitCounts())) out[`visit:${id}`] = n;\n for (const flow of engine.flows()) {\n for (const [id, n] of Object.entries(flow.getVisitCounts())) out[`${flow.id}/visit:${id}`] = n;\n }\n return out;\n}\n\n/** Create a state logger over an engine. Property writes log as they land; call `capture()`\n * after each `advance`/`choose` to pick up the visit counts and re-baseline. */\nexport function createStateLogger(engine: Engine, opts: StateLoggerOptions = {}): StateLogger {\n const tag = opts.label ? `[${opts.label}] ` : \"\";\n const kernel = createKernelStateLogger({\n // Re-read on every capture: openFlow and loadGame both replace bags, and the kernel\n // re-mounts whatever it is handed.\n mounts: (): LogMount[] => allMounts(engine),\n extra: () => visitState(engine),\n }, { sink: opts.sink, label: tag });\n\n return {\n snapshot: () => snapshotState(engine),\n capture: () => kernel.capture(),\n dispose: () => kernel.dispose(),\n logStep(step) { (opts.sink ?? ((l: string) => console.log(l)))(`${tag}${describeStep(step)}`); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live debug link (#181) - the game-side client.\n//\n// Streams a running game's story position to Patterpad over a localhost\n// WebSocket, so the editor can follow the live cursor like a debugger. It is\n// OBSERVE-ONLY: the game stays in control; the editor is a passive mirror.\n//\n// Wire protocol `patterplay/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:1, build, project?, flows? } - on connect\n// frame : { t:\"frame\", flow, sceneId, beatId, type, choiceId? } - per transition\n// flow : { t:\"flowOpen\" | \"flowClose\", flow } - lifecycle\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: live bundle refresh.\n// `data` is the full .patterc JSON. Pass an `onBundle` handler (or just feed it to\n// `applyLiveBundle`, which picks strings-only vs full hot swap itself), then call\n// `link.setBuild(newHash)` so the editor's match/stale pill updates.\n//\n// The link is loopback-only on the editor side (127.0.0.1), so there's no pairing\n// token - only processes on the same machine can reach it. The client never throws\n// into the game loop - a missing/closed editor just means frames go nowhere. Wire\n// it up after each advance()/choose():\n//\n// const link = createDebugLink({ build: bundle.content.hash });\n// link.flowOpened(\"main\");\n// // ...after each step:\n// link.observe(\"main\", flow.currentScene, step.id ?? null, step.type);\n// ---------------------------------------------------------------------------\n\n/** A minimal structural type for a WebSocket implementation (browsers + Node 21+ have a global one). */\nexport interface DebugSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"close\" | \"error\", listener: () => void): void;\n /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype DebugSocketCtor = new (url: string) => DebugSocketLike;\n\nexport interface DebugLinkOptions {\n /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */\n build: string;\n /** Optional project name, shown in the editor's debug panel. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4471`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (Node < 21, or tests with `ws`). */\n WebSocket?: DebugSocketCtor;\n /** Live bundle refresh: the editor pushed a freshly compiled bundle. `data` is the .patterc JSON;\n * hand it (with your current engine + bundle) to `applyLiveBundle`, re-bind your flow handles if it\n * hot-swapped, then call `link.setBuild(build)`. Never called with malformed payloads. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\nexport interface DebugLink {\n /** Tell the editor a flow opened (so it can list it in the follow selector). */\n flowOpened(flowId: string): void;\n /** Report the current position of a flow - call after each advance()/choose(). */\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void;\n /** Tell the editor a flow closed. */\n flowClosed(flowId: string): void;\n /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's\n * match/stale pill updates and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link. */\n close(): void;\n /** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game \"the editor\n * is not listening\" and \"I never attached\" look the same, and this tells them apart. */\n readonly state: \"connecting\" | \"connected\" | \"closed\";\n /** The build the link handshakes with (the bundle's content hash), as `setBuild` last left it. */\n readonly build: string;\n /** The address the link dials. */\n readonly url: string;\n}\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/**\n * Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects\n * or if it was never listening - safe to leave wired into a shipping build behind a flag.\n */\nexport function createDebugLink(opts: DebugLinkOptions): DebugLink {\n const url = opts.url ?? \"ws://127.0.0.1:4471\";\n const Ctor: DebugSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: DebugSocketCtor }).WebSocket;\n const flows = new Set<string>();\n let queue: string[] = [];\n let sock: DebugSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live bundle refresh lands\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n // Queue only while there is a socket that is connecting or open. Once the editor is gone (or never\n // answered), the socket is dropped and a message has nowhere to go, so it is dropped too: queueing it\n // anyway grew the queue by one string per step for the rest of a game that shipped with the link wired\n // in, which is exactly the build the docs say this is safe in.\n const post = (msg: object): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n queue.push(JSON.stringify(msg));\n flush();\n };\n\n if (!Ctor) {\n // No WebSocket available (old Node, no impl passed) - degrade to a silent no-op link.\n return { flowOpened() {}, observe() {}, flowClosed() {}, setBuild() {}, close() { closed = true; }, state: \"closed\", build, url };\n }\n\n const sendHello = (): void => {\n const hello = JSON.stringify({ t: \"hello\", v: 1, build, project: opts.project, flows: [...flows] });\n try { sock?.send(hello); } catch { /* race: closed immediately */ }\n };\n\n try {\n sock = new Ctor(url);\n sock.addEventListener(\"open\", () => {\n // Handshake first, so the editor can verify the build + seed the flow list before frames arrive.\n sendHello();\n flush();\n });\n // Live bundle refresh: the editor pushed a new bundle. Validate the shape here so the host's\n // handler never sees a malformed payload; everything else the editor might send is ignored.\n sock.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n sock.addEventListener(\"error\", () => { /* editor not listening - stay a no-op */ });\n sock.addEventListener(\"close\", () => { sock = null; queue = []; });\n } catch { sock = null; } // malformed URL etc. - never throw into the game\n\n return {\n flowOpened(flowId: string): void { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); },\n flowClosed(flowId: string): void { flows.delete(flowId); post({ t: \"flowClose\", flow: flowId }); },\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void {\n // A flow reports itself. `flowOpened` is the host's job and nothing could check it, so a game\n // that opened a flow and forgot to announce it left the editor's list short - and the omission\n // survived a reconnect, because the hello sends this set. Observing a flow we have not seen\n // announces it first, which makes the host's call a courtesy (it lists the flow before its\n // first step) rather than something to remember.\n if (!flows.has(flowId)) { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); }\n post({ t: \"frame\", flow: flowId, sceneId, beatId, type, choiceId });\n },\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n if (sock && sock.readyState === OPEN) sendHello(); // re-handshake: the editor re-reads the build\n },\n close(): void { closed = true; queue = []; try { sock?.close(); } catch { /* already gone */ } sock = null; },\n get state() { return closed || !sock ? \"closed\" : sock.readyState === OPEN ? \"connected\" : \"connecting\"; },\n get build() { return build; },\n url,\n };\n}\n","// ---------------------------------------------------------------------------\n// Live bundle refresh (design/proposals/live-bundle-refresh.md) - the game-side\n// applier. The editor pushes a freshly compiled bundle over the debug link\n// (createDebugLink's `onBundle`); this helper picks the right tier and applies\n// it:\n// - same structureHash -> tier 1: engine.replaceStrings() - nothing restarts.\n// - changed structure -> tier 2: engine.hotSwap() - the run carried over,\n// content drift resolved by the save system (§9.8).\n// Returns the engine to keep using (the SAME one for \"text\", a REPLACEMENT for\n// \"structure\" - re-bind your Flow handles via engine.getFlow(id)) plus the\n// parsed bundle for the next comparison. Wire-up:\n//\n// let { engine, bundle } = boot();\n// const link = createDebugLink({\n// build: bundle.content.hash!,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, bundle, data);\n// engine = r.engine; bundle = r.bundle;\n// if (r.kind === \"structure\") flow = engine.getFlow(\"main\")!;\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport { Engine } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/runtime\";\n\nexport interface LiveBundleResult {\n /** The engine to keep using: the same instance for a \"text\" swap, a replacement for \"structure\". */\n engine: Engine;\n /** The parsed pushed bundle - hold on to it for the next apply's comparison. */\n bundle: Bundle;\n /** Which tier applied: \"text\" (strings-only, nothing restarted) or \"structure\" (full hot swap). */\n kind: \"text\" | \"structure\";\n}\n\n/**\n * Apply a bundle the editor pushed over the debug link. `current` is the bundle `engine` is running\n * (needed for the structure-hash comparison); `data` is the pushed .patterc JSON. Throws only on\n * unparseable JSON - a structural swap's edge cases are absorbed by the §9.8 drift policy inside\n * `hotSwap`, which never throws for ordinary edits.\n */\nexport function applyLiveBundle(engine: Engine, current: Bundle, data: string): LiveBundleResult {\n const next = JSON.parse(data) as Bundle;\n // Same structure = a text-only edit: swap the string tables in place and keep everything running.\n // Missing structureHash on either side (an older compiler) falls through to the full swap - safe,\n // just less gentle than it could be.\n const sameStructure = current.content.structureHash !== undefined\n && current.content.structureHash === next.content.structureHash;\n if (sameStructure) {\n engine.replaceStrings(next);\n return { engine, bundle: next, kind: \"text\" };\n }\n return { engine: engine.hotSwap(next), bundle: next, kind: \"structure\" };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// A live @patter property inspector for the browser: a small DOM panel that\n// watches AND edits a running Engine's shared @patter properties. Type-aware\n// editors (bool / number / text / enum / flags), a reset-to-default per row, and\n// a poll that refreshes values WITHOUT clobbering the field you're editing.\n//\n// The JS parity of the Unity PatterStateWindow / Godot PatterStatePanel. Those\n// use a PatterDebug registry because the editor window is separate from the game;\n// here the game runs in-process, so you just pass the engine directly:\n//\n// const inspector = createPropertyInspector(engine, { container: document.body });\n// // ...later: inspector.destroy();\n// ---------------------------------------------------------------------------\n\nimport type { Engine, PropertyRow } from \"@patterkit/runtime\";\nimport { serializeState, deserializeState } from \"./save.js\";\nimport type { PropertyValue } from \"./properties.js\";\n\nexport interface PropertyInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Runtime state\". */\n title?: string;\n /** Live-refresh interval in ms; 0 disables polling (call `refresh()` yourself). Default 250. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** Re-read every property and update the editors, skipping the field you're editing. */\n refresh(): void;\n /** Stop polling and remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-inspector-style\";\nconst CSS = `\n.pp-insp{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:22rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-insp h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-insp-empty{color:#8a9691;font-style:italic}\n.pp-insp-io { display: flex; gap: 6px; margin: 0 0 8px; }\n .pp-insp-io button { font: inherit; padding: 2px 8px; cursor: pointer; }\n .pp-insp-row{display:flex;align-items:center;gap:.4rem;margin:.18rem 0}\n.pp-insp-ref{flex:0 0 8rem;font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.pp-insp-ctl{flex:1;min-width:0;display:flex}\n.pp-insp-ctl input[type=text],.pp-insp-ctl input[type=number],.pp-insp-ctl select{width:100%;box-sizing:border-box;font:inherit;padding:.15rem .3rem;border:1px solid #cfc7b8;border-radius:6px;background:#fff;color:inherit}\n.pp-insp-reset{flex:0 0 auto;border:1px solid #cfc7b8;background:#fff;border-radius:6px;cursor:pointer;color:#5c6b62;width:1.6rem;height:1.6rem;line-height:1;padding:0}\n.pp-insp-reset:disabled{opacity:.35;cursor:default}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\n/** Has the DISPLAYED value changed? Order-sensitive on purpose, and deliberately\n * NOT the language's value equality, which compares flags as a set: this asks\n * whether the row needs redrawing, and the row renders the list in its stored\n * order, so a reordering is a change here even though it is not a change to\n * what the value means. */\nfunction sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => x === b[i]);\n return a === b;\n}\n\nexport function createPropertyInspector(engine: Engine, opts: PropertyInspectorOptions = {}): PropertyInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n\n const el = doc.createElement(\"div\");\n el.className = \"pp-insp\";\n const heading = doc.createElement(\"h4\");\n heading.textContent = opts.title ?? \"Runtime state\";\n const list = doc.createElement(\"div\");\n\n // Save/Load the whole run as a patter/save@0 envelope file - the parity of Unity's\n // PatterStateWindow buttons, Godot's state-panel dialogs, and Unreal's Slate panel.\n const io = doc.createElement(\"div\");\n io.className = \"pp-insp-io\";\n const saveBtn = doc.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.textContent = \"Save state\\u2026\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const a = doc.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"save.patterstate\";\n a.click();\n URL.revokeObjectURL(a.href);\n });\n const loadBtn = doc.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.textContent = \"Load state\\u2026\";\n loadBtn.addEventListener(\"click\", () => {\n const input = doc.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".patterstate,application/json\";\n input.addEventListener(\"change\", () => {\n const file = input.files?.[0];\n if (!file) return;\n void file.text().then((text) => {\n try { deserializeState(engine, text); refresh(); }\n catch (err) { console.error(\"Load state failed:\", err instanceof Error ? err.message : err); }\n });\n });\n input.click();\n });\n io.append(saveBtn, loadBtn);\n el.append(heading, io, list);\n\n const rowRefreshers: Array<() => void> = [];\n\n const buildRow = (row: PropertyRow): void => {\n const r = doc.createElement(\"div\");\n r.className = \"pp-insp-row\";\n const label = doc.createElement(\"span\");\n label.className = \"pp-insp-ref\";\n label.textContent = row.path;\n label.title = row.path;\n const ctl = doc.createElement(\"div\");\n ctl.className = \"pp-insp-ctl\";\n const reset = doc.createElement(\"button\");\n reset.className = \"pp-insp-reset\";\n reset.type = \"button\";\n reset.textContent = \"↺\"; // ↺\n reset.title = \"Reset to default\";\n r.append(label, ctl, reset);\n list.appendChild(r);\n\n // `read` pushes the live engine value into the editor, but never while that editor has focus\n // (so it can't wipe what you're typing). `syncReset` disables the ↺ when the value is the default.\n let read: () => void;\n const commit = (v: PropertyValue): void => { engine.setProperty(row.path, v); syncReset(); };\n const focused = (node: Element): boolean => doc.activeElement === node;\n function syncReset(): void { reset.disabled = sameValue(engine.getProperty(row.path), row.default); }\n reset.addEventListener(\"click\", () => { engine.setProperty(row.path, row.default); read(); syncReset(); });\n\n if (row.type === \"boolean\") {\n const cb = doc.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.addEventListener(\"change\", () => commit(cb.checked));\n ctl.appendChild(cb);\n read = () => { if (!focused(cb)) cb.checked = engine.getProperty(row.path) === true; };\n } else if (row.type === \"number\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"number\";\n inp.addEventListener(\"change\", () => commit(Number(inp.value)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"enum\" || row.type === \"quality\") {\n // A quality edits as a dropdown of its STAGES - the ladder is closed, like an enum's values.\n const sel = doc.createElement(\"select\");\n for (const v of (row.type === \"quality\" ? row.stages : row.values) ?? []) {\n const o = doc.createElement(\"option\");\n o.value = v; o.textContent = v;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", () => commit(sel.value));\n ctl.appendChild(sel);\n read = () => { if (!focused(sel)) sel.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"flags\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.placeholder = \"comma, separated, flags\";\n inp.addEventListener(\"change\", () => commit(inp.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) { const v = engine.getProperty(row.path); inp.value = Array.isArray(v) ? v.join(\", \") : \"\"; } };\n } else {\n // string (and any unrecognised type) -> a text field\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.addEventListener(\"change\", () => commit(inp.value));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n }\n\n read();\n syncReset();\n rowRefreshers.push(() => { read(); syncReset(); });\n };\n\n // Properties are fixed for a bundle, so build the rows once; polling just refreshes their values.\n const props = engine.listProperties();\n if (props.length === 0) {\n const empty = doc.createElement(\"div\");\n empty.className = \"pp-insp-empty\";\n empty.textContent = \"No @patter properties.\";\n list.appendChild(empty);\n } else {\n for (const row of props) buildRow(row);\n }\n\n const refresh = (): void => { for (const fn of rowRefreshers) fn(); };\n\n opts.container?.appendChild(el);\n const pollMs = opts.pollMs ?? 250;\n let timer: ReturnType<typeof setInterval> | undefined;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n return {\n el,\n refresh,\n destroy() { if (timer !== undefined) clearInterval(timer); el.remove(); },\n };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// The bundle inspector's VIEW, for the web: a read-only panel over\n// `describeBundle`, in the same visual grammar as the live property inspector.\n//\n// The other three runtimes hang their view off an imported asset - a Unity\n// CustomEditor, an Unreal details customisation, a Godot EditorInspectorPlugin.\n// JS has no asset pipeline, so there is no inspector to extend and the summary\n// function IS the surface. This exists because \"the function is the surface\"\n// leaves the JS side without the thing the other three give you for free: a\n// place to LOOK at a bundle. A drop-in page can now do\n//\n// const panel = createBundleInspector(bundle, { container: document.body });\n// // ...later: panel.destroy();\n//\n// Read-only on purpose. The live inspector edits because a running game has\n// state worth changing; this describes an artefact, and there is nothing here a\n// reader could sensibly write to.\n// ---------------------------------------------------------------------------\n\nimport { describeBundle } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/model\";\nimport type { BundleDescription, PropertySummary } from \"@patterkit/runtime\";\n\nexport interface BundleInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Bundle\". */\n title?: string;\n /** Sections open on mount. Default: identity and addresses, the two an\n * integrator reaches for first; the rest are a click away. */\n open?: BundleSection[];\n}\n\nexport type BundleSection = \"identity\" | \"addresses\" | \"hostScopes\" | \"properties\" | \"gameData\" | \"counts\";\n\nexport interface BundleInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** The description the panel is showing, for a caller that wants the data too. */\n readonly description: BundleDescription;\n /** Remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-bundle-style\";\n// Deliberately the property inspector's palette and metrics: the two panels are\n// often on the same page, and a second look-and-feel would read as a second tool.\nconst CSS = `\n.pp-bundle{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:26rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-bundle h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-bundle details{border-top:1px solid #e3dccf;padding:.3rem 0}\n.pp-bundle details:first-of-type{border-top:0}\n.pp-bundle summary{cursor:pointer;font-weight:600;color:#214f4b;list-style:none}\n.pp-bundle summary::-webkit-details-marker{display:none}\n.pp-bundle summary::before{content:\"\\\\25B8\";display:inline-block;width:1rem;color:#8a9691}\n.pp-bundle details[open]>summary::before{content:\"\\\\25BE\"}\n.pp-bundle-count{color:#8a9691;font-weight:400}\n.pp-bundle-row{display:flex;gap:.4rem;margin:.16rem 0 .16rem 1rem}\n.pp-bundle-key{flex:0 0 8rem;color:#5c6b62}\n.pp-bundle-val,.pp-bundle-addr{font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;word-break:break-all}\n.pp-bundle-sub{margin-left:2rem}\n.pp-bundle-empty{color:#8a9691;font-style:italic;margin-left:1rem}\n.pp-bundle-warn{color:#8a3a2f;font-weight:600}\n.pp-bundle-tag{font-size:.7rem;color:#5c6b62;border:1px solid #cfc7b8;border-radius:5px;padding:0 .25rem;margin-left:.3rem}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\nconst el = (doc: Document, tag: string, cls?: string, text?: string): HTMLElement => {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text !== undefined) e.textContent = text;\n return e;\n};\n\n/** One \"key: value\" line. */\nfunction row(doc: Document, key: string, value: string, valueClass = \"pp-bundle-val\"): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key\", key), el(doc, \"span\", valueClass, value));\n return r;\n}\n\n/** A property line: the name, its type, and whether the HOST must supply a value.\n * \"no default\" is the part an integrator is actually scanning for. */\nfunction propertyRow(doc: Document, p: PropertySummary): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", p.name));\n const meta = el(doc, \"span\", \"pp-bundle-val\", p.type);\n if (!p.hasDefault) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"no default\"));\n r.append(meta);\n return r;\n}\n\nfunction section(doc: Document, name: BundleSection, label: string, count: number | null, open: boolean): HTMLDetailsElement {\n const d = doc.createElement(\"details\");\n d.dataset[\"section\"] = name;\n d.open = open;\n const s = doc.createElement(\"summary\");\n s.textContent = label;\n if (count !== null) s.append(el(doc, \"span\", \"pp-bundle-count\", ` ${count}`));\n d.append(s);\n return d;\n}\n\n/**\n * Build a read-only panel describing a compiled bundle: what it is, what game\n * code may call on it, and what the host must supply.\n */\nexport function createBundleInspector(bundle: Bundle, opts: BundleInspectorOptions = {}): BundleInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n const d = describeBundle(bundle);\n const open = new Set<BundleSection>(opts.open ?? [\"identity\", \"addresses\"]);\n\n const root = el(doc, \"div\", \"pp-bundle\");\n root.append(el(doc, \"h4\", undefined, opts.title ?? \"Bundle\"));\n\n // --- identity ------------------------------------------------------------\n const id = section(doc, \"identity\", d.identity.project || \"(unnamed project)\", null, open.has(\"identity\"));\n if (d.identity.version) id.append(row(doc, \"version\", d.identity.version));\n id.append(row(doc, \"schema\", d.identity.schema));\n id.append(row(doc, \"locales\", `${d.identity.defaultLocale}${d.identity.locales.length > 1 ? ` (+${d.identity.locales.length - 1})` : \"\"}`));\n id.append(row(doc, \"strings\", d.identity.localisation));\n if (d.identity.voiced) id.append(row(doc, \"voiced\", \"yes\"));\n if (d.identity.hash) id.append(row(doc, \"hash\", d.identity.hash));\n // Equal structureHash + a different hash = a text-only edit, which is what makes\n // a live hot-swap safe. Both are shown so an integrator can tell those apart.\n if (d.identity.structureHash) id.append(row(doc, \"structure\", d.identity.structureHash));\n if (d.identity.sourceDebug) {\n id.append(row(doc, \"build\", \"SOURCE DEBUG - not shippable\", \"pp-bundle-val pp-bundle-warn\"));\n }\n root.append(id);\n\n // --- addresses -----------------------------------------------------------\n const addr = section(doc, \"addresses\", \"Addresses\", d.addresses.length, open.has(\"addresses\"));\n if (!d.addresses.length) addr.append(el(doc, \"div\", \"pp-bundle-empty\", \"no scenes\"));\n for (const a of d.addresses) {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", a.gameId), el(doc, \"span\", \"pp-bundle-val\", a.name));\n addr.append(r);\n // Nested, because a block address is scene-scoped: the pair is the address.\n for (const b of a.blocks) {\n const br = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n br.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", b.gameId), el(doc, \"span\", \"pp-bundle-val\", b.name));\n addr.append(br);\n }\n }\n root.append(addr);\n\n // --- host scopes ---------------------------------------------------------\n // Counted in PROPERTIES rather than scopes: the heading says \"properties\", and a\n // reader comparing it with the rows below should not have to work out which it meant.\n // An opaque scope contributes none, and says so on its own row.\n const hostCount = d.hostScopes.reduce((n, s) => n + s.properties.length, 0);\n const host = section(doc, \"hostScopes\", \"Host properties\", hostCount, open.has(\"hostScopes\"));\n if (!d.hostScopes.length) host.append(el(doc, \"div\", \"pp-bundle-empty\", \"the game supplies nothing\"));\n for (const s of d.hostScopes) {\n const head = el(doc, \"div\", \"pp-bundle-row\");\n head.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", `@${s.token}`));\n const meta = el(doc, \"span\", \"pp-bundle-val\", s.opaque ? \"any name, unchecked\" : `${s.properties.length} declared`);\n if (!s.writable) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"read-only\"));\n head.append(meta);\n host.append(head);\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n host.append(pr);\n }\n }\n root.append(host);\n\n // --- owned properties ----------------------------------------------------\n const ownedCount = d.properties.patter.length + d.properties.scene.reduce((n, s) => n + s.properties.length, 0);\n const owned = section(doc, \"properties\", \"Story properties\", ownedCount, open.has(\"properties\"));\n if (!ownedCount) owned.append(el(doc, \"div\", \"pp-bundle-empty\", \"none declared\"));\n for (const p of d.properties.patter) owned.append(propertyRow(doc, p));\n for (const s of d.properties.scene) {\n owned.append(row(doc, `@scene`, s.gameId, \"pp-bundle-val pp-bundle-addr\"));\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n owned.append(pr);\n }\n }\n root.append(owned);\n\n // --- gameData ------------------------------------------------------------\n if (d.gameData.length) {\n const gd = section(doc, \"gameData\", \"Game data\", d.gameData.reduce((n, g) => n + g.fields.length, 0), open.has(\"gameData\"));\n for (const g of d.gameData) {\n gd.append(row(doc, \"on\", g.kind));\n for (const f of g.fields) {\n const fr = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n fr.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", f.name), el(doc, \"span\", \"pp-bundle-val\", f.type));\n gd.append(fr);\n }\n }\n root.append(gd);\n }\n\n // --- counts --------------------------------------------------------------\n const counts = section(doc, \"counts\", \"Counts\", null, open.has(\"counts\"));\n for (const [key, value] of Object.entries(d.counts)) counts.append(row(doc, key, String(value)));\n root.append(counts);\n\n opts.container?.append(root);\n return {\n el: root,\n description: d,\n destroy() { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Audio resolver (#206): map a beat id to the path/URL of its winning audio\n// take, using the `patteraudio.json` manifest Patterpad (or the CLI) emits next\n// to the Audio Folders. It RESOLVES ONLY - playback stays yours (Web Audio, an\n// <audio> element, etc.), which can't be portable across platforms anyway.\n//\n// The manifest already encodes the highest-rung \"winner\" per beat, so there's no\n// folder search at runtime. Deploy your audio folder somewhere your game can read\n// (e.g. under /public), fetch the manifest once, and:\n//\n// const audio = createAudioResolver(manifestJson, \"audio\"); // base = where the folder is served\n// const src = audio.resolve(step.id); // \"audio/final/L1.wav\", or null when the beat has no recording\n// if (src) new Audio(src).play();\n// ---------------------------------------------------------------------------\n\nexport interface AudioResolver {\n /** The full path/URL of a beat's winning audio take, or null when it has none. */\n resolve(beatId: string): string | null;\n}\n\ninterface AudioManifest {\n schema?: string;\n clips?: Record<string, { file: string; status?: string }>;\n}\n\n/**\n * Build an audio resolver from a `patteraudio.json` manifest string plus the base path/URL its files live\n * under (wherever you deployed the audio folder). `resolve(beatId)` joins the base with the winning clip's\n * relative path, or returns null when the beat has no recording. Never throws on a missing beat.\n */\nexport function createAudioResolver(manifestJson: string, basePath: string): AudioResolver {\n const manifest = JSON.parse(manifestJson) as AudioManifest;\n const clips = manifest.clips ?? {};\n // A base that already ends in a separator is joined as it stands: trimming it turned a root such as \"/\"\n // or \"res://\" into \"\" or \"res:\", and the path lost its root.\n const sep = basePath === \"\" || /[/\\\\]$/.test(basePath) ? \"\" : \"/\";\n return {\n resolve(beatId: string): string | null {\n const clip = clips[beatId];\n if (!clip || !clip.file) return null;\n return `${basePath}${sep}${clip.file}`;\n },\n };\n}\n"],"mappings":"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","names":["browser_exports","__export","Checkpoint","Engine","Flow","SAVE_SCHEMA","applyLiveBundle","buildTagIndex","createAudioResolver","createBundleInspector","createDebugLink","createPropertyInspector","createStateLogger","describeBundle","deserializeState","diffState","effectiveGameData","gameDataFields","gameDataValue","getProperty","loadState","saveState","serializeState","setProperties","setProperty","snapshotState","own","record","key","deserialiseAst","node","args","EvalError","message","evaluate","node","ctx","dialect","missingPolicy","s","rec","n","scope","own","val","arg","ladder","ladderOf","current","stageIndex","def","r","left","right","lLadder","rLadder","sameLadder","valueEquals","assertNumbers","a","b","x","y","i","l","op","value","toUint32","seed","modded","makePrng","s","CHECK_FLAGS_COUNTING_CALL","node","DEFAULT_COUNTING_CALLS","matchedSpecificity","evalTruthy","opts","countingCalls","walk","want","l","r","rule","c","operands","holds","diffState","prev","next","changes","paths","path","from","to","show","v","prefixOf","m","createStateLogger","adapter","opts","sink","line","label","emit","c","full","out","prefix","name","value","baseline","pushed","mounted","hook","bag","change","mount","mounts","i","diffed","PropertyBag","_PropertyBag","declarations","opts","d","name","defaultFor","value","n","change","audit","fn","rowFor","c","k","values","v","writable","pathPrefix","rowName","lowerCase","SAVE_FRAGMENT_VERSION","ScopeRegistry","token","o","bag","waiting","e","prefix","key","resolver","norm","decls","scope","out","owner","row","host","view","scopes","qualities","normOf","aliases","declsOf","m","properties","vals","blob","fragment","by","wants","ENGINE_SCOPES","host","h","patterDialect","args","EvalError","next","a","b","lo","hi","flagsCall","flags","readFlags","i","arg","result","idx","idArg","nodeId","EXTERNAL_SCOPES","ENGINE_SCOPES","s","t","splitRef","ref","isScope","parts","nodeId","args","h","fn","v","EvalError","idArg","fnName","first","readFlags","arg","flagsCall","meta","i","BARE_REF","tokenise","text","buf","i","c","close","raw","inner","renderSlotValue","v","isCaptionWs","c","collapseCaptionWs","s","out","pendingSpace","stripCaptions","text","open","close","i","removed","end","interpolate","resolve","tok","tokenise","walkNodes","nodes","visit","node","children","gameIdify","text","effectiveGameId","entity","g","gameIdify","castStringKey","name","DEFAULT_CAPTION_DELIMITERS","DEFAULT_CAPTION_CHARACTER","SAVE_SCHEMA","SAVE_VERSION","dedupe","tags","seen","out","t","buildTagIndex","bundle","index","visit","node","inherited","acc","child","beat","scene","sceneAcc","block","blockAcc","astCache","tagIndexCache","tagIndexFor","bundle","index","buildTagIndex","tagsOf","host","id","OWNER","esc","OWNS_REGISTRY","keys","sceneId","flowId","Checkpoint","journal","Engine","_Engine","options","locale","allStrings","strings","defaultStrings","loc","emitIds","castDisplay","c","nodeIndex","blockIndex","blockById","scene","effectiveGameId","blockAddrs","block","walkNodes","n","props","patterSharedDecls","p","toDecl","patterLocalDecls","patterSharedNames","d","registry","ScopeRegistry","ownsRegistry","patterBag","PropertyBag","hostScopes","registered","hostBound","token","resolver","boundScopes","spec","s","decls","toForeignDecl","selfBackedDecl","e","k","sceneSharedNames","names","flow","event","DEFAULT_CAPTION_DELIMITERS","DEFAULT_CAPTION_CHARACTER","snapshot","carryOver","next","make","fresh","f","on","opts","blockId","prior","Flow","existing","resolveScene","resolveBlock","ref","beatId","sceneRef","blockRef","out","collectCast","gameDataField","beat","b","tags","info","name","castStringKey","source","mounts","bag","checkpoint","undo","i","what","keep","reg","t","scope","value","prev","handler","h","defaultFor","split","splitHostRef","blob","flows","SAVE_VERSION","serialiseSelectors","save","version","values","sectionsFromV2","st","deserialiseSelectors","snap","v","seed","toUint32","base","scopes","key","ladders","first","rngState","started","flowEnded","currentSceneId","activeSnippet","beatIndex","pendingChoice","pendingPrompt","pendingPromptOwnerId","stack","played","r","transitions","jump","frame","children","from","choice","option","o","node","authored","shared","savedPrompt","nextId","at","ch","byId","owner","selector","pick","containerId","snippet","group","fallbacks","child","eligible","hidden","fallback","to","mode","verdicts","_","order","exhaust","trace","picked","len","stick","fill","eligibleIds","last","pool","remaining","best","tier","x","want","matchedSpecificity","truthy","evaluate","patterDialect","err","map","seen","was","copy","effects","kind","expr","message","ast","deserialiseAst","own","ownBefore","before","prng","makePrng","draw","withTags","raw","off","text","silent","interpolate","stripCaptions","shown","found","active","character","claimed","mountClaims","decl","nodes","gameData","rec","ask","cacheKey","scopeWritable","writable","hit","splitRef","isShared","d","scopeDefault","summariseProperty","summariseField","f","countBlock","block","counts","stack","node","beat","describeBundle","bundle","addresses","sceneProps","scene","gameId","effectiveGameId","b","hostScopes","s","gameData","fields","kind","gameDataFields","bundle","kind","gameDataValue","fields","node","name","f","effectiveGameData","out","v","k","saveState","engine","SAVE_SCHEMA","loadState","env","serializeState","deserializeState","json","getProperty","holder","ref","setProperty","value","setProperties","values","snapshotState","engine","out","m","allMounts","prefix","name","v","visitState","f","describeStep","step","gd","data","id","flow","n","createStateLogger","opts","tag","kernel","l","createDebugLink","opts","url","Ctor","flows","queue","sock","closed","build","flush","m","post","msg","sendHello","hello","ev","flowId","sceneId","beatId","type","choiceId","next","applyLiveBundle","engine","current","data","next","STYLE_ID","CSS","injectStyle","doc","s","sameValue","a","b","x","i","createPropertyInspector","engine","opts","el","heading","list","io","saveBtn","blob","serializeState","loadBtn","input","file","text","deserializeState","refresh","err","rowRefreshers","buildRow","row","r","label","ctl","reset","read","commit","v","syncReset","focused","node","cb","inp","sel","o","props","empty","fn","pollMs","timer","STYLE_ID","CSS","injectStyle","doc","s","el","tag","cls","text","e","row","key","value","valueClass","r","propertyRow","p","meta","section","name","label","count","open","d","createBundleInspector","bundle","opts","describeBundle","root","id","addr","a","b","br","hostCount","n","host","head","pr","ownedCount","owned","gd","g","f","fr","counts","createAudioResolver","manifestJson","basePath","clips","sep","beatId","clip"]}
|