@patterkit/runtime 0.5.0 → 0.5.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/CHANGELOG.md +17 -1
- package/dist/index.cjs +29 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +29 -3
- package/dist/index.js.map +1 -1
- package/dist/patterplay.min.js +2 -2
- package/dist/patterplay.min.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../../../../expr/packages/expr/src/ast.ts","../../../../expr/packages/expr/src/evaluate.ts","../../../node_modules/@wildwinter/expr-specificity/src/index.ts","../../../../expr/packages/scoperegistry/src/index.ts","../../dialect/src/index.ts","../../model/src/index.ts","../src/tags.ts","../src/engine.ts","../src/describe.ts","../src/gamedata.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @patterkit/runtime - public surface.\n//\n// The reference runtime. Construct an `Engine` from a compiled Bundle (the world\n// + flow manager: shared scope state, foreign scopes, whole-game save/load), then\n// `engine.openFlow(id, { scene })` to get a `Flow` and play it (advance / choices\n// / properties). Many flows run concurrently, sharing the shared `@patter`/`@scene`\n// state, each with its own per-flow half + cursor + PRNG.\n// ---------------------------------------------------------------------------\n\nexport { Engine, Flow } from \"./engine.js\";\n// The compiled-bundle type the Engine constructor consumes (from the shared model), so hosts can\n// type a parsed .patterc without depending on @patterkit/model directly.\nexport type { Bundle } from \"@patterkit/model\";\nexport type {\n StepResult, AdvanceToStopResult, ChoiceOption, EngineOptions, OpenFlowOptions, WorldResolver, PropertyRow,\n EngineSave, SaveGame, FlowSnapshot, SelectorSnapshot, SavedChoice, StackFrame,\n BeatInfo, OutlineNode, OutlineBlock, OutlineScene, FlatBeat,\n} from \"./engine.js\";\n\n// The bundle inspector's runtime half: what a game may call, read off the asset with no Engine.\nexport { describeBundle } from \"./describe.js\";\nexport type {\n BundleDescription, BundleIdentity, AddressSummary, HostScopeSummary,\n PropertySummary, OwnedProperties, GameDataSummary, GameDataFieldSummary, BundleCounts,\n} from \"./describe.js\";\n\n// gameData read helpers (sparse overrides + field-default merge).\nexport { gameDataFields, gameDataValue, effectiveGameData } from \"./gamedata.js\";\n\n// Author tags (#215): accumulated node-tag index (also surfaced via Engine.tagsFor* + step.tags).\nexport { buildTagIndex } from \"./tags.js\";\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\";\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 = 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 : (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 const def = 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 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 element-wise by value, in order. Plain\n * `===` on arrays would be reference equality - two distinct arrays with the\n * same contents would never be equal, and a fresh array (e.g. from a scope\n * read or a function result) would never equal another. Mixed array/non-array\n * operands are unequal. Matches the value-equality the Unreal and Unity\n * runtimes implement for flags.\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 for (let i = 0; i < a.length; i++) if (a[i] !== b[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// @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// @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; default is read/write. (`type`/`values` feed validation.)\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\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 type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: 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 readonly values: 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 constructor(declarations: ScopeDeclaration[] = [], opts?: { normalise?: (name: string) => string }) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\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 /** 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. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string }): BagChange {\n const n = this.norm(name);\n if (this.decls.get(n)?.writable === false) 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 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));\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 });\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(d: ScopeDeclaration, value: ScalarValue | undefined, writable?: boolean, name?: string): PropertyRow {\n return {\n name: name ?? d.name.toLowerCase(),\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\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}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n}\ntype Entry = OwnedScope | ForeignScope;\n\n/** The versioned owned-state fragment both product save envelopes embed\n * (design/engine-runtimes.md 3.1: one serialisation shape for bags). */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\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 defineOwned(token: string, declarations: ScopeDeclaration[]): this {\n return this.mountOwned(token, new PropertyBag(declarations));\n }\n\n /**\n * Attach an EXISTING bag as an owned scope - the shared-container move: a\n * host (or the other product) holds the bag; this registry reads, writes\n * and lists it like its own, but the holder saves it.\n */\n mountOwned(token: string, bag: PropertyBag): this {\n this.assertFree(token);\n this.scopes.set(token, { kind: \"owned\", bag });\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 scopeWritable = true,\n ): this {\n this.assertFree(token);\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(d.name.toLowerCase(), d);\n this.scopes.set(token, { kind: \"foreign\", resolver, decls, scopeWritable });\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(name.toLowerCase());\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 or\n * read-only scope/property. */\n set(scope: string, name: string, value: ScalarValue): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n try {\n e.bag.set(name, value);\n } catch {\n throw new Error(`'@${scope}.${name}' is read-only`);\n }\n return;\n }\n const n = name.toLowerCase();\n if (!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 } & PropertyRow)[] {\n const out: ({ scope: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...row });\n } else {\n for (const d of e.decls.values()) {\n out.push({\n scope: token,\n ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase())),\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>): EvalContext {\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of this.scopes) {\n scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n }\n return { scopes, host };\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.\n */\n toSchema(): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[] }>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[] }>();\n for (const d of decls) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes only (foreign scopes are host-owned,\n * host-saved), as bare bags - the 0.1.x shape, kept stable so existing\n * consumers' save formats are untouched. A product embedding the\n * versioned cross-product shape uses `saveFragment`. */\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 return out;\n }\n\n /** Restore owned-scope values from a `save` blob. Unknown/foreign scopes\n * are ignored. */\n load(blob: Record<string, Record<string, ScalarValue>>): void {\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 }\n }\n\n /** The versioned owned-state fragment (the one serialisation shape both\n * product families' save envelopes embed when they adopt the kernel;\n * design/engine-runtimes.md 3.1). `save()` wrapped with a version stamp. */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /** Restore from a versioned fragment; an unsupported version throws. */\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 private assertFree(token: string): void {\n if (this.scopes.has(token)) throw new Error(`scope '@${token}' is already registered`);\n }\n}\n\nfunction defaultFor(d: ScopeDeclaration): 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 }\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 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 * 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(spec?: ScopeRegistrySpec): Dialect {\n if (!spec || 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.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[] }>>();\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 });\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[] }>();\n for (const d of scope.declarations) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values });\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. */\nexport const RESERVED_PROPERTY_NAMES: readonly string[] = [\"true\", \"false\", \"and\", \"or\", \"not\"];\n\n/** Coerce a label into a legal property name: lower case, apostrophes dropped, runs of\n * anything else to a single underscore, no trailing underscore, an underscore in front\n * of a leading digit, one behind a keyword. \"\" when nothing usable was left. */\nexport function propertyNameify(text: string): string {\n const trimmed = text.trim();\n // An underscore the author typed is kept; one that is only the ghost of leading\n // punctuation is not. That is the difference between `_private` and `!gold`.\n const deliberateLeading = trimmed.startsWith(\"_\");\n let out = trimmed.toLowerCase().replace(/['\\u2019]/g, \"\")\n .replace(/[^a-z0-9_]+/g, \"_\").replace(/_+/g, \"_\").replace(/^_+|_+$/g, \"\");\n if (out === \"\") return \"\";\n if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;\n if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;\n return out;\n}\n\n/** Is this a name an expression can actually reach? Lower case letters, digits and\n * underscores, not starting with a digit, not a keyword. \"\" is not a name. */\nexport function isValidPropertyName(name: string): boolean {\n return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);\n}\n\n/** True when folding case ALONE would make it legal (`isNight`). The only violation a\n * loader may repair without guessing at intent: every reference is folded already, so\n * folding the declaration to match changes nothing observable. */\nexport function isCaseOnlyPropertyName(name: string): boolean {\n return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());\n}\n\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\";\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 /** 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 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); studios add their\n * own (e.g. `sfx`, `art`), 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];\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 /** 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 (review flow, design/proposals/\n * suggest-rewrite.md). Whole-beat anchored: `baseline` is the say text WHEN suggested (the \"before\" +\n * the drift detector - if it no longer matches the live text, the line changed since and the suggestion\n * is shown stale), `proposed` is the replacement. Accept overwrites the beat's say text; both accept and\n * reject set `resolved` (archived) + `outcome` (a light audit trail). Stored in the authoring shard,\n * never in the flow text, so downstream tools ignore it. */\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 author: string;\n ts: string;\n /** Accepted or rejected -> archived (hidden unless \"show resolved suggestions\" is on). */\n resolved?: boolean;\n outcome?: \"accepted\" | \"rejected\";\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// 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 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// 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) 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 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// `engine.saveGame()` / `loadGame()` snapshot + restore the WHOLE game: `@patter`\n// plus every live flow's scopes + PRNG + cursor.\n// ---------------------------------------------------------------------------\n\nimport { evaluate, deserialiseAst } 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 type { 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 type {\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/** A property-state snapshot: owned scope -> property name -> value. */\nexport type EngineSave = Record<string, Record<string, ScalarValue>>;\n\n/** Serialised `sequence` selector visit state for one group (spec §4 / §7). */\nexport interface SelectorSnapshot {\n seq?: number; // sequential cursor (visits taken)\n bag?: string[]; // shuffle: child ids still undrawn this pass\n last?: string; // last child id picked (no-immediate-repeat)\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 set on a live frame): the id of the child at `index` when the save was\n * taken. On restore the child is re-found by this id, so a save survives siblings being inserted,\n * removed, or reordered before the cursor (live bundle refresh / patched-game saves). Absent (an\n * older save, or a frame saved at its container's end) falls back to the raw `index`. */\n nextId?: string;\n}\n\n/** The serialised cursor + scopes + PRNG of a single flow. */\nexport interface FlowSnapshot {\n /** This flow's owned-scope values = the NOT-shared `@patter` globals (under token \"patter\"). */\n scopes: EngineSave;\n /** Per-scene NOT-shared `@scene` bags (scene id -> name -> value); persist across re-entries (spec §7). */\n sceneBags: Record<string, Record<string, ScalarValue>>;\n /** This flow's built-in PRNG position (mulberry32 state). */\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: {\n flowEnded: boolean;\n currentSceneId: string | null;\n /** The continuation stack (call frames + the active block run). */\n stack: StackFrame[];\n activeSnippetId: string | null;\n beatIndex: number;\n /** The pending choice's exact option set, REPLAYED on load (schema 9.3). */\n pendingChoice: SavedChoice | null;\n /** The chosen option owning a prompt still to be replayed (save taken between choose + advance).\n * Optional / absent in older saves -> no pending prompt. */\n pendingPromptOwnerId?: string | null;\n /** This flow's `sequence` selector cursors. */\n selectors: Record<string, SelectorSnapshot>;\n };\n}\n\n/**\n * A pending choice as saved: the option set the player was shown, restored\n * verbatim - re-deriving on load would re-evaluate conditions (consuming PRNG\n * draws a second time) and could mutate the choice under the player.\n */\nexport interface SavedChoice {\n groupId: string;\n options: ChoiceOption[];\n}\n\n/** A full resumable save-game: shared `@patter` state + every live flow. */\nexport interface SaveGame {\n version: number;\n /** Shared `@patter` globals (owned scope \"patter\"). */\n shared: 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 /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) - the shared scene props. */\n stageBags: Record<string, Record<string, ScalarValue>>;\n /** Each live flow's snapshot, keyed by flow id. */\n flows: Record<string, FlowSnapshot>;\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 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 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\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. Shared by all flows. */\n world?: WorldResolver;\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}\n\n/** One shared `@patter` property, for a live state inspector: its ref, declared type, current value,\n * declared default (for reset), and enum options. Mirrors the Unity / Godot ports' ListProperties. */\nexport interface PropertyRow {\n ref: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n}\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/** Shared, read-mostly context the engine hands to every flow it owns. */\ninterface FlowHost {\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 SHARED `@patter` globals (owned scope \"patter\") + world properties (`@world`). */\n shared: ScopeRegistry;\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 /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */\n stageBags: Map<string, Record<string, ScalarValue>>;\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 /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on `shared`'s scope\n * set, which is fixed for the engine's life, so every effect target / `{@ref}` slot parses once. */\n refSplitCache: Map<string, { scope: string; name: string }>;\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\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 const shared = new ScopeRegistry().defineOwned(\"patter\", patterSharedDecls);\n const hostBound = new Set<string>();\n // The host's World Properties resolver binds `@world`; its declarations (types, read-only) come from\n // the compiled bundle's declared world properties. An explicit binding always wins over the self-backed\n // fallback below.\n if (options.world) {\n const worldSpec = bundle.scopeRegistry?.scopes.find((s) => s.token === \"world\");\n const decls = (worldSpec?.declarations ?? []).map(toForeignDecl);\n shared.defineForeign(\"world\", options.world, decls, worldSpec?.writable ?? true);\n hostBound.add(\"world\");\n }\n // A project that DECLARES `@world` but whose embedder binds no resolver (the standalone case) gets a\n // self-backed one: a live in-memory bag seeded from the declarations' defaults. The story reads/writes\n // it like any scope; it stays *foreign* (not in Patter's save: the host owns it conceptually).\n for (const spec of bundle.scopeRegistry?.scopes ?? []) {\n if (hostBound.has(spec.token)) continue;\n const decls = (spec.declarations ?? []).map(toForeignDecl);\n shared.defineForeign(spec.token, selfBackedResolver(spec.declarations ?? []), decls, spec.writable ?? true);\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 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), shared,\n patterSharedDecls, patterLocalDecls, patterSharedNames, sceneSharedNames,\n sharedVisits: new Map(),\n sharedSelectors: new Map(),\n stageBags: new Map(),\n customRng: options.rng,\n onDryChoice: options.onDryChoice,\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 };\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; this one is left untouched and should be discarded. Hosts\n * re-bind their flow handles via `next.getFlow(id)`. If the restore throws (defensive - §9.8\n * makes this unreachable for ordinary edits), the swap falls back to a cold engine with each\n * saved flow restarted from the top of the scene it was in.\n */\n hotSwap(bundle: Bundle): Engine {\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 const next = new Engine(bundle, this.creationOptions);\n try {\n next.loadGame(snapshot);\n return carryOver(next);\n } catch {\n // A partial load may have mutated `next`: fall back on a THIRD, cold engine and restart each\n // flow at the top of the scene it was in (dropped when that scene is gone too).\n const fresh = new Engine(bundle, this.creationOptions);\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 openFlow(id: string, opts: OpenFlowOptions = {}): Flow {\n const sceneId = this.resolveSceneRef(opts.scene);\n const blockId = this.resolveBlockRef(sceneId, opts.block);\n this.flowsById.get(id)?.close(); // finish the flow this name used to mean\n const flow = new Flow(id, this.host, opts.seed ?? this.defaultSeed);\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; // start() reports 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 /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */\n private resolveSceneRef(ref?: string): string | undefined {\n if (ref == null) return undefined;\n if (this.host.bundle.scenes[ref]) return ref; // already an internal id\n return this.sceneGameIdToId.get(ref) ?? ref; // a gameId, else pass through (start reports)\n }\n\n /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */\n private resolveBlockRef(sceneId: string | undefined, ref?: string): string | undefined {\n if (ref == null) return undefined;\n if (this.host.blockById.has(ref)) return ref; // already an internal id\n if (sceneId != null) { const id = this.blockGameIdToId.get(sceneId)?.get(ref); if (id) return id; }\n return ref; // pass through (start reports an unknown block)\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 * 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 ...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 ...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 /** 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.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 for (const flow of this.flowsById.values()) flow.close(); // finish them, don't just forget them\n this.flowsById.clear();\n this.host.shared.reseedOwned(\"patter\", this.host.patterSharedDecls);\n this.host.sharedVisits.clear();\n this.host.sharedSelectors.clear();\n this.host.stageBags.clear();\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.shared.get(scope, name);\n }\n\n /** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */\n setProperty(ref: string, value: ScalarValue): void {\n const { scope, name } = this.splitShared(ref);\n this.host.shared.set(scope, name, value);\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 listProperties(): PropertyRow[] {\n return this.host.patterSharedDecls.map((d) => ({\n ref: `@${d.name}`,\n type: d.type as PropertyType,\n values: d.values,\n value: this.getProperty(`@${d.name}`),\n default: declDefault(d),\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 let split = this.host.refSplitCache.get(ref);\n if (!split) { split = splitRef(ref, (t) => t === \"scene\" || this.host.shared.has(t)); this.host.refSplitCache.set(ref, split); }\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 /** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */\n save(): EngineSave {\n return this.host.shared.save();\n }\n\n /** Restore shared `@patter` values (world properties untouched). */\n load(blob: EngineSave): void {\n this.host.shared.load(blob);\n }\n\n /** Snapshot the whole game: shared `@patter` + visit counts + every live flow. */\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: 2,\n shared: this.host.shared.save(),\n sharedVisits: Object.fromEntries(this.host.sharedVisits),\n sharedSelectors: serialiseSelectors(this.host.sharedSelectors),\n stageBags: Object.fromEntries([...this.host.stageBags].map(([s, bag]) => [s, { ...bag }])),\n flows,\n };\n }\n\n /** Restore a `saveGame()`: shared globals + visit counts + shared scene bags + reconstruct every flow. */\n loadGame(save: SaveGame): void {\n if (save.version !== 2) throw new Error(`unsupported save version: ${save.version}`);\n this.host.shared.load(save.shared);\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 this.host.stageBags.clear();\n for (const [s, bag] of Object.entries(save.stageBags ?? {})) this.host.stageBags.set(s, { ...bag });\n this.flowsById.clear();\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: ScopeRegistry; // owns \"patter\" = the NOT-shared globals (this flow's copy)\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 prompt beat to deliver before its content. */\n private pendingPromptBeat: LineBeat | TextBeat | null = null;\n /** The chosen option that owns `pendingPromptBeat`, so a save taken between choose() and the next\n * advance() can re-derive the prompt on load (the beat 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` (owned scope \"patter\"); the NOT-shared `@scene` props live in\n // `sceneBags` (namespaced per scene; they PERSIST across re-entries, spec §7).\n // The SHARED halves live on the host (`host.shared` / `host.stageBags`). Each\n // resolver presents one merged scope, routing each property to its half by the\n // declared `shared` flag.\n private sceneBags = new Map<string, Record<string, ScalarValue>>();\n\n private readonly patterResolver: ScopeResolver = {\n get: (n) => (this.host.patterSharedNames.has(n) ? this.host.shared.get(\"patter\", n) : this.local.get(\"patter\", n)),\n set: (n, v) => {\n if (this.host.patterSharedNames.has(n)) this.host.shared.set(\"patter\", n, v);\n else this.local.set(\"patter\", 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 const bag = this.host.sceneSharedNames.get(s)?.has(n) ? this.host.stageBags.get(s) : this.sceneBags.get(s);\n return bag?.[n];\n },\n set: (n, v) => {\n const s = this.currentSceneId;\n if (s === null) return;\n const bag = this.host.sceneSharedNames.get(s)?.has(n) ? this.host.stageBags.get(s) : this.sceneBags.get(s);\n if (bag) bag[n] = v;\n },\n };\n\n // The eval context is built ONCE: every constituent resolves live state at\n // call time (shared bags mutate in place per scoperegistry's contract;\n // patter/scene route through this flow's resolvers, which read the current\n // `local`/`sceneBags`/`currentSceneId`; the host callbacks read current flow\n // fields). Rebuilding it per evaluation was the engine's hottest allocation.\n private readonly evalCtx: EvalContext;\n\n constructor(id: string, host: FlowHost, seed: number) {\n this.id = id;\n this.host = host;\n this.rngState = seed >>> 0;\n this.local = this.freshLocal();\n\n const scopes = { ...host.shared.toEvalContext().scopes }; // shared @patter bag + foreign resolvers\n scopes[\"patter\"] = this.patterResolver; // override with the merged shared+per-flow view\n scopes[\"scene\"] = this.sceneResolver;\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 };\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 this.sceneBags.clear();\n this.local = this.freshLocal();\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 if (scene === \"END\") {\n this.started = true; this.pendingChoice = null; this.pendingPromptBeat = 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 = this.host.sceneGameIdToId.get(scene) ?? (this.host.bundle.scenes[scene] ? scene : undefined);\n if (sceneId === undefined) return false;\n let blockId: string | undefined;\n if (block !== undefined) {\n blockId = this.host.blockGameIdToId.get(sceneId)?.get(block)\n ?? (this.host.blockIndex.get(block)?.sceneId === sceneId ? block : undefined);\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.pendingPromptBeat = 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.closed = true;\n this.flowEnded = true;\n this.stack = [];\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.pendingChoice = null;\n this.pendingPromptBeat = null;\n this.pendingPromptOwnerId = null;\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 // A replayed prompt (replayPromptOnChoose) is delivered first, before the option's content.\n if (this.pendingPromptBeat) { const b = this.pendingPromptBeat; this.pendingPromptBeat = null; this.pendingPromptOwnerId = null; return this.beatResult(b); }\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);\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 while (frame.index < children.length && !this.eligible(children[frame.index]!)) frame.index++;\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 /** 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 const node = choice.byId.get(id)!;\n this.pendingChoice = null;\n // Optionally speak the chosen option's prompt back as its first beat (spec §5).\n this.pendingPromptBeat = this.host.replayPromptOnChoose ? this.promptBeatOf(node) ?? null : null;\n this.pendingPromptOwnerId = this.pendingPromptBeat ? 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.shared.get(scope, name); // foreign\n }\n\n /** Write a property by ref (routed by scope, then by the property's `shared` flag). */\n setProperty(ref: string, value: ScalarValue): void {\n const { scope, name } = this.splitRef(ref);\n if (scope === \"patter\") {\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 this.sceneResolver.set!(name, value);\n } else {\n this.host.shared.set(scope, name, value); // foreign\n }\n }\n\n // -- Save / restore (engine-driven) --------------------------------------\n\n /** @internal Snapshot this flow's cursor + per-flow scopes (not-shared `@patter`/`@scene`) + PRNG. */\n snapshot(): FlowSnapshot {\n return {\n scopes: this.local.save(), // owned scope \"patter\" = the NOT-shared globals (@scene saved separately)\n sceneBags: Object.fromEntries([...this.sceneBags].map(([s, bag]) => [s, { ...bag }])),\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 selectors: serialiseSelectors(this.selectors),\n },\n };\n }\n\n /** @internal Restore this flow from a snapshot. */\n restore(snap: FlowSnapshot): void {\n this.rngState = snap.rngState >>> 0;\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 // Restore the per-flow @scene bags, then the per-flow @patter globals. @scene\n // resolves through `sceneResolver` over these bags, so nothing else to reseed.\n this.sceneBags = new Map(Object.entries(snap.sceneBags ?? {}).map(([s, bag]) => [s, { ...bag }]));\n this.local = this.freshLocal();\n this.local.load(snap.scopes); // loads the owned not-shared globals; shared halves live on the host\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). Re-derive it from the chosen option - dropped if that option drifted out\n // of the bundle (§9.8), exactly as the live choose() would have produced nothing.\n this.pendingPromptBeat = null;\n this.pendingPromptOwnerId = c.pendingPromptOwnerId ?? null;\n if (this.pendingPromptOwnerId) {\n const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);\n this.pendingPromptBeat = owner ? this.promptBeatOf(owner) ?? null : null;\n if (!this.pendingPromptBeat) 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); // 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);\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 if (options.length > 0) { this.pendingChoice = { groupId: group.id, options, byId }; return; }\n // No normal option survives. 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.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 (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 const eligible = group.children.filter((c) => this.eligible(c));\n if (eligible.length === 0) return null;\n const st = this.selectorState(group);\n\n switch (group.selector) {\n case \"branch\":\n return eligible[0]!;\n\n case \"sequence\": {\n const order = group.options?.order ?? \"sequential\";\n const exhaust = group.options?.exhaust ?? \"once\";\n return order === \"shuffle\" ? this.pickShuffle(eligible, exhaust, st)\n : order === \"specificity\" ? this.pickSpecificity(eligible, exhaust, st)\n : this.pickSequential(eligible, exhaust, st);\n }\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\n if (st.bag === undefined) st.bag = fill();\n if (st.bag.length === 0) { // a full pass just completed\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 }\n\n // Draw without replacement, never repeating the immediately-previous pick. Done allocation-free:\n // rather than materialise a filtered pool, find last's slot `p` and draw into the reduced span,\n // skipping that slot - identical distribution to filtering it out, then erase the pick in place.\n const pool = st.bag;\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 pool.splice(i, 1);\n st.last = id;\n return eligible.find((c) => c.id === id)!;\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) : 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): 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.\n const evalTruthy: EvalTruthy = (n) => truthy(evaluate(n, this.evalCtx, patterDialect));\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 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 private runEffects(effects: CompiledEffect[] | undefined): 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 this.setProperty(e.target, this.evalExpr(e.value));\n }\n }\n\n private eligible(node: SelectableNode): boolean {\n if (!node.condition) return true;\n return truthy(this.evalExpr(node.condition));\n }\n\n private evalExpr(expr: Expression): ScalarValue {\n return evaluate(this.conditionAst(expr), this.evalCtx, 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 this.visitCounts.set(id, (this.visitCounts.get(id) ?? 0) + 1);\n this.host.sharedVisits.set(id, (this.host.sharedVisits.get(id) ?? 0) + 1);\n }\n\n /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32. */\n private readonly rng = (): number => {\n if (this.host.customRng) return this.host.customRng();\n const a = (this.rngState + 0x6d2b79f5) | 0;\n this.rngState = a;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\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 /** 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`, foreign tokens, else `@patter` (incl. bare `@name`). */\n private splitRef(ref: string): { scope: string; name: string } {\n // host.shared.has(\"patter\") is true, so it covers @patter + every foreign token; @scene is explicit.\n let hit = this.host.refSplitCache.get(ref);\n if (!hit) { hit = splitRef(ref, (t) => t === \"scene\" || this.host.shared.has(t)); this.host.refSplitCache.set(ref, hit); }\n return hit;\n }\n\n /** The per-flow registry: the NOT-shared `@patter` globals (the shared ones live on the host). */\n private freshLocal(): ScopeRegistry {\n return new ScopeRegistry().defineOwned(\"patter\", this.host.patterLocalDecls);\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 if (!this.sceneBags.has(scene.id)) {\n const bag: Record<string, ScalarValue> = {};\n for (const decl of scene.sceneProps ?? []) {\n const name = decl.name.toLowerCase();\n if (!shared.has(name)) bag[name] = sceneDefault(decl);\n }\n this.sceneBags.set(scene.id, bag);\n }\n if (!this.host.stageBags.has(scene.id)) {\n const bag: Record<string, ScalarValue> = {};\n for (const decl of scene.sceneProps ?? []) {\n const name = decl.name.toLowerCase();\n if (shared.has(name)) bag[name] = sceneDefault(decl);\n }\n this.host.stageBags.set(scene.id, bag);\n }\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) bag[name] = sceneDefault(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/** Serialise a `sequence` selector-cursor map to plain snapshots. */\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). */\nfunction toDecl(decl: PropertyDecl): ScopeDeclaration {\n return { name: decl.name, type: decl.type, values: decl.values, default: decl.default };\n}\n\n/** A shared-decl's value for reset-to-default: its declared default, else the type default. */\nfunction declDefault(d: ScopeDeclaration): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"flags\": return [];\n case \"enum\": return d.values?.[0] ?? \"\";\n default: return false; // boolean (and any unknown) → false\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, default: decl.default, writable: decl.writable };\n}\n\n/** The seed value for a host-scope property: its declared default, else the type default. */\nfunction hostScopeDefault(decl: HostScopeDecl): ScalarValue {\n if (decl.default !== undefined) return decl.default;\n switch (decl.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"flags\": return [];\n case \"enum\": return decl.values?.[0] ?? \"\";\n }\n}\n\n/** Build a live in-memory `{ get, set }` resolver for a self-backed host scope (the standalone `@world`):\n * a plain bag seeded from declaration defaults. Declared-but-unseeded names still read `undefined`; an\n * opaque scope (no declarations) starts empty and accepts any name. Per-property read-only is enforced at\n * validation, not here (the registry is per-scope), so `set` accepts any name. */\nfunction selfBackedResolver(decls: HostScopeDecl[]): ScopeResolver {\n // Keyed LOWERCASE. The compiler lowercases every property reference, so an AST reads `isnight` where\n // the declaration says `isNight`; seeding the bag verbatim meant any declared name carrying a capital\n // was never found, read as undefined, and silently took the falsy branch. `@patter` and `@scene`\n // already normalise (patterSharedNames / sceneSharedNames); this resolver was the one that did not.\n const key = (name: string): string => name.toLowerCase();\n const bag = new Map<string, ScalarValue>();\n for (const d of decls) bag.set(key(d.name), hostScopeDefault(d));\n return {\n get: (name) => bag.get(key(name)),\n set: (name, value) => { bag.set(key(name), value); },\n };\n}\n\n/** The seed value for a scene-local property (its `default`, else the type default). */\nfunction sceneDefault(decl: PropertyDecl): ScalarValue {\n if (decl.default !== undefined) return decl.default;\n switch (decl.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"flags\": return [];\n case \"enum\": return decl.values?.[0] ?? \"\";\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"],"mappings":"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","names":["src_exports","__export","Engine","Flow","buildTagIndex","describeBundle","effectiveGameData","gameDataFields","gameDataValue","deserialiseAst","node","args","EvalError","message","evaluate","node","ctx","dialect","missingPolicy","s","rec","n","scope","val","def","l","r","left","right","valueEquals","assertNumbers","a","b","i","op","CHECK_FLAGS_COUNTING_CALL","node","DEFAULT_COUNTING_CALLS","matchedSpecificity","evalTruthy","opts","countingCalls","walk","want","l","r","rule","c","operands","holds","PropertyBag","_PropertyBag","declarations","opts","n","d","name","defaultFor","value","change","audit","fn","rowFor","c","k","values","v","writable","SAVE_FRAGMENT_VERSION","ScopeRegistry","token","bag","e","resolver","scopeWritable","decls","scope","out","row","host","scopes","properties","m","blob","vals","fragment","host","h","patterDialect","args","EvalError","next","a","b","lo","hi","flagsCall","flags","readFlags","i","arg","result","idx","idArg","nodeId","splitRef","ref","isScope","parts","nodeId","args","h","fn","v","EvalError","idArg","fnName","first","readFlags","arg","flagsCall","meta","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","dedupe","tags","seen","out","t","buildTagIndex","bundle","index","visit","node","inherited","acc","child","beat","scene","sceneAcc","block","blockAcc","astCache","Engine","_Engine","bundle","options","locale","allStrings","strings","defaultStrings","loc","emitIds","castDisplay","c","nodeIndex","blockIndex","blockById","sceneId","scene","effectiveGameId","blockAddrs","block","walkNodes","n","props","patterSharedDecls","p","toDecl","patterLocalDecls","patterSharedNames","shared","ScopeRegistry","hostBound","worldSpec","s","decls","toForeignDecl","spec","selfBackedResolver","sceneSharedNames","names","buildTagIndex","DEFAULT_CAPTION_DELIMITERS","DEFAULT_CAPTION_CHARACTER","snapshot","carryOver","next","fresh","id","f","on","opts","blockId","flow","Flow","existing","ref","beatId","sceneRef","blockRef","out","collectCast","beat","b","tags","info","name","castStringKey","source","scope","value","d","declDefault","split","splitRef","t","blob","flows","serialiseSelectors","bag","save","st","deserialiseSelectors","snap","v","host","seed","scopes","first","played","r","transitions","jump","frame","children","choice","option","o","node","nextId","at","ch","byId","owner","selector","pick","containerId","snippet","group","fallbacks","child","eligible","hidden","fallback","to","mode","order","exhaust","len","stick","fill","last","pool","i","remaining","best","tier","x","want","matchedSpecificity","truthy","evaluate","patterDialect","map","effects","e","expr","ast","deserialiseAst","a","withTags","raw","off","text","silent","interpolate","stripCaptions","found","active","character","key","hit","decl","sceneDefault","nodes","rec","hostScopeDefault","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../../../../expr/packages/expr/src/ast.ts","../../../../expr/packages/expr/src/evaluate.ts","../../../node_modules/@wildwinter/expr-specificity/src/index.ts","../../../../expr/packages/scoperegistry/src/index.ts","../../dialect/src/index.ts","../../model/src/index.ts","../src/tags.ts","../src/engine.ts","../src/describe.ts","../src/gamedata.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @patterkit/runtime - public surface.\n//\n// The reference runtime. Construct an `Engine` from a compiled Bundle (the world\n// + flow manager: shared scope state, foreign scopes, whole-game save/load), then\n// `engine.openFlow(id, { scene })` to get a `Flow` and play it (advance / choices\n// / properties). Many flows run concurrently, sharing the shared `@patter`/`@scene`\n// state, each with its own per-flow half + cursor + PRNG.\n// ---------------------------------------------------------------------------\n\nexport { Engine, Flow } from \"./engine.js\";\n// The compiled-bundle type the Engine constructor consumes (from the shared model), so hosts can\n// type a parsed .patterc without depending on @patterkit/model directly.\nexport type { Bundle } from \"@patterkit/model\";\nexport type {\n StepResult, AdvanceToStopResult, ChoiceOption, EngineOptions, OpenFlowOptions, WorldResolver, PropertyRow,\n EngineSave, SaveGame, FlowSnapshot, SelectorSnapshot, SavedChoice, StackFrame,\n BeatInfo, OutlineNode, OutlineBlock, OutlineScene, FlatBeat,\n} from \"./engine.js\";\n\n// The bundle inspector's runtime half: what a game may call, read off the asset with no Engine.\nexport { describeBundle } from \"./describe.js\";\nexport type {\n BundleDescription, BundleIdentity, AddressSummary, HostScopeSummary,\n PropertySummary, OwnedProperties, GameDataSummary, GameDataFieldSummary, BundleCounts,\n} from \"./describe.js\";\n\n// gameData read helpers (sparse overrides + field-default merge).\nexport { gameDataFields, gameDataValue, effectiveGameData } from \"./gamedata.js\";\n\n// Author tags (#215): accumulated node-tag index (also surfaced via Engine.tagsFor* + step.tags).\nexport { buildTagIndex } from \"./tags.js\";\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\";\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 = 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 : (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\" && !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 = 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 element-wise by value, in order. Plain\n * `===` on arrays would be reference equality - two distinct arrays with the\n * same contents would never be equal, and a fresh array (e.g. from a scope\n * read or a function result) would never equal another. Mixed array/non-array\n * operands are unequal. Matches the value-equality the Unreal and Unity\n * runtimes implement for flags.\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 for (let i = 0; i < a.length; i++) if (a[i] !== b[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// @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// @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; default is read/write. (`type`/`values` feed validation.)\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}\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 type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: 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 readonly values: 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 constructor(declarations: ScopeDeclaration[] = [], opts?: { normalise?: (name: string) => string }) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\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 /** 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. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string }): BagChange {\n const n = this.norm(name);\n if (this.decls.get(n)?.writable === false) 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 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));\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 });\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(d: ScopeDeclaration, value: ScalarValue | undefined, writable?: boolean, name?: string): PropertyRow {\n return {\n name: name ?? d.name.toLowerCase(),\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\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}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n}\ntype Entry = OwnedScope | ForeignScope;\n\n/** The versioned owned-state fragment both product save envelopes embed\n * (design/engine-runtimes.md 3.1: one serialisation shape for bags). */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\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 defineOwned(token: string, declarations: ScopeDeclaration[]): this {\n return this.mountOwned(token, new PropertyBag(declarations));\n }\n\n /**\n * Attach an EXISTING bag as an owned scope - the shared-container move: a\n * host (or the other product) holds the bag; this registry reads, writes\n * and lists it like its own, but the holder saves it.\n */\n mountOwned(token: string, bag: PropertyBag): this {\n this.assertFree(token);\n this.scopes.set(token, { kind: \"owned\", bag });\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 scopeWritable = true,\n ): this {\n this.assertFree(token);\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(d.name.toLowerCase(), d);\n this.scopes.set(token, { kind: \"foreign\", resolver, decls, scopeWritable });\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(name.toLowerCase());\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 or\n * read-only scope/property. */\n set(scope: string, name: string, value: ScalarValue): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n try {\n e.bag.set(name, value);\n } catch {\n throw new Error(`'@${scope}.${name}' is read-only`);\n }\n return;\n }\n const n = name.toLowerCase();\n if (!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 } & PropertyRow)[] {\n const out: ({ scope: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...row });\n } else {\n for (const d of e.decls.values()) {\n out.push({\n scope: token,\n ...rowFor(d, e.resolver.get(d.name.toLowerCase()), this.foreignWritable(e, d.name.toLowerCase())),\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>): EvalContext {\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of this.scopes) {\n scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n }\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();\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => qualities.get(scope)?.get(name.toLowerCase()),\n };\n }\n\n /** Every quality declaration's ladder, keyed scope token then name. */\n private qualityLadders(): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n for (const d of decls) {\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(d.name.toLowerCase(), 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.\n */\n toSchema(): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.scopes) {\n const decls = e.kind === \"owned\" ? e.bag.declarations() : [...e.decls.values()];\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const d of decls) m.set(d.name.toLowerCase(), {\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 only (foreign scopes are host-owned,\n * host-saved), as bare bags - the 0.1.x shape, kept stable so existing\n * consumers' save formats are untouched. A product embedding the\n * versioned cross-product shape uses `saveFragment`. */\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 return out;\n }\n\n /** Restore owned-scope values from a `save` blob. Unknown/foreign scopes\n * are ignored. */\n load(blob: Record<string, Record<string, ScalarValue>>): void {\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 }\n }\n\n /** The versioned owned-state fragment (the one serialisation shape both\n * product families' save envelopes embed when they adopt the kernel;\n * design/engine-runtimes.md 3.1). `save()` wrapped with a version stamp. */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /** Restore from a versioned fragment; an unsupported version throws. */\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 private assertFree(token: string): void {\n if (this.scopes.has(token)) throw new Error(`scope '@${token}' is already registered`);\n }\n}\n\nfunction defaultFor(d: ScopeDeclaration): 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 }\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 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 * 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(spec?: ScopeRegistrySpec): Dialect {\n if (!spec || 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. */\nexport const RESERVED_PROPERTY_NAMES: readonly string[] = [\"true\", \"false\", \"and\", \"or\", \"not\"];\n\n/** Coerce a label into a legal property name: lower case, apostrophes dropped, runs of\n * anything else to a single underscore, no trailing underscore, an underscore in front\n * of a leading digit, one behind a keyword. \"\" when nothing usable was left. */\nexport function propertyNameify(text: string): string {\n const trimmed = text.trim();\n // An underscore the author typed is kept; one that is only the ghost of leading\n // punctuation is not. That is the difference between `_private` and `!gold`.\n const deliberateLeading = trimmed.startsWith(\"_\");\n let out = trimmed.toLowerCase().replace(/['\\u2019]/g, \"\")\n .replace(/[^a-z0-9_]+/g, \"_\").replace(/_+/g, \"_\").replace(/^_+|_+$/g, \"\");\n if (out === \"\") return \"\";\n if (deliberateLeading || /^[0-9]/.test(out)) out = `_${out}`;\n if (RESERVED_PROPERTY_NAMES.includes(out)) out = `${out}_`;\n return out;\n}\n\n/** Is this a name an expression can actually reach? Lower case letters, digits and\n * underscores, not starting with a digit, not a keyword. \"\" is not a name. */\nexport function isValidPropertyName(name: string): boolean {\n return /^[a-z_][a-z0-9_]*$/.test(name) && !RESERVED_PROPERTY_NAMES.includes(name);\n}\n\n/** True when folding case ALONE would make it legal (`isNight`). The only violation a\n * loader may repair without guessing at intent: every reference is folded already, so\n * folding the declaration to match changes nothing observable. */\nexport function isCaseOnlyPropertyName(name: string): boolean {\n return !isValidPropertyName(name) && isValidPropertyName(name.toLowerCase());\n}\n\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); studios add their\n * own (e.g. `sfx`, `art`), 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];\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 /** 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 (review flow, design/proposals/\n * suggest-rewrite.md). Whole-beat anchored: `baseline` is the say text WHEN suggested (the \"before\" +\n * the drift detector - if it no longer matches the live text, the line changed since and the suggestion\n * is shown stale), `proposed` is the replacement. Accept overwrites the beat's say text; both accept and\n * reject set `resolved` (archived) + `outcome` (a light audit trail). Stored in the authoring shard,\n * never in the flow text, so downstream tools ignore it. */\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 author: string;\n ts: string;\n /** Accepted or rejected -> archived (hidden unless \"show resolved suggestions\" is on). */\n resolved?: boolean;\n outcome?: \"accepted\" | \"rejected\";\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// 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 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// 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) 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 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// `engine.saveGame()` / `loadGame()` snapshot + restore the WHOLE game: `@patter`\n// plus every live flow's scopes + PRNG + cursor.\n// ---------------------------------------------------------------------------\n\nimport { evaluate, deserialiseAst } 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 type { 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 type {\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/** A property-state snapshot: owned scope -> property name -> value. */\nexport type EngineSave = Record<string, Record<string, ScalarValue>>;\n\n/** Serialised `sequence` selector visit state for one group (spec §4 / §7). */\nexport interface SelectorSnapshot {\n seq?: number; // sequential cursor (visits taken)\n bag?: string[]; // shuffle: child ids still undrawn this pass\n last?: string; // last child id picked (no-immediate-repeat)\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 set on a live frame): the id of the child at `index` when the save was\n * taken. On restore the child is re-found by this id, so a save survives siblings being inserted,\n * removed, or reordered before the cursor (live bundle refresh / patched-game saves). Absent (an\n * older save, or a frame saved at its container's end) falls back to the raw `index`. */\n nextId?: string;\n}\n\n/** The serialised cursor + scopes + PRNG of a single flow. */\nexport interface FlowSnapshot {\n /** This flow's owned-scope values = the NOT-shared `@patter` globals (under token \"patter\"). */\n scopes: EngineSave;\n /** Per-scene NOT-shared `@scene` bags (scene id -> name -> value); persist across re-entries (spec §7). */\n sceneBags: Record<string, Record<string, ScalarValue>>;\n /** This flow's built-in PRNG position (mulberry32 state). */\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: {\n flowEnded: boolean;\n currentSceneId: string | null;\n /** The continuation stack (call frames + the active block run). */\n stack: StackFrame[];\n activeSnippetId: string | null;\n beatIndex: number;\n /** The pending choice's exact option set, REPLAYED on load (schema 9.3). */\n pendingChoice: SavedChoice | null;\n /** The chosen option owning a prompt still to be replayed (save taken between choose + advance).\n * Optional / absent in older saves -> no pending prompt. */\n pendingPromptOwnerId?: string | null;\n /** This flow's `sequence` selector cursors. */\n selectors: Record<string, SelectorSnapshot>;\n };\n}\n\n/**\n * A pending choice as saved: the option set the player was shown, restored\n * verbatim - re-deriving on load would re-evaluate conditions (consuming PRNG\n * draws a second time) and could mutate the choice under the player.\n */\nexport interface SavedChoice {\n groupId: string;\n options: ChoiceOption[];\n}\n\n/** A full resumable save-game: shared `@patter` state + every live flow. */\nexport interface SaveGame {\n version: number;\n /** Shared `@patter` globals (owned scope \"patter\"). */\n shared: 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 /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) - the shared scene props. */\n stageBags: Record<string, Record<string, ScalarValue>>;\n /** Each live flow's snapshot, keyed by flow id. */\n flows: Record<string, FlowSnapshot>;\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 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 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\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. Shared by all flows. */\n world?: WorldResolver;\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}\n\n/** One shared `@patter` property, for a live state inspector: its ref, declared type, current value,\n * declared default (for reset), and enum options. Mirrors the Unity / Godot ports' ListProperties. */\nexport interface PropertyRow {\n ref: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder (lets an inspector offer stages instead of free text). */\n stages?: string[];\n}\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/** Shared, read-mostly context the engine hands to every flow it owns. */\ninterface FlowHost {\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 SHARED `@patter` globals (owned scope \"patter\") + world properties (`@world`). */\n shared: ScopeRegistry;\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 /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */\n stageBags: Map<string, Record<string, ScalarValue>>;\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 /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on `shared`'s scope\n * set, which is fixed for the engine's life, so every effect target / `{@ref}` slot parses once. */\n refSplitCache: Map<string, { scope: string; name: string }>;\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\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 const shared = new ScopeRegistry().defineOwned(\"patter\", patterSharedDecls);\n const hostBound = new Set<string>();\n // The host's World Properties resolver binds `@world`; its declarations (types, read-only) come from\n // the compiled bundle's declared world properties. An explicit binding always wins over the self-backed\n // fallback below.\n if (options.world) {\n const worldSpec = bundle.scopeRegistry?.scopes.find((s) => s.token === \"world\");\n const decls = (worldSpec?.declarations ?? []).map(toForeignDecl);\n shared.defineForeign(\"world\", options.world, decls, worldSpec?.writable ?? true);\n hostBound.add(\"world\");\n }\n // A project that DECLARES `@world` but whose embedder binds no resolver (the standalone case) gets a\n // self-backed one: a live in-memory bag seeded from the declarations' defaults. The story reads/writes\n // it like any scope; it stays *foreign* (not in Patter's save: the host owns it conceptually).\n for (const spec of bundle.scopeRegistry?.scopes ?? []) {\n if (hostBound.has(spec.token)) continue;\n const decls = (spec.declarations ?? []).map(toForeignDecl);\n shared.defineForeign(spec.token, selfBackedResolver(spec.declarations ?? []), decls, spec.writable ?? true);\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 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), shared,\n patterSharedDecls, patterLocalDecls, patterSharedNames, sceneSharedNames,\n sharedVisits: new Map(),\n sharedSelectors: new Map(),\n stageBags: new Map(),\n customRng: options.rng,\n onDryChoice: options.onDryChoice,\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 };\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; this one is left untouched and should be discarded. Hosts\n * re-bind their flow handles via `next.getFlow(id)`. If the restore throws (defensive - §9.8\n * makes this unreachable for ordinary edits), the swap falls back to a cold engine with each\n * saved flow restarted from the top of the scene it was in.\n */\n hotSwap(bundle: Bundle): Engine {\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 const next = new Engine(bundle, this.creationOptions);\n try {\n next.loadGame(snapshot);\n return carryOver(next);\n } catch {\n // A partial load may have mutated `next`: fall back on a THIRD, cold engine and restart each\n // flow at the top of the scene it was in (dropped when that scene is gone too).\n const fresh = new Engine(bundle, this.creationOptions);\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 openFlow(id: string, opts: OpenFlowOptions = {}): Flow {\n const sceneId = this.resolveSceneRef(opts.scene);\n const blockId = this.resolveBlockRef(sceneId, opts.block);\n this.flowsById.get(id)?.close(); // finish the flow this name used to mean\n const flow = new Flow(id, this.host, opts.seed ?? this.defaultSeed);\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; // start() reports 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 /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */\n private resolveSceneRef(ref?: string): string | undefined {\n if (ref == null) return undefined;\n if (this.host.bundle.scenes[ref]) return ref; // already an internal id\n return this.sceneGameIdToId.get(ref) ?? ref; // a gameId, else pass through (start reports)\n }\n\n /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */\n private resolveBlockRef(sceneId: string | undefined, ref?: string): string | undefined {\n if (ref == null) return undefined;\n if (this.host.blockById.has(ref)) return ref; // already an internal id\n if (sceneId != null) { const id = this.blockGameIdToId.get(sceneId)?.get(ref); if (id) return id; }\n return ref; // pass through (start reports an unknown block)\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 * 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 ...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 ...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 /** 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.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 for (const flow of this.flowsById.values()) flow.close(); // finish them, don't just forget them\n this.flowsById.clear();\n this.host.shared.reseedOwned(\"patter\", this.host.patterSharedDecls);\n this.host.sharedVisits.clear();\n this.host.sharedSelectors.clear();\n this.host.stageBags.clear();\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.shared.get(scope, name);\n }\n\n /** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */\n setProperty(ref: string, value: ScalarValue): void {\n const { scope, name } = this.splitShared(ref);\n this.host.shared.set(scope, name, value);\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 listProperties(): PropertyRow[] {\n return this.host.patterSharedDecls.map((d) => ({\n ref: `@${d.name}`,\n type: d.type as PropertyType,\n values: d.values,\n stages: d.stages,\n value: this.getProperty(`@${d.name}`),\n default: declDefault(d),\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 let split = this.host.refSplitCache.get(ref);\n if (!split) { split = splitRef(ref, (t) => t === \"scene\" || this.host.shared.has(t)); this.host.refSplitCache.set(ref, split); }\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 /** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */\n save(): EngineSave {\n return this.host.shared.save();\n }\n\n /** Restore shared `@patter` values (world properties untouched). */\n load(blob: EngineSave): void {\n this.host.shared.load(blob);\n }\n\n /** Snapshot the whole game: shared `@patter` + visit counts + every live flow. */\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: 2,\n shared: this.host.shared.save(),\n sharedVisits: Object.fromEntries(this.host.sharedVisits),\n sharedSelectors: serialiseSelectors(this.host.sharedSelectors),\n stageBags: Object.fromEntries([...this.host.stageBags].map(([s, bag]) => [s, { ...bag }])),\n flows,\n };\n }\n\n /** Restore a `saveGame()`: shared globals + visit counts + shared scene bags + reconstruct every flow. */\n loadGame(save: SaveGame): void {\n if (save.version !== 2) throw new Error(`unsupported save version: ${save.version}`);\n this.host.shared.load(save.shared);\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 this.host.stageBags.clear();\n for (const [s, bag] of Object.entries(save.stageBags ?? {})) this.host.stageBags.set(s, { ...bag });\n this.flowsById.clear();\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: ScopeRegistry; // owns \"patter\" = the NOT-shared globals (this flow's copy)\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 prompt beat to deliver before its content. */\n private pendingPromptBeat: LineBeat | TextBeat | null = null;\n /** The chosen option that owns `pendingPromptBeat`, so a save taken between choose() and the next\n * advance() can re-derive the prompt on load (the beat 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` (owned scope \"patter\"); the NOT-shared `@scene` props live in\n // `sceneBags` (namespaced per scene; they PERSIST across re-entries, spec §7).\n // The SHARED halves live on the host (`host.shared` / `host.stageBags`). Each\n // resolver presents one merged scope, routing each property to its half by the\n // declared `shared` flag.\n private sceneBags = new Map<string, Record<string, ScalarValue>>();\n\n private readonly patterResolver: ScopeResolver = {\n get: (n) => (this.host.patterSharedNames.has(n) ? this.host.shared.get(\"patter\", n) : this.local.get(\"patter\", n)),\n set: (n, v) => {\n if (this.host.patterSharedNames.has(n)) this.host.shared.set(\"patter\", n, v);\n else this.local.set(\"patter\", 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 const bag = this.host.sceneSharedNames.get(s)?.has(n) ? this.host.stageBags.get(s) : this.sceneBags.get(s);\n return bag?.[n];\n },\n set: (n, v) => {\n const s = this.currentSceneId;\n if (s === null) return;\n const bag = this.host.sceneSharedNames.get(s)?.has(n) ? this.host.stageBags.get(s) : this.sceneBags.get(s);\n if (bag) bag[n] = v;\n },\n };\n\n // The eval context is built ONCE: every constituent resolves live state at\n // call time (shared bags mutate in place per scoperegistry's contract;\n // patter/scene route through this flow's resolvers, which read the current\n // `local`/`sceneBags`/`currentSceneId`; the host callbacks read current flow\n // fields). Rebuilding it per evaluation was the engine's hottest allocation.\n private readonly evalCtx: EvalContext;\n\n constructor(id: string, host: FlowHost, seed: number) {\n this.id = id;\n this.host = host;\n this.rngState = seed >>> 0;\n this.local = this.freshLocal();\n\n const scopes = { ...host.shared.toEvalContext().scopes }; // shared @patter bag + foreign resolvers\n scopes[\"patter\"] = this.patterResolver; // override with the merged shared+per-flow view\n scopes[\"scene\"] = this.sceneResolver;\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 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 return 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 this.sceneBags.clear();\n this.local = this.freshLocal();\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 if (scene === \"END\") {\n this.started = true; this.pendingChoice = null; this.pendingPromptBeat = 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 = this.host.sceneGameIdToId.get(scene) ?? (this.host.bundle.scenes[scene] ? scene : undefined);\n if (sceneId === undefined) return false;\n let blockId: string | undefined;\n if (block !== undefined) {\n blockId = this.host.blockGameIdToId.get(sceneId)?.get(block)\n ?? (this.host.blockIndex.get(block)?.sceneId === sceneId ? block : undefined);\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.pendingPromptBeat = 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.closed = true;\n this.flowEnded = true;\n this.stack = [];\n this.activeSnippet = null;\n this.beatIndex = 0;\n this.pendingChoice = null;\n this.pendingPromptBeat = null;\n this.pendingPromptOwnerId = null;\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 // A replayed prompt (replayPromptOnChoose) is delivered first, before the option's content.\n if (this.pendingPromptBeat) { const b = this.pendingPromptBeat; this.pendingPromptBeat = null; this.pendingPromptOwnerId = null; return this.beatResult(b); }\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);\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 while (frame.index < children.length && !this.eligible(children[frame.index]!)) frame.index++;\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 /** 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 const node = choice.byId.get(id)!;\n this.pendingChoice = null;\n // Optionally speak the chosen option's prompt back as its first beat (spec §5).\n this.pendingPromptBeat = this.host.replayPromptOnChoose ? this.promptBeatOf(node) ?? null : null;\n this.pendingPromptOwnerId = this.pendingPromptBeat ? 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.shared.get(scope, name); // foreign\n }\n\n /** Write a property by ref (routed by scope, then by the property's `shared` flag). */\n setProperty(ref: string, value: ScalarValue): void {\n const { scope, name } = this.splitRef(ref);\n if (scope === \"patter\") {\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 this.sceneResolver.set!(name, value);\n } else {\n this.host.shared.set(scope, name, value); // foreign\n }\n }\n\n // -- Save / restore (engine-driven) --------------------------------------\n\n /** @internal Snapshot this flow's cursor + per-flow scopes (not-shared `@patter`/`@scene`) + PRNG. */\n snapshot(): FlowSnapshot {\n return {\n scopes: this.local.save(), // owned scope \"patter\" = the NOT-shared globals (@scene saved separately)\n sceneBags: Object.fromEntries([...this.sceneBags].map(([s, bag]) => [s, { ...bag }])),\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 selectors: serialiseSelectors(this.selectors),\n },\n };\n }\n\n /** @internal Restore this flow from a snapshot. */\n restore(snap: FlowSnapshot): void {\n this.rngState = snap.rngState >>> 0;\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 // Restore the per-flow @scene bags, then the per-flow @patter globals. @scene\n // resolves through `sceneResolver` over these bags, so nothing else to reseed.\n this.sceneBags = new Map(Object.entries(snap.sceneBags ?? {}).map(([s, bag]) => [s, { ...bag }]));\n this.local = this.freshLocal();\n this.local.load(snap.scopes); // loads the owned not-shared globals; shared halves live on the host\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). Re-derive it from the chosen option - dropped if that option drifted out\n // of the bundle (§9.8), exactly as the live choose() would have produced nothing.\n this.pendingPromptBeat = null;\n this.pendingPromptOwnerId = c.pendingPromptOwnerId ?? null;\n if (this.pendingPromptOwnerId) {\n const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);\n this.pendingPromptBeat = owner ? this.promptBeatOf(owner) ?? null : null;\n if (!this.pendingPromptBeat) 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); // 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);\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 if (options.length > 0) { this.pendingChoice = { groupId: group.id, options, byId }; return; }\n // No normal option survives. 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.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 (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 const eligible = group.children.filter((c) => this.eligible(c));\n if (eligible.length === 0) return null;\n const st = this.selectorState(group);\n\n switch (group.selector) {\n case \"branch\":\n return eligible[0]!;\n\n case \"sequence\": {\n const order = group.options?.order ?? \"sequential\";\n const exhaust = group.options?.exhaust ?? \"once\";\n return order === \"shuffle\" ? this.pickShuffle(eligible, exhaust, st)\n : order === \"specificity\" ? this.pickSpecificity(eligible, exhaust, st)\n : this.pickSequential(eligible, exhaust, st);\n }\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\n if (st.bag === undefined) st.bag = fill();\n if (st.bag.length === 0) { // a full pass just completed\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 }\n\n // Draw without replacement, never repeating the immediately-previous pick. Done allocation-free:\n // rather than materialise a filtered pool, find last's slot `p` and draw into the reduced span,\n // skipping that slot - identical distribution to filtering it out, then erase the pick in place.\n const pool = st.bag;\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 pool.splice(i, 1);\n st.last = id;\n return eligible.find((c) => c.id === id)!;\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) : 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): 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.\n const evalTruthy: EvalTruthy = (n) => truthy(evaluate(n, this.evalCtx, patterDialect));\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 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 private runEffects(effects: CompiledEffect[] | undefined): 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 this.setProperty(e.target, this.evalExpr(e.value));\n }\n }\n\n private eligible(node: SelectableNode): boolean {\n if (!node.condition) return true;\n return truthy(this.evalExpr(node.condition));\n }\n\n private evalExpr(expr: Expression): ScalarValue {\n return evaluate(this.conditionAst(expr), this.evalCtx, 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 this.visitCounts.set(id, (this.visitCounts.get(id) ?? 0) + 1);\n this.host.sharedVisits.set(id, (this.host.sharedVisits.get(id) ?? 0) + 1);\n }\n\n /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32. */\n private readonly rng = (): number => {\n if (this.host.customRng) return this.host.customRng();\n const a = (this.rngState + 0x6d2b79f5) | 0;\n this.rngState = a;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\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 /** 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`, foreign tokens, else `@patter` (incl. bare `@name`). */\n private splitRef(ref: string): { scope: string; name: string } {\n // host.shared.has(\"patter\") is true, so it covers @patter + every foreign token; @scene is explicit.\n let hit = this.host.refSplitCache.get(ref);\n if (!hit) { hit = splitRef(ref, (t) => t === \"scene\" || this.host.shared.has(t)); this.host.refSplitCache.set(ref, hit); }\n return hit;\n }\n\n /** The per-flow registry: the NOT-shared `@patter` globals (the shared ones live on the host). */\n private freshLocal(): ScopeRegistry {\n return new ScopeRegistry().defineOwned(\"patter\", this.host.patterLocalDecls);\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 if (!this.sceneBags.has(scene.id)) {\n const bag: Record<string, ScalarValue> = {};\n for (const decl of scene.sceneProps ?? []) {\n const name = decl.name.toLowerCase();\n if (!shared.has(name)) bag[name] = sceneDefault(decl);\n }\n this.sceneBags.set(scene.id, bag);\n }\n if (!this.host.stageBags.has(scene.id)) {\n const bag: Record<string, ScalarValue> = {};\n for (const decl of scene.sceneProps ?? []) {\n const name = decl.name.toLowerCase();\n if (shared.has(name)) bag[name] = sceneDefault(decl);\n }\n this.host.stageBags.set(scene.id, bag);\n }\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) bag[name] = sceneDefault(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/** Serialise a `sequence` selector-cursor map to plain snapshots. */\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). */\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/** A shared-decl's value for reset-to-default: its declared default, else the type default.\n * A quality seeds at its FIRST stage - the ladder's start is the story's start. */\nfunction declDefault(d: ScopeDeclaration): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"flags\": return [];\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"quality\": return d.stages?.[0] ?? \"\";\n default: return false; // boolean (and any unknown) → false\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/** The seed value for a host-scope property: its declared default, else the type default. */\nfunction hostScopeDefault(decl: HostScopeDecl): ScalarValue {\n if (decl.default !== undefined) return decl.default;\n switch (decl.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"flags\": return [];\n case \"enum\": return decl.values?.[0] ?? \"\";\n case \"quality\": return decl.stages?.[0] ?? \"\";\n }\n}\n\n/** Build a live in-memory `{ get, set }` resolver for a self-backed host scope (the standalone `@world`):\n * a plain bag seeded from declaration defaults. Declared-but-unseeded names still read `undefined`; an\n * opaque scope (no declarations) starts empty and accepts any name. Per-property read-only is enforced at\n * validation, not here (the registry is per-scope), so `set` accepts any name. */\nfunction selfBackedResolver(decls: HostScopeDecl[]): ScopeResolver {\n // Keyed LOWERCASE. The compiler lowercases every property reference, so an AST reads `isnight` where\n // the declaration says `isNight`; seeding the bag verbatim meant any declared name carrying a capital\n // was never found, read as undefined, and silently took the falsy branch. `@patter` and `@scene`\n // already normalise (patterSharedNames / sceneSharedNames); this resolver was the one that did not.\n const key = (name: string): string => name.toLowerCase();\n const bag = new Map<string, ScalarValue>();\n for (const d of decls) bag.set(key(d.name), hostScopeDefault(d));\n return {\n get: (name) => bag.get(key(name)),\n set: (name, value) => { bag.set(key(name), value); },\n };\n}\n\n/** The seed value for a scene-local property (its `default`, else the type default). */\nfunction sceneDefault(decl: PropertyDecl): ScalarValue {\n if (decl.default !== undefined) return decl.default;\n switch (decl.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n case \"string\": return \"\";\n case \"flags\": return [];\n case \"enum\": return decl.values?.[0] ?? \"\";\n case \"quality\": return decl.stages?.[0] ?? \"\";\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"],"mappings":"wcAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,YAAAE,EAAA,SAAAC,EAAA,kBAAAC,EAAA,mBAAAC,GAAA,sBAAAC,GAAA,mBAAAC,GAAA,kBAAAC,ICwEO,SAASC,EAAeC,EAAyB,CACtD,OAAQA,EAAK,CAAC,EAAG,CACf,IAAK,IAAO,MAAO,CAAE,KAAM,OAAU,MAAOA,EAAK,CAAC,CAAE,EACpD,IAAK,IAAO,MAAO,CAAE,KAAM,SAAU,MAAOA,EAAK,CAAC,CAAE,EACpD,IAAK,IAAO,MAAO,CAAE,KAAM,SAAU,MAAOA,EAAK,CAAC,CAAE,EACpD,IAAK,KAAO,MAAO,CAAE,KAAM,YAAa,MAAOA,EAAK,CAAC,EAAG,KAAMA,EAAK,CAAC,CAAE,EACtE,IAAK,IAAO,MAAO,CAAE,KAAM,QAAU,GAAIA,EAAK,CAAC,EAAG,QAASD,EAAeC,EAAK,CAAC,CAAC,CAAE,EACnF,IAAK,MAAO,MAAO,CAAE,KAAM,SAAU,GAAIA,EAAK,CAAC,EAAG,KAAMD,EAAeC,EAAK,CAAC,CAAC,EAAG,MAAOD,EAAeC,EAAK,CAAC,CAAC,CAAE,EAChH,IAAK,OAAQ,CACX,IAAMC,EAAQD,EAAK,MAAM,CAAC,EAAgB,IAAID,CAAc,EAC5D,MAAO,CAAE,KAAM,OAAQ,KAAMC,EAAK,CAAC,EAAG,KAAAC,CAAK,CAC7C,CACA,IAAK,KAAO,MAAO,CAAE,KAAM,YAAa,KAAMD,EAAK,CAAC,EAAG,KAAMA,EAAK,CAAC,CAAE,CACvE,CACF,CCxEO,IAAME,EAAN,cAAwB,KAAM,CACnC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,WACd,CACF,EAEO,SAASC,EAASC,EAAgBC,EAAkBC,EAA+B,CAExF,IAAMC,EAAgB,IAAI,IACxBD,EAAQ,OAAO,IAAKE,GAAM,CAACA,EAAE,MAAOA,EAAE,SAAW,OAAO,CAAC,CAC3D,EAEMC,EAAOC,GAA6B,CACxC,OAAQA,EAAE,KAAM,CACd,IAAK,OAAU,OAAOA,EAAE,MACxB,IAAK,SAAU,OAAOA,EAAE,MACxB,IAAK,SAAU,OAAOA,EAAE,MAExB,IAAK,YAAa,CAChB,IAAMC,EAAQN,EAAI,OAAOK,EAAE,KAAK,EAChC,GAAIC,IAAU,OAGZ,MAAO,GAKT,IAAMC,EAAM,OAAQD,EAAwB,KAAQ,WAC/CA,EAAwB,IAAID,EAAE,IAAI,EAClCC,EAAsCD,EAAE,IAAI,EACjD,GAAIE,IAAQ,OAAW,CAIrB,GAAIL,EAAc,IAAIG,EAAE,KAAK,IAAM,QACjC,MAAM,IAAIT,EAAU,IAAIS,EAAE,KAAK,IAAIA,EAAE,IAAI,mCAAmCA,EAAE,KAAK,GAAG,EAExF,MAAO,EACT,CACA,OAAOE,CACT,CAEA,IAAK,OAAQ,CAQX,GAAIF,EAAE,OAAS,WAAa,CAACJ,EAAQ,UAAUI,EAAE,IAAI,EAAG,CACtD,IAAMG,EAAMH,EAAE,KAAK,CAAC,EACpB,GAAIA,EAAE,KAAK,SAAW,GAAKG,IAAQ,OACjC,MAAM,IAAIZ,EAAU,2CAA2CS,EAAE,KAAK,MAAM,EAAE,EAEhF,IAAMI,EAASC,EAASF,EAAKR,CAAG,EAChC,GAAIS,IAAW,OACb,MAAM,IAAIb,EAAU,yEAAyE,EAE/F,IAAMe,EAAUC,EAAWR,EAAII,CAAG,EAAGC,EAAQ,SAAS,EACtD,OAAOA,EAAO,KAAK,IAAIE,EAAU,EAAGF,EAAO,OAAS,CAAC,CAAC,CACxD,CACA,IAAMI,EAAMZ,EAAQ,UAAUI,EAAE,IAAI,EACpC,GAAI,CAACQ,EAAK,MAAM,IAAIjB,EAAU,qBAAqBS,EAAE,IAAI,GAAG,EAC5D,OAAOQ,EAAI,KAAKR,EAAE,KAAM,CAAE,SAAUD,EAAK,IAAAJ,CAAI,CAAC,CAChD,CAEA,IAAK,YACH,MAAM,IAAIJ,EAAU,sEAAsE,EAE5F,IAAK,QAAS,CACZ,GAAIS,EAAE,KAAO,MAAO,CAClB,IAAME,EAAMH,EAAIC,EAAE,OAAO,EACzB,GAAI,OAAOE,GAAQ,UAAW,MAAM,IAAIX,EAAU,yCAAyC,OAAOW,CAAG,EAAE,EACvG,MAAO,CAACA,CACV,CAEA,IAAMA,EAAMH,EAAIC,EAAE,OAAO,EACzB,GAAI,OAAOE,GAAQ,SAAU,MAAM,IAAIX,EAAU,6CAA6C,OAAOW,CAAG,EAAE,EAC1G,MAAO,CAACA,CACV,CAEA,IAAK,SAAU,CAEb,GAAIF,EAAE,KAAO,MAAO,CAClB,IAAMS,EAAIV,EAAIC,EAAE,IAAI,EACpB,GAAI,OAAOS,GAAM,UAAW,MAAM,IAAIlB,EAAU,4CAA4C,OAAOkB,CAAC,EAAE,EACtG,GAAI,CAACA,EAAG,MAAO,GACf,IAAMC,EAAIX,EAAIC,EAAE,KAAK,EACrB,GAAI,OAAOU,GAAM,UAAW,MAAM,IAAInB,EAAU,6CAA6C,OAAOmB,CAAC,EAAE,EACvG,OAAOA,CACT,CACA,GAAIV,EAAE,KAAO,KAAM,CACjB,IAAMS,EAAIV,EAAIC,EAAE,IAAI,EACpB,GAAI,OAAOS,GAAM,UAAW,MAAM,IAAIlB,EAAU,2CAA2C,OAAOkB,CAAC,EAAE,EACrG,GAAIA,EAAG,MAAO,GACd,IAAMC,EAAIX,EAAIC,EAAE,KAAK,EACrB,GAAI,OAAOU,GAAM,UAAW,MAAM,IAAInB,EAAU,4CAA4C,OAAOmB,CAAC,EAAE,EACtG,OAAOA,CACT,CAEA,IAAMC,EAAQZ,EAAIC,EAAE,IAAI,EAClBY,EAAQb,EAAIC,EAAE,KAAK,EAMnBa,EAAUR,EAASL,EAAE,KAAML,CAAG,EAC9BmB,EAAUT,EAASL,EAAE,MAAOL,CAAG,EAC/BS,EAASS,GAAWC,EAC1B,GAAIV,IAAW,OAAW,CACxB,GAAIS,GAAWC,GAAW,CAACC,GAAWF,EAASC,CAAO,IAChDd,EAAE,KAAO,KAAOA,EAAE,KAAO,MAAQA,EAAE,KAAO,KAAOA,EAAE,KAAO,MAC5D,MAAM,IAAIT,EAAU,IAAIS,EAAE,EAAE,sEAAsE,EAGtG,OAAQA,EAAE,GAAI,CACZ,IAAK,IAAM,OAAOO,EAAWI,EAAMP,EAAQ,GAAG,EAAMG,EAAWK,EAAOR,EAAQ,GAAG,EACjF,IAAK,KAAM,OAAOG,EAAWI,EAAMP,EAAQ,IAAI,GAAKG,EAAWK,EAAOR,EAAQ,IAAI,EAClF,IAAK,IAAM,OAAOG,EAAWI,EAAMP,EAAQ,GAAG,EAAMG,EAAWK,EAAOR,EAAQ,GAAG,EACjF,IAAK,KAAM,OAAOG,EAAWI,EAAMP,EAAQ,IAAI,GAAKG,EAAWK,EAAOR,EAAQ,IAAI,EAClF,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,IACjC,MAAM,IAAIb,EAAU,IAAIS,EAAE,EAAE,kGAAkG,EAChI,QAAS,KACX,CACF,CAEA,OAAQA,EAAE,GAAI,CACZ,IAAK,KAAM,OAAOgB,EAAYL,EAAMC,CAAK,EACzC,IAAK,KAAM,MAAO,CAACI,EAAYL,EAAMC,CAAK,EAC1C,IAAK,IAAM,OAAAK,EAAcN,EAAMC,EAAO,GAAG,EAAYD,EAAoBC,EACzE,IAAK,KAAM,OAAAK,EAAcN,EAAMC,EAAO,IAAI,EAAWD,GAAoBC,EACzE,IAAK,IAAM,OAAAK,EAAcN,EAAMC,EAAO,GAAG,EAAYD,EAAoBC,EACzE,IAAK,KAAM,OAAAK,EAAcN,EAAMC,EAAO,IAAI,EAAWD,GAAoBC,EACzE,IAAK,IAEH,GADI,OAAOD,GAAS,UAAY,OAAOC,GAAU,UAC7C,OAAOD,GAAS,UAAY,OAAOC,GAAU,SAAU,OAAOD,EAAOC,EACzE,MAAM,IAAIrB,EAAU,gDAAgD,OAAOoB,CAAI,QAAQ,OAAOC,CAAK,EAAE,EACvG,IAAK,IAAK,OAAAK,EAAcN,EAAMC,EAAO,GAAG,EAAWD,EAAmBC,EACtE,IAAK,IAAK,OAAAK,EAAcN,EAAMC,EAAO,GAAG,EAAWD,EAAmBC,EACtE,IAAK,IAEH,GADAK,EAAcN,EAAMC,EAAO,GAAG,EACzBA,IAAqB,EAAG,MAAM,IAAIrB,EAAU,kBAAkB,EACnE,OAAQoB,EAAmBC,CAC/B,CACF,CACF,CACF,EAEA,OAAOb,EAAIL,CAAI,CACjB,CAWA,SAASsB,EAAYE,EAAgBC,EAAyB,CAC5D,GAAI,MAAM,QAAQD,CAAC,GAAK,MAAM,QAAQC,CAAC,EAAG,CAExC,GADI,CAAC,MAAM,QAAQD,CAAC,GAAK,CAAC,MAAM,QAAQC,CAAC,GACrCD,EAAE,SAAWC,EAAE,OAAQ,MAAO,GAClC,QAASC,EAAI,EAAGA,EAAIF,EAAE,OAAQE,IAAK,GAAIF,EAAEE,CAAC,IAAMD,EAAEC,CAAC,EAAG,MAAO,GAC7D,MAAO,EACT,CACA,OAAOF,IAAMC,CACf,CAEA,SAASF,EAAcR,EAAgBC,EAAgBW,EAAkB,CACvE,GAAI,OAAOZ,GAAM,UAAY,OAAOC,GAAM,SACxC,MAAM,IAAInB,EAAU,IAAI8B,CAAE,oCAAoC,OAAOZ,CAAC,QAAQ,OAAOC,CAAC,EAAE,CAE5F,CAOA,SAASL,EAASX,EAAgBC,EAAiD,CACjF,GAAI,EAAAD,EAAK,OAAS,aAAeC,EAAI,YAAc,QACnD,OAAOA,EAAI,UAAUD,EAAK,MAAOA,EAAK,IAAI,CAC5C,CAIA,SAASa,EAAWe,EAAoBlB,EAA2BiB,EAAoB,CACrF,GAAI,OAAOC,GAAU,SACnB,MAAM,IAAI/B,EAAU,IAAI8B,CAAE,uCAAuC,OAAOC,CAAK,EAAE,EAEjF,IAAMF,EAAIhB,EAAO,QAAQkB,CAAK,EAC9B,GAAIF,EAAI,EAAG,MAAM,IAAI7B,EAAU,IAAI+B,CAAK,6CAA6ClB,EAAO,KAAK,IAAI,CAAC,GAAG,EACzG,OAAOgB,CACT,CAEA,IAAML,GAAa,CAACG,EAAsBC,IACxCD,EAAE,SAAWC,EAAE,QAAUD,EAAE,MAAM,CAACK,EAAGH,IAAMG,IAAMJ,EAAEC,CAAC,CAAC,EC7JhD,IAAMI,GAA0C,CACrD,KAAM,cACN,MAAQC,GAAS,KAAK,IAAI,EAAGA,EAAK,KAAK,OAAS,CAAC,CACnD,EAEMC,GAAkD,CAACF,EAAyB,EAmB3E,SAASG,EACdF,EACAG,EACAC,EACQ,CACR,IAAMC,EAAgBD,GAAM,eAAiBH,GAC7C,OAAOK,EAAKN,EAAMI,GAAM,MAAQ,GAAMD,EAAYE,CAAa,CACjE,CAEA,SAASC,EACPN,EACAO,EACAJ,EACAE,EACQ,CACR,GAAIL,EAAK,OAAS,WAAaA,EAAK,KAAO,OAASA,EAAK,KAAO,MAAO,CACrE,IAAMQ,EAAIF,EAAKN,EAAK,KAAMO,EAAMJ,EAAYE,CAAa,EACnDI,EAAIH,EAAKN,EAAK,MAAOO,EAAMJ,EAAYE,CAAa,EAG1D,OADqBL,EAAK,KAAO,QAAWO,EACpBC,EAAI,GAAKC,EAAI,EAAID,EAAIC,EAAI,EAC1C,KAAK,IAAID,EAAGC,CAAC,CACtB,CACA,GAAIT,EAAK,OAAS,SAAWA,EAAK,KAAO,MACvC,OAAOM,EAAKN,EAAK,QAAS,CAACO,EAAMJ,EAAYE,CAAa,EAE5D,GAAIL,EAAK,OAAS,OAAQ,CACxB,IAAMU,EAAOL,EAAc,KAAMM,GAAMA,EAAE,OAASX,EAAK,IAAI,EAC3D,GAAIU,EAAM,CACR,IAAME,EAAWF,EAAK,MAAMV,CAAI,EAC1Ba,EAAQV,EAAWH,CAAI,EAC7B,OAAIO,EAAaM,EAAQD,EAAW,EAC7BC,EAAQ,EAAI,CACrB,CACF,CAEA,OAAOV,EAAWH,CAAI,IAAMO,EAAO,EAAI,CACzC,CCCO,IAAMO,EAAN,MAAMC,CAAY,CAId,OAAsC,CAAC,EACxC,MAAQ,IAAI,IACH,YAAc,IAAI,IAClB,SAAW,IAAI,IAIf,KAEjB,YAAYC,EAAmC,CAAC,EAAGC,EAAiD,CAClG,KAAK,KAAOA,GAAM,YAAeC,GAAMA,EAAE,YAAY,GACrD,KAAK,KAAKF,CAAY,CACxB,CAEQ,KAAKA,EAAwC,CACnD,QAAWG,KAAKH,EAAc,CAC5B,IAAMI,EAAO,KAAK,KAAKD,EAAE,IAAI,EAC7B,KAAK,MAAM,IAAIC,EAAMD,CAAC,EAGtB,KAAK,OAAOC,CAAI,EAAI,gBAAgBD,EAAE,SAAWE,EAAWF,CAAC,CAAC,CAChE,CACF,CAEA,IAAIC,EAAuC,CACzC,OAAO,KAAK,OAAO,KAAK,KAAKA,CAAI,CAAC,CACpC,CAKA,IAAIA,EAAcE,EAAoBL,EAAyD,CAC7F,IAAMC,EAAI,KAAK,KAAKE,CAAI,EACxB,GAAI,KAAK,MAAM,IAAIF,CAAC,GAAG,WAAa,GAAO,MAAM,IAAI,MAAM,IAAIE,CAAI,gBAAgB,EACnF,IAAMG,EAAoB,CACxB,KAAML,EACN,KAAM,KAAK,OAAOA,CAAC,EACnB,KAAMI,EACN,OAAQL,GAAM,QAAU,GACxB,OAAQA,GAAM,MAChB,EACA,KAAK,OAAOC,CAAC,EAAII,EACjB,QAAWE,KAAS,KAAK,SAAUA,EAAMD,CAAM,EAC/C,GAAI,CAACA,EAAO,OAAQ,QAAWE,KAAM,KAAK,YAAaA,EAAGF,CAAM,EAChE,OAAOA,CACT,CAGA,UAAUE,EAA6C,CACrD,YAAK,YAAY,IAAIA,CAAE,EAChB,IAAM,KAAK,YAAY,OAAOA,CAAE,CACzC,CAGA,QAAQA,EAA6C,CACnD,YAAK,SAAS,IAAIA,CAAE,EACb,IAAM,KAAK,SAAS,OAAOA,CAAE,CACtC,CAIA,MAAsB,CACpB,MAAO,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,EAAE,IAAI,CAAC,CAACL,EAAMD,CAAC,IAAMO,EAAOP,EAAG,KAAK,IAAIC,CAAI,EAAG,OAAWA,CAAI,CAAC,CAChG,CAEA,cAAmC,CACjC,MAAO,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC,CAChC,CAKA,OAAqB,CACnB,IAAMO,EAAI,IAAIZ,EAAY,CAAC,EAAG,CAAE,UAAW,KAAK,IAAK,CAAC,EACtD,OAAAY,EAAE,MAAQ,IAAI,IAAI,KAAK,KAAK,EAC5B,OAAO,OAAOA,EAAE,OAAQ,gBAAgB,KAAK,MAAM,CAAC,EAC7CA,CACT,CAIA,OAAOX,EAAwC,CAC7C,QAAWY,KAAK,OAAO,KAAK,KAAK,MAAM,EAAG,OAAO,KAAK,OAAOA,CAAC,EAC9D,KAAK,MAAM,MAAM,EACjB,KAAK,KAAKZ,CAAY,CACxB,CAGA,MAAoC,CAClC,OAAO,gBAAgB,KAAK,MAAM,CACpC,CAKA,KAAKa,EAA2C,CAC9C,OAAW,CAACD,EAAGE,CAAC,IAAK,OAAO,QAAQD,CAAM,EAAG,KAAK,OAAO,KAAK,KAAKD,CAAC,CAAC,EAAIE,CAC3E,CACF,EAEA,SAASJ,EAAOP,EAAqBG,EAAgCS,EAAoBX,EAA4B,CACnH,MAAO,CACL,KAAMA,GAAQD,EAAE,KAAK,YAAY,EACjC,KAAMA,EAAE,KACR,MAAAG,EACA,QAASH,EAAE,SAAWE,EAAWF,CAAC,EAClC,GAAIA,EAAE,SAAW,OAAY,CAAE,OAAQA,EAAE,MAAO,EAAI,CAAC,EACrD,SAAUY,GAAYZ,EAAE,UAAY,EACtC,CACF,CAyBO,IAAMa,EAAwB,EAExBC,EAAN,KAAoB,CACR,OAAS,IAAI,IAO9B,YAAYC,EAAelB,EAAwC,CACjE,OAAO,KAAK,WAAWkB,EAAO,IAAIpB,EAAYE,CAAY,CAAC,CAC7D,CAOA,WAAWkB,EAAeC,EAAwB,CAChD,YAAK,WAAWD,CAAK,EACrB,KAAK,OAAO,IAAIA,EAAO,CAAE,KAAM,QAAS,IAAAC,CAAI,CAAC,EACtC,IACT,CAGA,SAASD,EAA4B,CACnC,IAAME,EAAI,KAAK,OAAO,IAAIF,CAAK,EAC/B,GAAI,CAACE,GAAKA,EAAE,OAAS,QAAS,MAAM,IAAI,MAAM,KAAKF,CAAK,yBAAyB,EACjF,OAAOE,EAAE,GACX,CASA,YAAYF,EAAelB,EAAwC,CACjE,YAAK,SAASkB,CAAK,EAAE,OAAOlB,CAAY,EACjC,IACT,CAQA,cACEkB,EACAG,EACArB,EAAmC,CAAC,EACpCsB,EAAgB,GACV,CACN,KAAK,WAAWJ,CAAK,EACrB,IAAMK,EAAQ,IAAI,IAClB,QAAWpB,KAAKH,EAAcuB,EAAM,IAAIpB,EAAE,KAAK,YAAY,EAAGA,CAAC,EAC/D,YAAK,OAAO,IAAIe,EAAO,CAAE,KAAM,UAAW,SAAAG,EAAU,MAAAE,EAAO,cAAAD,CAAc,CAAC,EACnE,IACT,CAEA,IAAIJ,EAAwB,CAC1B,OAAO,KAAK,OAAO,IAAIA,CAAK,CAC9B,CAGA,IAAIM,EAAepB,EAAuC,CACxD,IAAMgB,EAAI,KAAK,OAAO,IAAII,CAAK,EAC/B,GAAKJ,EACL,OAAOA,EAAE,OAAS,QAAUA,EAAE,IAAI,IAAIhB,CAAI,EAAIgB,EAAE,SAAS,IAAIhB,EAAK,YAAY,CAAC,CACjF,CAKA,IAAIoB,EAAepB,EAAcE,EAA0B,CACzD,IAAMc,EAAI,KAAK,OAAO,IAAII,CAAK,EAC/B,GAAI,CAACJ,EAAG,MAAM,IAAI,MAAM,mBAAmBI,CAAK,GAAG,EACnD,GAAIJ,EAAE,OAAS,QAAS,CACtB,GAAI,CACFA,EAAE,IAAI,IAAIhB,EAAME,CAAK,CACvB,MAAQ,CACN,MAAM,IAAI,MAAM,KAAKkB,CAAK,IAAIpB,CAAI,gBAAgB,CACpD,CACA,MACF,CACA,IAAMF,EAAIE,EAAK,YAAY,EAC3B,GAAI,CAAC,KAAK,gBAAgBgB,EAAGlB,CAAC,EAAG,MAAM,IAAI,MAAM,KAAKsB,CAAK,IAAIpB,CAAI,gBAAgB,EACnFgB,EAAE,SAAS,IAAKlB,EAAGI,CAAK,CAC1B,CAEQ,gBAAgB,EAAiBF,EAAuB,CAC9D,OAAK,EAAE,SAAS,IACT,EAAE,MAAM,IAAIA,CAAI,GAAG,UAAY,EAAE,cADZ,EAE9B,CAKA,gBAAsD,CACpD,IAAMqB,EAA2C,CAAC,EAClD,OAAW,CAACP,EAAOE,CAAC,IAAK,KAAK,OAC5B,GAAIA,EAAE,OAAS,QACb,QAAWM,KAAON,EAAE,IAAI,KAAK,EAAGK,EAAI,KAAK,CAAE,MAAOP,EAAO,GAAGQ,CAAI,CAAC,MAEjE,SAAWvB,KAAKiB,EAAE,MAAM,OAAO,EAC7BK,EAAI,KAAK,CACP,MAAOP,EACP,GAAGR,EAAOP,EAAGiB,EAAE,SAAS,IAAIjB,EAAE,KAAK,YAAY,CAAC,EAAG,KAAK,gBAAgBiB,EAAGjB,EAAE,KAAK,YAAY,CAAC,CAAC,CAClG,CAAC,EAIP,OAAOsB,CACT,CAOA,cAAcE,EAA6C,CACzD,IAAMC,EAAgC,CAAC,EACvC,OAAW,CAACV,EAAOE,CAAC,IAAK,KAAK,OAC5BQ,EAAOV,CAAK,EAAIE,EAAE,OAAS,QAAUA,EAAE,IAAI,OAASA,EAAE,SAMxD,IAAMS,EAAY,KAAK,eAAe,EACtC,OAAOA,EAAU,OAAS,EAAI,CAAE,OAAAD,EAAQ,KAAAD,CAAK,EAAI,CAC/C,OAAAC,EAAQ,KAAAD,EACR,UAAW,CAACH,EAAOpB,IAASyB,EAAU,IAAIL,CAAK,GAAG,IAAIpB,EAAK,YAAY,CAAC,CAC1E,CACF,CAGQ,gBAA8D,CACpE,IAAMqB,EAAM,IAAI,IAChB,OAAW,CAACP,EAAOE,CAAC,IAAK,KAAK,OAAQ,CACpC,IAAMG,EAAQH,EAAE,OAAS,QAAUA,EAAE,IAAI,aAAa,EAAI,CAAC,GAAGA,EAAE,MAAM,OAAO,CAAC,EAC9E,QAAWjB,KAAKoB,EAAO,CACrB,GAAIpB,EAAE,OAAS,WAAaA,EAAE,SAAW,OAAW,SACpD,IAAI2B,EAAIL,EAAI,IAAIP,CAAK,EAChBY,IAAKA,EAAI,IAAI,IAAOL,EAAI,IAAIP,EAAOY,CAAC,GACzCA,EAAE,IAAI3B,EAAE,KAAK,YAAY,EAAGA,EAAE,MAAM,CACtC,CACF,CACA,OAAOsB,CACT,CAOA,UAA6B,CAC3B,IAAMM,EAAa,IAAI,IACvB,OAAW,CAACb,EAAOE,CAAC,IAAK,KAAK,OAAQ,CACpC,IAAMG,EAAQH,EAAE,OAAS,QAAUA,EAAE,IAAI,aAAa,EAAI,CAAC,GAAGA,EAAE,MAAM,OAAO,CAAC,EAC9E,GAAIG,EAAM,SAAW,EAAG,SACxB,IAAMO,EAAI,IAAI,IACd,QAAW3B,KAAKoB,EAAOO,EAAE,IAAI3B,EAAE,KAAK,YAAY,EAAG,CACjD,KAAMA,EAAE,KAAM,WAAYA,EAAE,OAC5B,GAAIA,EAAE,SAAW,OAAY,CAAE,OAAQA,EAAE,MAAO,EAAI,CAAC,CACvD,CAAC,EACD4B,EAAW,IAAIb,EAAOY,CAAC,CACzB,CACA,MAAO,CAAE,WAAAC,CAAW,CACtB,CAMA,MAAoD,CAClD,IAAMN,EAAmD,CAAC,EAC1D,OAAW,CAACP,EAAOE,CAAC,IAAK,KAAK,OAAYA,EAAE,OAAS,UAASK,EAAIP,CAAK,EAAIE,EAAE,IAAI,KAAK,GACtF,OAAOK,CACT,CAIA,KAAKO,EAAyD,CAC5D,OAAW,CAACd,EAAOe,CAAI,IAAK,OAAO,QAAQD,CAAI,EAAG,CAChD,IAAMZ,EAAI,KAAK,OAAO,IAAIF,CAAK,EAC3BE,GAAG,OAAS,SAASA,EAAE,IAAI,KAAKa,CAAI,CAC1C,CACF,CAKA,cAAmC,CACjC,MAAO,CAAE,QAASjB,EAAuB,OAAQ,KAAK,KAAK,CAAE,CAC/D,CAGA,aAAakB,EAAoC,CAC/C,GAAIA,EAAS,UAAYlB,EACvB,MAAM,IAAI,MAAM,4CAA4CkB,EAAS,OAAO,gBAAgBlB,CAAqB,GAAG,EAEtH,KAAK,KAAKkB,EAAS,MAAM,CAC3B,CAEQ,WAAWhB,EAAqB,CACtC,GAAI,KAAK,OAAO,IAAIA,CAAK,EAAG,MAAM,IAAI,MAAM,WAAWA,CAAK,yBAAyB,CACvF,CACF,EAEA,SAASb,EAAWF,EAAkC,CACpD,GAAIA,EAAE,UAAY,OAAW,OAAOA,EAAE,QACtC,OAAQA,EAAE,KAAM,CACd,IAAK,UAAW,MAAO,GACvB,IAAK,SAAU,MAAO,GACtB,IAAK,SAAU,MAAO,GACtB,IAAK,OAAQ,OAAOA,EAAE,SAAS,CAAC,GAAK,GACrC,IAAK,QAAS,MAAO,CAAC,EAEtB,IAAK,UAAW,OAAOA,EAAE,SAAS,CAAC,GAAK,EAC1C,CACF,CC9bA,SAASgC,EAAKC,EAA4B,CACxC,OAAQA,EAAE,IAAI,MAAQ,CAAC,CACzB,CAGO,IAAMC,EAAyB,CACpC,aAAc,SACd,OAAQ,CACN,CAAE,MAAO,QAAS,EAClB,CAAE,MAAO,OAAQ,CACnB,EACA,UAAW,CACT,OAAQ,CACN,QAAS,EAAG,QAAS,EAAG,WAAY,SACpC,KAAKC,EAAkBF,EAA6B,CAClD,GAAIE,EAAK,SAAW,EAAG,MAAM,IAAIC,EAAU,2CAA2C,EACtF,IAAMC,EAAOL,EAAKC,CAAC,EAAE,WACrB,GAAI,CAACI,EAAM,MAAM,IAAID,EAAU,2CAA2C,EAC1E,IAAME,EAAIL,EAAE,SAASE,EAAK,CAAC,CAAE,EAAGI,EAAIN,EAAE,SAASE,EAAK,CAAC,CAAE,EACvD,GAAI,OAAOG,GAAM,UAAY,OAAOC,GAAM,SAAU,MAAM,IAAIH,EAAU,wCAAwC,EAChH,GAAI,CAAC,OAAO,UAAUE,CAAC,GAAK,CAAC,OAAO,UAAUC,CAAC,EAAG,MAAM,IAAIH,EAAU,yCAAyC,EAC/G,IAAMI,EAAK,KAAK,IAAIF,EAAGC,CAAC,EAAGE,EAAK,KAAK,IAAIH,EAAGC,CAAC,EAC7C,OAAO,KAAK,MAAMF,EAAK,GAAKI,EAAKD,EAAK,EAAE,EAAIA,CAC9C,CACF,EACA,YAAa,CACX,QAAS,EAAG,WAAY,UAAW,cAAe,GAClD,SAAUE,GAAU,aAAa,EACjC,KAAKP,EAAkBF,EAA6B,CAClD,IAAMU,EAAQC,GAAUT,EAAK,CAAC,EAAGF,EAAG,aAAa,EACjD,QAASY,EAAI,EAAGA,EAAIV,EAAK,OAAQU,IAAK,CACpC,IAAMC,EAAMX,EAAKU,CAAC,EAClB,GAAIC,EAAI,OAAS,YAAa,MAAM,IAAIV,EAAU,wDAAwD,EAC1G,GAAIU,EAAI,OAAS,IAAM,CAACH,EAAM,SAASG,EAAI,IAAI,EAAIH,EAAM,SAASG,EAAI,IAAI,EAAG,MAAO,EACtF,CACA,MAAO,EACT,CACF,EACA,UAAW,CACT,QAAS,EAAG,WAAY,QAAS,cAAe,GAChD,SAAUJ,GAAU,WAAW,EAC/B,KAAKP,EAAkBF,EAA6B,CAClD,IAAMc,EAAS,CAAC,GAAGH,GAAUT,EAAK,CAAC,EAAGF,EAAG,WAAW,CAAC,EACrD,QAASY,EAAI,EAAGA,EAAIV,EAAK,OAAQU,IAAK,CACpC,IAAMC,EAAMX,EAAKU,CAAC,EAClB,GAAIC,EAAI,OAAS,YAAa,MAAM,IAAIV,EAAU,sDAAsD,EACxG,GAAIU,EAAI,OAAS,IAAYC,EAAO,SAASD,EAAI,IAAI,GAAGC,EAAO,KAAKD,EAAI,IAAI,MACvE,CAAE,IAAME,EAAMD,EAAO,QAAQD,EAAI,IAAI,EAAOE,GAAO,GAAGD,EAAO,OAAOC,EAAK,CAAC,CAAG,CACpF,CACA,OAAOD,CACT,CACF,EAKA,OAAQ,CACN,QAAS,EAAG,QAAS,EAAG,WAAY,SACpC,SAAUE,EAAM,QAAQ,EACxB,KAAM,CAACd,EAAMF,IAAMD,EAAKC,CAAC,EAAE,SAASiB,EAAOf,EAAMF,EAAG,QAAQ,CAAC,GAAK,CACpE,EACA,KAAM,CACJ,QAAS,EAAG,QAAS,EAAG,WAAY,UACpC,SAAUgB,EAAM,MAAM,EACtB,KAAM,CAACd,EAAMF,KAAOD,EAAKC,CAAC,EAAE,SAASiB,EAAOf,EAAMF,EAAG,MAAM,CAAC,GAAK,GAAK,CACxE,EACA,cAAe,CACb,QAAS,EAAG,QAAS,EAAG,WAAY,SACpC,SAAUgB,EAAM,eAAe,EAC/B,KAAM,CAACd,EAAMF,IAAMD,EAAKC,CAAC,EAAE,eAAeiB,EAAOf,EAAMF,EAAG,eAAe,CAAC,GAAK,CACjF,EACA,YAAa,CACX,QAAS,EAAG,QAAS,EAAG,WAAY,UACpC,SAAUgB,EAAM,aAAa,EAC7B,KAAM,CAACd,EAAMF,KAAOD,EAAKC,CAAC,EAAE,eAAeiB,EAAOf,EAAMF,EAAG,aAAa,CAAC,GAAK,GAAK,CACrF,CACF,CACF,EA2BO,SAASkB,EAASC,EAAaC,EAAsE,CAC1G,IAAMC,EAAQF,EAAI,QAAQ,KAAM,EAAE,EAAE,MAAM,GAAG,EAC7C,OAAIE,EAAM,SAAW,GAAKD,EAAQC,EAAM,CAAC,CAAE,EAClC,CAAE,MAAOA,EAAM,CAAC,EAAI,KAAMA,EAAM,CAAC,EAAG,YAAY,CAAE,EAEpD,CAAE,MAAO,SAAU,KAAMA,EAAM,KAAK,GAAG,EAAE,YAAY,CAAE,CAChE,CAGA,SAASC,EAAOC,EAAkBC,EAAgBC,EAAoB,CACpE,IAAMC,EAAIF,EAAE,SAASD,EAAK,CAAC,CAAE,EAC7B,GAAI,OAAOG,GAAM,SAAU,MAAM,IAAIC,EAAU,GAAGF,CAAE,gCAAgC,EACpF,OAAOC,CACT,CAGA,SAASE,EAAMC,EAAgB,CAC7B,MAAO,CAACN,EAAkBC,IAAwD,CAChF,IAAMM,EAAQP,EAAK,CAAC,EAChBO,GAASA,EAAM,OAAS,UAC1BN,EAAE,OAAO,CAAE,KAAM,CAAC,GAAGA,EAAE,KAAM,OAAQ,CAAC,EAAG,KAAM,iBAAkB,SAAU,QACzE,QAAS,GAAGK,CAAM,4EAA6E,CAAC,CAEtG,CACF,CAEA,SAASE,GAAUC,EAA2BR,EAAgBC,EAAsB,CAClF,GAAI,CAACO,EAAK,MAAM,IAAIL,EAAU,GAAGF,CAAE,wDAAwD,EAC3F,IAAMC,EAAIF,EAAE,SAASQ,CAAG,EACxB,GAAI,MAAM,QAAQN,CAAC,EAAG,OAAOA,EAC7B,GAAIA,IAAM,IAASA,IAAM,MAAQA,IAAM,OAAW,MAAO,CAAC,EAC1D,MAAM,IAAIC,EAAU,GAAGF,CAAE,4CAA4C,CACvE,CAGA,SAASQ,GAAUJ,EAAgB,CACjC,MAAO,CAACN,EAAkBC,IAAwD,CAChF,GAAID,EAAK,SAAW,EAAG,OACvB,IAAMO,EAAQP,EAAK,CAAC,EACpB,GAAIO,EAAM,OAAS,YAAa,CAC9BN,EAAE,OAAO,CAAE,KAAM,CAAC,GAAGA,EAAE,KAAM,OAAQ,CAAC,EAAG,KAAM,iBAAkB,SAAU,QACzE,QAAS,GAAGK,CAAM,8EAA+E,CAAC,EACpG,MACF,CACA,IAAMK,EAAOV,EAAE,OAAO,WAAW,IAAIM,EAAM,KAAK,GAAG,IAAIA,EAAM,IAAI,EACjE,GAAII,GAAQA,EAAK,OAAS,QAAS,CACjC,IAAMf,EAAMW,EAAM,QAAUN,EAAE,aAAeM,EAAM,KAAO,GAAGA,EAAM,KAAK,IAAIA,EAAM,IAAI,GACtFN,EAAE,OAAO,CAAE,KAAM,CAAC,GAAGA,EAAE,KAAM,OAAQ,CAAC,EAAG,KAAM,iBAAkB,SAAU,QACzE,QAAS,GAAGK,CAAM,SAASV,CAAG,kCAAkCe,EAAK,IAAI,GAAI,CAAC,EAChF,MACF,CAGA,QAAS,EAAI,EAAG,EAAIX,EAAK,OAAQ,IAAK,CACpC,IAAMS,EAAMT,EAAK,CAAC,EACdS,EAAI,OAAS,YACfR,EAAE,OAAO,CAAE,KAAM,CAAC,GAAGA,EAAE,KAAM,OAAQ,CAAC,EAAG,KAAM,iBAAkB,SAAU,QACzE,QAAS,GAAGK,CAAM,gBAAgB,EAAI,CAAC,iCAAkC,CAAC,EACnEK,GAAM,OAAS,SAAWA,EAAK,YAAc,CAACA,EAAK,WAAW,SAASF,EAAI,IAAI,GACxFR,EAAE,OAAO,CAAE,KAAM,CAAC,GAAGA,EAAE,KAAM,OAAQ,CAAC,EAAG,KAAM,oBAAqB,SAAU,QAC5E,QAAS,GAAGK,CAAM,qBAAqBG,EAAI,IAAI,IAAK,UAAWA,EAAI,IAAK,CAAC,CAE/E,CACF,CACF,CA6FA,IAAMG,GAAW,oBAajB,SAAUC,GAASC,EAAgC,CACjD,IAAIC,EAAM,GACNC,EAAI,EACR,KAAOA,EAAIF,EAAK,QAAQ,CACtB,IAAMG,EAAIH,EAAKE,CAAC,EAChB,GAAIC,IAAM,KAAOH,EAAKE,EAAI,CAAC,IAAM,IAAK,CAAED,GAAO,IAAKC,GAAK,EAAG,QAAU,CACtE,GAAIC,IAAM,KAAOH,EAAKE,EAAI,CAAC,IAAM,IAAK,CAAED,GAAO,IAAKC,GAAK,EAAG,QAAU,CACtE,GAAIC,IAAM,IAAK,CACb,IAAMC,EAAQJ,EAAK,QAAQ,IAAKE,EAAI,CAAC,EACrC,GAAIE,IAAU,GAAI,CAChB,IAAMC,EAAML,EAAK,MAAME,EAAGE,EAAQ,CAAC,EAC7BE,EAAQN,EAAK,MAAME,EAAI,EAAGE,CAAK,EAAE,KAAK,EAC5C,GAAIE,EAAM,WAAW,GAAG,EAAG,CACrBL,IAAO,KAAM,CAAE,KAAM,OAAQ,MAAOA,CAAI,EAAGA,EAAM,IACrD,KAAM,CAAE,KAAM,OAAQ,IAAAI,EAAK,MAAAC,EAAO,IAAKR,GAAS,KAAKQ,CAAK,EAAIA,EAAQ,MAAU,EAChFJ,EAAIE,EAAQ,EACZ,QACF,CACAH,GAAOI,EAAKH,EAAIE,EAAQ,EAAG,QAC7B,CACF,CACAH,GAAOE,EAAGD,GAAK,CACjB,CACID,IAAK,KAAM,CAAE,KAAM,OAAQ,MAAOA,CAAI,EAC5C,CAiBO,SAASM,GAAgBC,EAAwB,CACtD,OAAI,MAAM,QAAQA,CAAC,EAAUA,EAAE,KAAK,IAAI,EACpC,OAAOA,GAAM,UAAkBA,EAAI,OAAS,QACzC,OAAOA,CAAC,CACjB,CAIA,SAASC,GAAYC,EAAoB,CACvC,OAAOA,IAAM,KAAOA,IAAM,KAAQA,IAAM;AAAA,GAAQA,IAAM,MAAQA,IAAM,MAAQA,IAAM,IACpF,CAIA,SAASC,GAAkBC,EAAmB,CAC5C,IAAIC,EAAM,GACNC,EAAe,GACnB,QAAWJ,KAAKE,EAAG,CACjB,GAAIH,GAAYC,CAAC,EAAG,CAAEI,EAAe,GAAM,QAAU,CACjDA,GAAgBD,EAAI,OAAS,IAAGA,GAAO,KAC3CC,EAAe,GACfD,GAAOH,CACT,CACA,OAAOG,CACT,CAWO,SAASE,GAAcC,EAAcC,EAAcC,EAAuB,CAC/E,GAAID,EAAK,SAAW,GAAKD,EAAK,QAAQC,CAAI,EAAI,EAAG,OAAOD,EACxD,IAAIH,EAAM,GACNM,EAAI,EACJC,EAAU,GACd,KAAOD,EAAIH,EAAK,QAAQ,CACtB,GAAIA,EAAK,WAAWC,EAAME,CAAC,EAAG,CAC5B,IAAME,EAAML,EAAK,QAAQE,EAAOC,EAAIF,EAAK,MAAM,EAC/C,GAAII,GAAO,EAAG,CAAEF,EAAIE,EAAMH,EAAM,OAAQE,EAAU,GAAM,QAAU,CAClEP,GAAOG,EAAK,MAAMG,CAAC,EACnB,KACF,CACAN,GAAOG,EAAKG,CAAC,EACbA,GAAK,CACP,CACA,OAAOC,EAAUT,GAAkBE,CAAG,EAAIG,CAC5C,CAQO,SAASM,GAAYN,EAAcO,EAA2D,CACnG,GAAIP,EAAK,QAAQ,GAAG,EAAI,EAAG,OAAOA,EAClC,IAAIH,EAAM,GACV,QAAWW,KAAOC,GAAST,CAAI,EAAG,CAChC,GAAIQ,EAAI,OAAS,OAAQ,CAAEX,GAAOW,EAAI,MAAO,QAAU,CACvD,GAAI,CAACA,EAAI,IAAK,CAAEX,GAAOW,EAAI,IAAK,QAAU,CAC1C,IAAMhB,EAAIe,EAAQC,EAAI,GAAG,EACzBX,GAAOL,IAAM,OAAY,GAAKD,GAAgBC,CAAC,CACjD,CACA,OAAOK,CACT,CCzKO,SAASa,EACdC,EACAC,EACM,CACN,QAAWC,KAAQF,EAAO,CACxBC,EAAMC,CAAI,EAGV,IAAMC,EAAYD,EAAyC,SACvDC,GAAUJ,EAAUI,EAAUF,CAAK,CACzC,CACF,CA4BO,SAASG,GAAUC,EAAsB,CAC9C,OAAOA,EACJ,YAAY,EACZ,QAAQ,QAAS,EAAE,EACnB,QAAQ,eAAgB,GAAG,EAC3B,QAAQ,MAAO,GAAG,EAClB,QAAQ,WAAY,EAAE,CAC3B,CA2DO,SAASC,EAAgBC,EAAmD,CACjF,IAAMC,EAAID,EAAO,QAAQ,KAAK,EAC9B,OAAOC,GAAQC,GAAUF,EAAO,IAAI,CACtC,CAgbO,SAASG,EAAcC,EAAsB,CAClD,MAAO,QAAQA,CAAI,EACrB,CA0PO,IAAMC,EAAgD,CAAE,KAAM,IAAK,MAAO,GAAI,EAIxEC,GAA4B,MC3gCzC,SAASC,EAAOC,EAA0B,CACxC,IAAMC,EAAO,IAAI,IACXC,EAAgB,CAAC,EACvB,QAAWC,KAAKH,EAAWC,EAAK,IAAIE,CAAC,IAAKF,EAAK,IAAIE,CAAC,EAAGD,EAAI,KAAKC,CAAC,GACjE,OAAOD,CACT,CAQO,SAASE,EAAcC,EAAuC,CACnE,IAAMC,EAAQ,IAAI,IAEZC,EAAQ,CAACC,EAAuCC,IAA8B,CAClF,IAAMC,EAAMX,EAAO,CAAC,GAAGU,EAAW,GAAID,EAAK,MAAQ,CAAC,CAAE,CAAC,EAEvD,GADAF,EAAM,IAAIE,EAAK,GAAIE,CAAG,EAClBF,EAAK,OAAS,QAChB,QAAWG,KAASH,EAAK,SAAUD,EAAMI,EAAOD,CAAG,MAEnD,SAAWE,KAAQJ,EAAK,OAAS,CAAC,EAAGF,EAAM,IAAIM,EAAK,GAAIb,EAAO,CAAC,GAAGW,EAAK,GAAIE,EAAK,MAAQ,CAAC,CAAE,CAAC,CAAC,CAElG,EAEA,QAAWC,KAAS,OAAO,OAAOR,EAAO,MAAM,EAAG,CAChD,IAAMS,EAAWf,EAAOc,EAAM,MAAQ,CAAC,CAAC,EACxCP,EAAM,IAAIO,EAAM,GAAIC,CAAQ,EAC5B,QAAWC,KAASF,EAAM,OAAQ,CAChC,IAAMG,EAAWjB,EAAO,CAAC,GAAGe,EAAU,GAAIC,EAAM,MAAQ,CAAC,CAAE,CAAC,EAC5DT,EAAM,IAAIS,EAAM,GAAIC,CAAQ,EAC5B,QAAWL,KAASI,EAAM,SAAUR,EAAMI,EAAOK,CAAQ,CAC3D,CACF,CAEA,OAAOV,CACT,CCNA,IAAMW,GAAW,IAAI,QAqURC,EAAN,MAAMC,CAAO,CACD,KACA,YACA,UAAY,IAAI,IAIzB,WAEA,cAGS,YAGA,gBAAkB,IAAI,IACtB,gBAAkB,IAAI,IAItB,gBAEjB,YAAYC,EAAgBC,EAAyB,CAAC,EAAG,CACvD,KAAK,gBAAkBA,EACvB,IAAMC,EAASD,EAAQ,QAAUD,EAAO,QAAQ,QAC1CG,EAAaH,EAAO,QAC1B,KAAK,WAAaG,EAClB,KAAK,cAAgBD,EACrB,IAAME,EAAUD,EAAWD,CAAM,GAAK,CAAC,EACjCG,EAAiBF,EAAWH,EAAO,QAAQ,OAAO,GAAK,CAAC,EAGxDM,EAAMN,EAAO,aACbO,EAAUD,GAAK,OAAS,OAAS,CAACA,EAAI,YAC5C,KAAK,YAAcA,GAAK,OAAS,OAAS,CAAC,CAACA,EAAI,YAC5C,KAAK,aAAe,OAAO,QAAY,KACzC,QAAQ,KAAK,uHAAuH,EAItI,IAAME,EAAc,IAAI,IACxB,QAAWC,KAAKT,EAAO,MAAQ,CAAC,EAAOS,EAAE,aAAaD,EAAY,IAAIC,EAAE,KAAMA,EAAE,WAAW,EAC3F,KAAK,aAAeR,EAAQ,MAAQ,cAAgB,EAEpD,IAAMS,EAAY,IAAI,IAChBC,EAAa,IAAI,IACjBC,EAAY,IAAI,IACtB,OAAW,CAACC,EAASC,CAAK,IAAK,OAAO,QAAQd,EAAO,MAAM,EAAG,CAC5D,KAAK,gBAAgB,IAAIe,EAAgBD,CAAK,EAAGD,CAAO,EACxD,IAAMG,EAAa,IAAI,IACvB,QAAWC,KAASH,EAAM,OACxBH,EAAW,IAAIM,EAAM,GAAI,CAAE,QAAAJ,CAAQ,CAAC,EACpCD,EAAU,IAAIK,EAAM,GAAIA,CAAK,EAC7BD,EAAW,IAAID,EAAgBE,CAAK,EAAGA,EAAM,EAAE,EAC/CC,EAA0BD,EAAM,SAAWE,GAAMT,EAAU,IAAIS,EAAE,GAAIA,CAAC,CAAC,EAEzE,KAAK,gBAAgB,IAAIN,EAASG,CAAU,CAC9C,CAIA,IAAMI,EAAQpB,EAAO,YAAc,CAAC,EAC9BqB,EAAoBD,EAAM,OAAQE,GAAMA,EAAE,QAAU,EAAI,EAAE,IAAIC,EAAM,EACpEC,GAAmBJ,EAAM,OAAQE,GAAM,EAAEA,EAAE,QAAU,GAAK,EAAE,IAAIC,EAAM,EACtEE,GAAoB,IAAI,IAAIJ,EAAkB,IAAKK,GAAMA,EAAE,KAAK,YAAY,CAAC,CAAC,EAE9EC,EAAS,IAAIC,EAAc,EAAE,YAAY,SAAUP,CAAiB,EACpEQ,EAAY,IAAI,IAItB,GAAI5B,EAAQ,MAAO,CACjB,IAAM6B,EAAY9B,EAAO,eAAe,OAAO,KAAM+B,GAAMA,EAAE,QAAU,OAAO,EACxEC,GAASF,GAAW,cAAgB,CAAC,GAAG,IAAIG,EAAa,EAC/DN,EAAO,cAAc,QAAS1B,EAAQ,MAAO+B,EAAOF,GAAW,UAAY,EAAI,EAC/ED,EAAU,IAAI,OAAO,CACvB,CAIA,QAAWK,KAAQlC,EAAO,eAAe,QAAU,CAAC,EAAG,CACrD,GAAI6B,EAAU,IAAIK,EAAK,KAAK,EAAG,SAC/B,IAAMF,GAASE,EAAK,cAAgB,CAAC,GAAG,IAAID,EAAa,EACzDN,EAAO,cAAcO,EAAK,MAAOC,GAAmBD,EAAK,cAAgB,CAAC,CAAC,EAAGF,EAAOE,EAAK,UAAY,EAAI,CAC5G,CAIA,IAAME,EAAmB,IAAI,IAC7B,OAAW,CAACvB,EAASC,CAAK,IAAK,OAAO,QAAQd,EAAO,MAAM,EAAG,CAC5D,IAAMqC,EAAQ,IAAI,KAAKvB,EAAM,YAAc,CAAC,GAAG,OAAQQ,GAAMA,EAAE,QAAU,EAAK,EAAE,IAAKA,GAAMA,EAAE,KAAK,YAAY,CAAC,CAAC,EAChHc,EAAiB,IAAIvB,EAASwB,CAAK,CACrC,CAEA,KAAK,KAAO,CACV,OAAArC,EAAQ,QAAAO,EAAS,QAAAH,EAAS,eAAAC,EAAgB,YAAAG,EAAa,UAAAE,EAAW,WAAAC,EAAY,UAAAC,EAC9E,gBAAiB,KAAK,gBAAiB,gBAAiB,KAAK,gBAC7D,SAAU0B,EAActC,CAAM,EAAG,OAAA2B,EACjC,kBAAAN,EAAmB,iBAAAG,GAAkB,kBAAAC,GAAmB,iBAAAW,EACxD,aAAc,IAAI,IAClB,gBAAiB,IAAI,IACrB,UAAW,IAAI,IACf,UAAWnC,EAAQ,IACnB,YAAaA,EAAQ,YACrB,qBAAsBA,EAAQ,sBAAwB,GACtD,WAAYA,EAAQ,gBAAkB,GACtC,aAAcD,EAAO,gBAAkBuC,GAA4B,KACnE,cAAevC,EAAO,gBAAkBuC,GAA4B,MACpE,iBAAkBvC,EAAO,gBAAgB,WAAawC,GACtD,cAAe,IAAI,GACrB,CACF,CAGA,IAAI,QAAiB,CAAE,OAAO,KAAK,aAAe,CAIlD,IAAI,eAAyB,CAAE,OAAO,KAAK,WAAa,CAUxD,UAAUtC,EAAsB,CAC9B,KAAK,cAAgBA,EACrB,KAAK,KAAK,QAAU,KAAK,WAAWA,CAAM,GAAK,CAAC,CAClD,CAUA,eAAeF,EAAsB,CACnC,KAAK,WAAaA,EAAO,QACzB,KAAK,KAAK,QAAU,KAAK,WAAW,KAAK,aAAa,GAAK,CAAC,EAC5D,KAAK,KAAK,eAAiB,KAAK,WAAW,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAK,CAAC,CACnF,CAeA,QAAQA,EAAwB,CAC9B,IAAMyC,EAAW,KAAK,SAAS,EACzBC,EAAaC,IACjBA,EAAK,UAAU,KAAK,aAAa,EACjCA,EAAK,kBAAkB,KAAK,KAAK,UAAU,EACpCA,GAEHA,EAAO,IAAI5C,EAAOC,EAAQ,KAAK,eAAe,EACpD,GAAI,CACF,OAAA2C,EAAK,SAASF,CAAQ,EACfC,EAAUC,CAAI,CACvB,MAAQ,CAGN,IAAMC,EAAQ,IAAI7C,EAAOC,EAAQ,KAAK,eAAe,EACrD,OAAW,CAAC6C,EAAIC,CAAC,IAAK,OAAO,QAAQL,EAAS,KAAK,EAAG,CACpD,IAAM5B,EAAUiC,EAAE,OAAO,eACzB,GAAI,CAAEF,EAAM,SAASC,EAAIhC,IAAY,KAAO,CAAE,MAAOA,CAAQ,EAAI,CAAC,CAAC,CAAG,MAAQ,CAAqC,CACrH,CACA,OAAO6B,EAAUE,CAAK,CACxB,CACF,CAGA,IAAI,gBAA0B,CAAE,OAAO,KAAK,KAAK,UAAY,CAS7D,kBAAkBG,EAAmB,CACnC,KAAK,KAAK,WAAaA,CACzB,CAeA,SAASF,EAAYG,EAAwB,CAAC,EAAS,CACrD,IAAMnC,EAAU,KAAK,gBAAgBmC,EAAK,KAAK,EACzCC,EAAU,KAAK,gBAAgBpC,EAASmC,EAAK,KAAK,EACxD,KAAK,UAAU,IAAIH,CAAE,GAAG,MAAM,EAC9B,IAAMK,EAAO,IAAIC,EAAKN,EAAI,KAAK,KAAMG,EAAK,MAAQ,KAAK,WAAW,EAClE,YAAK,UAAU,IAAIH,EAAIK,CAAI,EAC3BA,EAAK,MAAMrC,EAASoC,CAAO,EACpBC,CACT,CAyBA,QAAQA,EAAcpC,EAAeG,EAA+C,CAClF,IAAMmC,EAAW,KAAK,UAAU,IAAIF,CAAI,EACxC,GAAI,CAACE,EAAU,OAAO,KAAK,SAASF,EAAM,CAAE,MAAApC,EAAO,MAAAG,CAAM,CAAC,EAAE,cAAc,EAAE,OAC5E,GAAI,CAACmC,EAAS,KAAKtC,EAAOG,CAAK,EAC7B,MAAM,IAAI,MAAM,+BAA+BH,CAAK,GAAGG,IAAU,OAAY,GAAK,MAAMA,CAAK,EAAE,EAAE,EAEnG,OAAOmC,EAAS,cAAc,EAAE,MAClC,CAGQ,gBAAgBC,EAAkC,CACxD,GAAIA,GAAO,KACX,OAAI,KAAK,KAAK,OAAO,OAAOA,CAAG,EAAUA,EAClC,KAAK,gBAAgB,IAAIA,CAAG,GAAKA,CAC1C,CAGQ,gBAAgBxC,EAA6BwC,EAAkC,CACrF,GAAIA,GAAO,KACX,IAAI,KAAK,KAAK,UAAU,IAAIA,CAAG,EAAG,OAAOA,EACzC,GAAIxC,GAAW,KAAM,CAAE,IAAMgC,EAAK,KAAK,gBAAgB,IAAIhC,CAAO,GAAG,IAAIwC,CAAG,EAAG,GAAIR,EAAI,OAAOA,CAAI,CAClG,OAAOQ,EACT,CAIA,aAAaxC,EAAqC,CAChD,IAAMC,EAAQ,KAAK,KAAK,OAAO,OAAOD,CAAO,EAC7C,OAAOC,EAAQC,EAAgBD,CAAK,EAAI,MAC1C,CACA,aAAamC,EAAqC,CAChD,IAAMhC,EAAQ,KAAK,KAAK,UAAU,IAAIgC,CAAO,EAC7C,OAAOhC,EAAQF,EAAgBE,CAAK,EAAI,MAC1C,CAOA,YAAYqC,EAA0B,CACpC,OAAO,KAAK,KAAK,SAAS,IAAIA,CAAM,GAAK,CAAC,CAC5C,CAEA,aAAaC,EAA4B,CACvC,IAAMV,EAAK,KAAK,gBAAgBU,CAAQ,EACxC,OAAQV,GAAM,KAAO,KAAK,KAAK,SAAS,IAAIA,CAAE,EAAI,SAAc,CAAC,CACnE,CAEA,aAAaU,EAAkBC,EAA4B,CACzD,IAAM3C,EAAU,KAAK,gBAAgB0C,CAAQ,EACvCV,EAAK,KAAK,gBAAgBhC,EAAS2C,CAAQ,EACjD,OAAQX,GAAM,KAAO,KAAK,KAAK,SAAS,IAAIA,CAAE,EAAI,SAAc,CAAC,CACnE,CAOA,SAAoB,CAGlB,IAAMR,EAAkB,CAAC,EACzB,QAAW5B,KAAK,KAAK,KAAK,OAAO,MAAQ,CAAC,EAAOA,GAAG,MAAM4B,EAAM,KAAK5B,EAAE,IAAI,EAC3E,OAAO4B,CACT,CAUA,aAAakB,EAA4B,CACvC,IAAMV,EAAK,KAAK,gBAAgBU,CAAQ,EAClCzC,EAAQ+B,GAAM,KAAO,KAAK,KAAK,OAAO,OAAOA,CAAE,EAAI,OACzD,GAAI,CAAC/B,EAAO,MAAO,CAAC,EACpB,IAAM2C,EAAM,IAAI,IAChB,QAAWxC,KAASH,EAAM,OAAQ4C,GAAYzC,EAAM,SAAUwC,CAAG,EACjE,MAAO,CAAC,GAAGA,CAAG,CAChB,CAGA,aAAaF,EAAkBC,EAA4B,CACzD,IAAM3C,EAAU,KAAK,gBAAgB0C,CAAQ,EACvCV,EAAK,KAAK,gBAAgBhC,EAAS2C,CAAQ,EAC3CvC,EAAQ4B,GAAM,KAAO,KAAK,KAAK,UAAU,IAAIA,CAAE,EAAI,OACzD,GAAI,CAAC5B,EAAO,MAAO,CAAC,EACpB,IAAMwC,EAAM,IAAI,IAChB,OAAAC,GAAYzC,EAAM,SAAUwC,CAAG,EACxB,CAAC,GAAGA,CAAG,CAChB,CAOA,YAA6B,CAC3B,OAAO,OAAO,OAAO,KAAK,KAAK,OAAO,MAAM,EAAE,IAAK3C,IAAW,CAC5D,GAAIA,EAAM,GACV,GAAIC,EAAgBD,CAAK,EAAI,CAAE,OAAQC,EAAgBD,CAAK,CAAE,EAAI,CAAC,EACnE,KAAMA,EAAM,KACZ,GAAG,KAAK,UAAUA,EAAM,EAAE,EAC1B,OAAQA,EAAM,OAAO,IAAKG,IAAW,CACnC,GAAIA,EAAM,GACV,GAAIF,EAAgBE,CAAK,EAAI,CAAE,OAAQF,EAAgBE,CAAK,CAAE,EAAI,CAAC,EACnE,KAAMA,EAAM,KACZ,GAAG,KAAK,UAAUA,EAAM,EAAE,EAC1B,SAAUA,EAAM,SAAS,IAAKE,GAAM,KAAK,YAAYA,CAAC,CAAC,CACzD,EAAE,CACJ,EAAE,CACJ,CAOA,iBAA8B,CAC5B,IAAMsC,EAAkB,CAAC,EACzB,QAAW3C,KAAS,OAAO,OAAO,KAAK,KAAK,OAAO,MAAM,EACvD,QAAWG,KAASH,EAAM,OACxBI,EAA0BD,EAAM,SAAWE,GAAM,CAC/C,GAAIA,EAAE,OAAS,UACf,QAAWwC,KAAQxC,EAAE,OAAS,CAAC,EAC7BsC,EAAI,KAAK,CAAE,QAAS3C,EAAM,GAAI,QAASG,EAAM,GAAI,UAAWE,EAAE,GAAI,KAAM,KAAK,SAASwC,CAAI,CAAE,CAAC,CAEjG,CAAC,EAGL,OAAOF,CACT,CAGQ,YAAYtC,EAAgC,CAClD,OAAIA,EAAE,OAAS,QACN,CACL,KAAM,QACN,GAAIA,EAAE,GACN,GAAG,KAAK,UAAUA,EAAE,EAAE,EACtB,GAAIA,EAAE,SAAW,CAAE,SAAUA,EAAE,QAAS,EAAI,CAAC,EAC7C,GAAIA,EAAE,OAAS,CAAE,OAAQ,KAAK,SAASA,EAAE,MAAM,CAAE,EAAI,CAAC,EACtD,SAAUA,EAAE,SAAS,IAAKV,GAAM,KAAK,YAAYA,CAAC,CAAC,CACrD,EAEK,CACL,KAAM,UACN,GAAIU,EAAE,GACN,GAAG,KAAK,UAAUA,EAAE,EAAE,EACtB,OAAQA,EAAE,OAAS,CAAC,GAAG,IAAKyC,GAAM,KAAK,SAASA,CAAC,CAAC,EAClD,GAAIzC,EAAE,KAAO,CAAE,OAAQA,EAAE,KAAK,GAAI,GAAIA,EAAE,KAAK,KAAO,CAAE,SAAUA,EAAE,KAAK,IAAK,EAAI,CAAC,CAAG,EAAI,CAAC,CAC3F,CACF,CAGQ,SAASwC,EAAsB,CACrC,IAAME,EAAO,KAAK,KAAK,SAAS,IAAIF,EAAK,EAAE,EACrCG,EAAiB,CAAE,GAAIH,EAAK,GAAI,KAAMA,EAAK,IAAK,EACtD,GAAIA,EAAK,OAAS,OAAQ,CACxB,GAAIA,EAAK,YAAc,OAAW,CAChCG,EAAK,UAAYH,EAAK,UACtB,IAAMI,EAAO,KAAK,KAAK,eAAeC,EAAcL,EAAK,SAAS,CAAC,GAAK,KAAK,KAAK,YAAY,IAAIA,EAAK,SAAS,EAC5GI,IAAS,SAAWD,EAAK,cAAgBC,EAC/C,CACIJ,EAAK,YAAc,SAAWG,EAAK,UAAYH,EAAK,UAC1D,CACA,GAAIA,EAAK,OAAS,QAAUA,EAAK,OAAS,OAAQ,CAChD,IAAMM,EAAS,KAAK,KAAK,eAAeN,EAAK,EAAE,EAC3CM,IAAW,SAAWH,EAAK,KAAOG,EACxC,CACA,OAAIN,EAAK,UAAY,OAAO,KAAKA,EAAK,QAAQ,EAAE,SAAQG,EAAK,SAAWH,EAAK,UACzEE,GAAQA,EAAK,SAAQC,EAAK,KAAOD,GAC9BC,CACT,CAGQ,UAAUjB,EAAiC,CACjD,IAAMgB,EAAO,KAAK,KAAK,SAAS,IAAIhB,CAAE,EACtC,OAAOgB,GAAQA,EAAK,OAAS,CAAE,KAAAA,CAAK,EAAI,CAAC,CAC3C,CAGA,QAAQhB,EAA8B,CACpC,OAAO,KAAK,UAAU,IAAIA,CAAE,CAC9B,CAGA,OAAgB,CACd,MAAO,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,CACpC,CAIA,UAAUA,EAAkB,CAC1B,KAAK,UAAU,IAAIA,CAAE,GAAG,MAAM,EAC9B,KAAK,UAAU,OAAOA,CAAE,CAC1B,CAQA,OAAc,CACZ,QAAWK,KAAQ,KAAK,UAAU,OAAO,EAAGA,EAAK,MAAM,EACvD,KAAK,UAAU,MAAM,EACrB,KAAK,KAAK,OAAO,YAAY,SAAU,KAAK,KAAK,iBAAiB,EAClE,KAAK,KAAK,aAAa,MAAM,EAC7B,KAAK,KAAK,gBAAgB,MAAM,EAChC,KAAK,KAAK,UAAU,MAAM,CAC5B,CAGA,YAAYG,EAAsC,CAChD,GAAM,CAAE,MAAAa,EAAO,KAAAH,CAAK,EAAI,KAAK,YAAYV,CAAG,EAC5C,OAAO,KAAK,KAAK,OAAO,IAAIa,EAAOH,CAAI,CACzC,CAGA,YAAYV,EAAac,EAA0B,CACjD,GAAM,CAAE,MAAAD,EAAO,KAAAH,CAAK,EAAI,KAAK,YAAYV,CAAG,EAC5C,KAAK,KAAK,OAAO,IAAIa,EAAOH,EAAMI,CAAK,CACzC,CAIA,gBAAgC,CAC9B,OAAO,KAAK,KAAK,kBAAkB,IAAKzC,IAAO,CAC7C,IAAK,IAAIA,EAAE,IAAI,GACf,KAAMA,EAAE,KACR,OAAQA,EAAE,OACV,OAAQA,EAAE,OACV,MAAO,KAAK,YAAY,IAAIA,EAAE,IAAI,EAAE,EACpC,QAAS0C,GAAY1C,CAAC,CACxB,EAAE,CACJ,CAIQ,YAAY2B,EAA8C,CAChE,IAAIgB,EAAQ,KAAK,KAAK,cAAc,IAAIhB,CAAG,EAE3C,GADKgB,IAASA,EAAQC,EAASjB,EAAMkB,GAAMA,IAAM,SAAW,KAAK,KAAK,OAAO,IAAIA,CAAC,CAAC,EAAG,KAAK,KAAK,cAAc,IAAIlB,EAAKgB,CAAK,GACxHA,EAAM,QAAU,QAClB,MAAM,IAAI,MAAM,IAAIhB,CAAG,mFAAmF,EAE5G,OAAOgB,CACT,CAGA,MAAmB,CACjB,OAAO,KAAK,KAAK,OAAO,KAAK,CAC/B,CAGA,KAAKG,EAAwB,CAC3B,KAAK,KAAK,OAAO,KAAKA,CAAI,CAC5B,CAGA,UAAqB,CACnB,IAAMC,EAAsC,CAAC,EAC7C,OAAW,CAAC5B,EAAIK,CAAI,IAAK,KAAK,UAAWuB,EAAM5B,CAAE,EAAIK,EAAK,SAAS,EACnE,MAAO,CACL,QAAS,EACT,OAAQ,KAAK,KAAK,OAAO,KAAK,EAC9B,aAAc,OAAO,YAAY,KAAK,KAAK,YAAY,EACvD,gBAAiBwB,GAAmB,KAAK,KAAK,eAAe,EAC7D,UAAW,OAAO,YAAY,CAAC,GAAG,KAAK,KAAK,SAAS,EAAE,IAAI,CAAC,CAAC3C,EAAG4C,CAAG,IAAM,CAAC5C,EAAG,CAAE,GAAG4C,CAAI,CAAC,CAAC,CAAC,EACzF,MAAAF,CACF,CACF,CAGA,SAASG,EAAsB,CAC7B,GAAIA,EAAK,UAAY,EAAG,MAAM,IAAI,MAAM,6BAA6BA,EAAK,OAAO,EAAE,EACnF,KAAK,KAAK,OAAO,KAAKA,EAAK,MAAM,EACjC,KAAK,KAAK,aAAa,MAAM,EAC7B,OAAW,CAAC/B,EAAI1B,CAAC,IAAK,OAAO,QAAQyD,EAAK,cAAgB,CAAC,CAAC,EAAG,KAAK,KAAK,aAAa,IAAI/B,EAAI1B,CAAC,EAC/F,KAAK,KAAK,gBAAgB,MAAM,EAChC,OAAW,CAAC0B,EAAIgC,CAAE,IAAKC,GAAqBF,EAAK,eAAe,EAAG,KAAK,KAAK,gBAAgB,IAAI/B,EAAIgC,CAAE,EACvG,KAAK,KAAK,UAAU,MAAM,EAC1B,OAAW,CAAC9C,EAAG4C,CAAG,IAAK,OAAO,QAAQC,EAAK,WAAa,CAAC,CAAC,EAAG,KAAK,KAAK,UAAU,IAAI7C,EAAG,CAAE,GAAG4C,CAAI,CAAC,EAClG,KAAK,UAAU,MAAM,EACrB,OAAW,CAAC9B,EAAIkC,CAAI,IAAK,OAAO,QAAQH,EAAK,KAAK,EAAG,CACnD,IAAM1B,EAAO,IAAIC,EAAKN,EAAI,KAAK,KAAM,KAAK,WAAW,EACrDK,EAAK,QAAQ6B,CAAI,EACjB,KAAK,UAAU,IAAIlC,EAAIK,CAAI,CAC7B,CACF,CACF,EAMaC,EAAN,KAAW,CACP,GACQ,KACT,MACA,SAMA,QAAU,GACV,UAAY,GAGZ,OAAS,GACT,eAAgC,KAChC,MAAsB,CAAC,EACvB,cAAwC,KACxC,UAAY,EACZ,cAAoC,KAEpC,kBAAgD,KAGhD,qBAAsC,KACtC,UAAY,IAAI,IAEhB,YAAc,IAAI,IAQlB,UAAY,IAAI,IAEP,eAAgC,CAC/C,IAAMhC,GAAO,KAAK,KAAK,kBAAkB,IAAIA,CAAC,EAAI,KAAK,KAAK,OAAO,IAAI,SAAUA,CAAC,EAAI,KAAK,MAAM,IAAI,SAAUA,CAAC,EAChH,IAAK,CAACA,EAAG6D,IAAM,CACT,KAAK,KAAK,kBAAkB,IAAI7D,CAAC,EAAG,KAAK,KAAK,OAAO,IAAI,SAAUA,EAAG6D,CAAC,EACtE,KAAK,MAAM,IAAI,SAAU7D,EAAG6D,CAAC,CACpC,CACF,EAEiB,cAA+B,CAC9C,IAAM7D,GAAM,CACV,IAAMY,EAAI,KAAK,eACf,OAAIA,IAAM,KAAM,QACJ,KAAK,KAAK,iBAAiB,IAAIA,CAAC,GAAG,IAAIZ,CAAC,EAAI,KAAK,KAAK,UAAU,IAAIY,CAAC,EAAI,KAAK,UAAU,IAAIA,CAAC,KAC5FZ,CAAC,CAChB,EACA,IAAK,CAACA,EAAG6D,IAAM,CACb,IAAMjD,EAAI,KAAK,eACf,GAAIA,IAAM,KAAM,OAChB,IAAM4C,EAAM,KAAK,KAAK,iBAAiB,IAAI5C,CAAC,GAAG,IAAIZ,CAAC,EAAI,KAAK,KAAK,UAAU,IAAIY,CAAC,EAAI,KAAK,UAAU,IAAIA,CAAC,EACrG4C,IAAKA,EAAIxD,CAAC,EAAI6D,EACpB,CACF,EAOiB,QAEjB,YAAYnC,EAAYoC,EAAgBC,EAAc,CACpD,KAAK,GAAKrC,EACV,KAAK,KAAOoC,EACZ,KAAK,SAAWC,IAAS,EACzB,KAAK,MAAQ,KAAK,WAAW,EAE7B,IAAMC,EAAS,CAAE,GAAGF,EAAK,OAAO,cAAc,EAAE,MAAO,EACvDE,EAAO,OAAY,KAAK,eACxBA,EAAO,MAAW,KAAK,cACvB,KAAK,QAAU,CACb,OAAAA,EACA,KAAM,CACJ,WAAY,KAAK,IACjB,OAAStC,GAAe,KAAK,YAAY,IAAIA,CAAE,GAAK,EACpD,aAAeA,GAAe,KAAK,KAAK,aAAa,IAAIA,CAAE,GAAK,CAClE,EAKA,UAAW,CAACqB,EAAOH,IAAS,KAAK,UAAUG,EAAOH,CAAI,CACxD,CACF,CAIQ,UAAUG,EAAeH,EAA6C,CAC5E,IAAMqB,EAAMrB,EAAK,YAAY,EACvBsB,EAAarD,GACjBA,GAAO,KAAMN,GAAMA,EAAE,KAAK,YAAY,IAAM0D,GAAO1D,EAAE,OAAS,SAAS,GAAG,OAC5E,GAAIwC,IAAU,SACZ,OAAOmB,EAAU,KAAK,KAAK,iBAAiB,GAAKA,EAAU,KAAK,KAAK,gBAAgB,EAEvF,GAAInB,IAAU,QAAS,CACrB,IAAMpD,EAAQ,KAAK,gBAAkB,KAAO,KAAK,KAAK,OAAO,OAAO,KAAK,cAAc,EAAI,OAC3F,OAAOuE,EAAUvE,GAAO,UAAU,CACpC,CACA,OAAOuE,EAAU,KAAK,KAAK,OAAO,eAAe,OAAO,KAAMtD,GAAMA,EAAE,QAAUmC,CAAK,GAAG,YAAY,CACtG,CAKA,MAAMrD,EAAkBoC,EAAwB,CAa9C,GAZA,KAAK,UAAU,MAAM,EACrB,KAAK,MAAQ,KAAK,WAAW,EAC7B,KAAK,UAAU,MAAM,EACrB,KAAK,YAAY,MAAM,EACvB,KAAK,MAAQ,CAAC,EACd,KAAK,eAAiB,KACtB,KAAK,UAAY,GACjB,KAAK,cAAgB,KACrB,KAAK,UAAY,EACjB,KAAK,cAAgB,KACrB,KAAK,QAAU,GAEXA,EAAS,CACX,IAAM3C,EAAM,KAAK,KAAK,WAAW,IAAI2C,CAAO,EAC5C,GAAI,CAAC3C,EAAK,MAAM,IAAI,MAAM,kBAAkB2C,CAAO,EAAE,EACrD,KAAK,gBAAgB3C,EAAI,OAAO,EAChC,KAAK,MAAQ,CAAC,CAAE,QAASA,EAAI,QAAS,YAAa2C,EAAS,MAAO,CAAE,CAAC,EACtE,KAAK,MAAMA,CAAO,CACpB,KAAO,CACL,IAAMJ,EAAKhC,GAAW,OAAO,KAAK,KAAK,KAAK,OAAO,MAAM,EAAE,CAAC,EACtDC,EAAQ+B,EAAK,KAAK,KAAK,OAAO,OAAOA,CAAE,EAAI,OACjD,GAAI,CAAC/B,EAAO,MAAM,IAAI,MAAM+B,EAAK,kBAAkBA,CAAE,GAAK,qBAAqB,EAC/E,KAAK,gBAAgBA,CAAG,EACxB,IAAMyC,EAAQxE,EAAM,OAAO,CAAC,EACxBwE,IAAS,KAAK,MAAQ,CAAC,CAAE,QAASzC,EAAK,YAAayC,EAAM,GAAI,MAAO,CAAE,CAAC,EAAG,KAAK,MAAMA,EAAM,EAAE,EACpG,CACA,KAAK,OAAO,CACd,CAQA,MAAMzE,EAAkBoC,EAAwB,CAC9C,KAAK,MAAMpC,EAASoC,CAAO,CAC7B,CAmBA,KAAKnC,EAAeG,EAAyB,CAC3C,GAAI,KAAK,OAAQ,MAAO,GACxB,GAAIH,IAAU,MACZ,YAAK,QAAU,GAAM,KAAK,cAAgB,KAAM,KAAK,kBAAoB,KAAM,KAAK,qBAAuB,KAC3G,KAAK,cAAgB,KAAM,KAAK,UAAY,EAC5C,KAAK,UAAY,GAAM,KAAK,MAAQ,CAAC,EAC9B,GAGT,IAAMD,EAAU,KAAK,KAAK,gBAAgB,IAAIC,CAAK,IAAM,KAAK,KAAK,OAAO,OAAOA,CAAK,EAAIA,EAAQ,QAClG,GAAID,IAAY,OAAW,MAAO,GAClC,IAAIoC,EACJ,OAAIhC,IAAU,SACZgC,EAAU,KAAK,KAAK,gBAAgB,IAAIpC,CAAO,GAAG,IAAII,CAAK,IACrD,KAAK,KAAK,WAAW,IAAIA,CAAK,GAAG,UAAYJ,EAAUI,EAAQ,QACjEgC,IAAY,QAAkB,GAG/B,KAAK,SAEV,KAAK,cAAgB,KAAM,KAAK,kBAAoB,KAAM,KAAK,qBAAuB,KACtF,KAAK,cAAgB,KAAM,KAAK,UAAY,EAC5C,KAAK,UAAY,GACjB,KAAK,YAAYA,GAAWpC,EAAS,MAAM,EAC3C,KAAK,OAAO,EACL,KAPc,KAAK,MAAMA,EAASoC,CAAO,EAAU,GAQ5D,CAYA,OAAc,CACZ,KAAK,OAAS,GACd,KAAK,UAAY,GACjB,KAAK,MAAQ,CAAC,EACd,KAAK,cAAgB,KACrB,KAAK,UAAY,EACjB,KAAK,cAAgB,KACrB,KAAK,kBAAoB,KACzB,KAAK,qBAAuB,IAC9B,CAGA,IAAI,UAAoB,CAAE,OAAO,KAAK,MAAQ,CAK9C,IAAI,cAA8B,CAAE,OAAO,KAAK,cAAgB,CAGhE,SAAsB,CACpB,GAAI,KAAK,OAAQ,MAAO,CAAE,KAAM,KAAM,EACtC,GAAI,CAAC,KAAK,QAAS,MAAM,IAAI,MAAM,2BAA2B,EAE9D,GAAI,KAAK,kBAAmB,CAAE,IAAMW,EAAI,KAAK,kBAAmB,YAAK,kBAAoB,KAAM,KAAK,qBAAuB,KAAa,KAAK,WAAWA,CAAC,CAAG,CAE5J,OADA,KAAK,OAAO,EACR,KAAK,UAAkB,CAAE,KAAM,KAAM,EACrC,KAAK,cAAsB,CAAE,KAAM,SAAU,QAAS,KAAK,cAAc,QAAS,QAAS,KAAK,cAAc,OAAQ,EACrH,KAAK,cACH,KAAK,WAAW,KAAK,cAAc,MAAO,KAAK,WAAW,CAAE,GADxC,KAAK,UAAY,GAAa,CAAE,KAAM,KAAM,EAEzE,CAQA,eAAqC,CACnC,IAAM2B,EAAwC,CAAC,EAC/C,OAAS,CACP,IAAMC,EAAI,KAAK,QAAQ,EACvB,GAAIA,EAAE,OAAS,UAAYA,EAAE,OAAS,MAAO,MAAO,CAAE,OAAAD,EAAQ,KAAMC,CAAE,EACtED,EAAO,KAAKC,CAAC,CACf,CACF,CAQQ,QAAe,CACrB,IAAIC,EAAc,EAClB,OAAS,CAIP,GAAI,EAAEA,EAAc,IAClB,MAAM,IAAI,MAAM,+FAA+F,EAEjH,GAAI,KAAK,WAAa,KAAK,cAAe,OAE1C,GAAI,KAAK,cAAe,CACtB,GAAI,KAAK,WAAa,KAAK,cAAc,OAAO,QAAU,GAAI,OAC9D,KAAK,WAAW,KAAK,cAAc,MAAM,EACzC,IAAMC,EAAO,KAAK,cAAc,KAChC,KAAK,cAAgB,KACrB,KAAK,UAAY,EACjB,KAAK,YAAYA,CAAI,EACrB,QACF,CAEA,IAAMC,EAAQ,KAAK,MAAM,KAAK,MAAM,OAAS,CAAC,EAC9C,GAAI,CAACA,EAAO,CAAE,KAAK,UAAY,GAAM,MAAQ,CACzCA,EAAM,UAAY,KAAK,iBAAgB,KAAK,eAAiBA,EAAM,SACvE,IAAMC,EAAW,KAAK,WAAWD,EAAM,WAAW,EAClD,GAAI,CAACC,EAAU,CAAE,KAAK,MAAM,IAAI,EAAG,QAAU,CAC7C,KAAOD,EAAM,MAAQC,EAAS,QAAU,CAAC,KAAK,SAASA,EAASD,EAAM,KAAK,CAAE,GAAGA,EAAM,QACtF,GAAIA,EAAM,OAASC,EAAS,OAAQ,CAAE,KAAK,MAAM,IAAI,EAAG,QAAU,CAClE,KAAK,WAAWA,EAASD,EAAM,OAAO,CAAE,CAC1C,CACF,CAGA,YAA6B,CAC3B,OAAO,KAAK,eAAe,SAAW,CAAC,CACzC,CAGA,OAAO9C,EAAkB,CACvB,IAAMgD,EAAS,KAAK,cACpB,GAAI,CAACA,EAAQ,MAAM,IAAI,MAAM,sBAAsB,EACnD,IAAMC,EAASD,EAAO,QAAQ,KAAME,GAAMA,EAAE,KAAOlD,CAAE,EACrD,GAAI,CAACiD,EAAQ,MAAM,IAAI,MAAM,0BAA0BjD,CAAE,EAAE,EAC3D,GAAI,CAACiD,EAAO,SAAU,MAAM,IAAI,MAAM,kCAAkCjD,CAAE,EAAE,EAC5E,IAAMmD,EAAOH,EAAO,KAAK,IAAIhD,CAAE,EAC/B,KAAK,cAAgB,KAErB,KAAK,kBAAoB,KAAK,KAAK,qBAAuB,KAAK,aAAamD,CAAI,GAAK,KAAO,KAC5F,KAAK,qBAAuB,KAAK,kBAAoBA,EAAK,GAAK,KAG/D,KAAK,WAAWA,CAAI,CACtB,CAEA,SAAmB,CACjB,OAAO,KAAK,SACd,CAGA,YAAY3C,EAAsC,CAChD,GAAM,CAAE,MAAAa,EAAO,KAAAH,CAAK,EAAI,KAAK,SAASV,CAAG,EACzC,OAAIa,IAAU,SAAiB,KAAK,eAAe,IAAIH,CAAI,EACvDG,IAAU,QAAgB,KAAK,cAAc,IAAIH,CAAI,EAClD,KAAK,KAAK,OAAO,IAAIG,EAAOH,CAAI,CACzC,CAGA,YAAYV,EAAac,EAA0B,CACjD,GAAM,CAAE,MAAAD,EAAO,KAAAH,CAAK,EAAI,KAAK,SAASV,CAAG,EACzC,GAAIa,IAAU,SACZ,KAAK,eAAe,IAAKH,EAAMI,CAAK,UAC3BD,IAAU,QAAS,CAG5B,GAAI,KAAK,iBAAmB,KAAM,MAAM,IAAI,MAAM,IAAIb,CAAG,yCAAyC,EAClG,KAAK,cAAc,IAAKU,EAAMI,CAAK,CACrC,MACE,KAAK,KAAK,OAAO,IAAID,EAAOH,EAAMI,CAAK,CAE3C,CAKA,UAAyB,CACvB,MAAO,CACL,OAAQ,KAAK,MAAM,KAAK,EACxB,UAAW,OAAO,YAAY,CAAC,GAAG,KAAK,SAAS,EAAE,IAAI,CAAC,CAACpC,EAAG4C,CAAG,IAAM,CAAC5C,EAAG,CAAE,GAAG4C,CAAI,CAAC,CAAC,CAAC,EACpF,SAAU,KAAK,SACf,OAAQ,OAAO,YAAY,KAAK,WAAW,EAC3C,OAAQ,CACN,UAAW,KAAK,UAChB,eAAgB,KAAK,eAIrB,MAAO,KAAK,MAAM,IAAK7B,GAAM,CAC3B,IAAMH,EAAO,KAAK,WAAWG,EAAE,WAAW,IAAIA,EAAE,KAAK,EACrD,OAAOH,EAAO,CAAE,GAAGG,EAAG,OAAQH,EAAK,EAAG,EAAI,CAAE,GAAGG,CAAE,CACnD,CAAC,EACD,gBAAiB,KAAK,eAAe,IAAM,KAC3C,UAAW,KAAK,UAChB,cAAe,KAAK,cAChB,CAAE,QAAS,KAAK,cAAc,QAAS,QAAS,KAAK,cAAc,QAAQ,IAAKiD,IAAO,CAAE,GAAGA,CAAE,EAAE,CAAE,EAClG,KACJ,qBAAsB,KAAK,qBAC3B,UAAWrB,GAAmB,KAAK,SAAS,CAC9C,CACF,CACF,CAGA,QAAQK,EAA0B,CAChC,KAAK,SAAWA,EAAK,WAAa,EAClC,KAAK,YAAc,IAAI,IAAI,OAAO,QAAQA,EAAK,QAAU,CAAC,CAAC,CAAC,EAC5D,IAAMtE,EAAIsE,EAAK,OA2Bf,GA1BA,KAAK,QAAU,GACf,KAAK,UAAYtE,EAAE,UACnB,KAAK,UAAYA,EAAE,UACnB,KAAK,eAAiBA,EAAE,eAIxB,KAAK,MAAQA,EAAE,MAAM,IAAKqC,GAAM,CAC9B,GAAM,CAAE,OAAAmD,EAAQ,GAAGN,CAAM,EAAI7C,EAC7B,GAAImD,IAAW,OAAW,CACxB,IAAMC,EAAK,KAAK,WAAWP,EAAM,WAAW,GAAG,UAAWQ,GAAOA,EAAG,KAAOF,CAAM,GAAK,GACtF,GAAIC,GAAM,EAAG,MAAO,CAAE,GAAGP,EAAO,MAAOO,CAAG,CAC5C,CACA,MAAO,CAAE,GAAGP,CAAM,CACpB,CAAC,EAID,KAAK,UAAY,IAAI,IAAI,OAAO,QAAQZ,EAAK,WAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAChD,EAAG4C,CAAG,IAAM,CAAC5C,EAAG,CAAE,GAAG4C,CAAI,CAAC,CAAC,CAAC,EAChG,KAAK,MAAQ,KAAK,WAAW,EAC7B,KAAK,MAAM,KAAKI,EAAK,MAAM,EAK3B,KAAK,cAAgB,KACjBtE,EAAE,kBAAoB,KAAM,CAC9B,IAAMuF,EAAO,KAAK,KAAK,UAAU,IAAIvF,EAAE,eAAe,EAClDuF,GAAQA,EAAK,OAAS,YAAW,KAAK,cAAgBA,EAC5D,CASA,GAPA,KAAK,UAAYlB,GAAqBrE,EAAE,SAAS,EAMjD,KAAK,cAAgB,KACjBA,EAAE,gBAAkB,KAAM,CAC5B,IAAM2F,EAAO,IAAI,IACXnG,EAA0B,CAAC,EACjC,QAAW8F,KAAKtF,EAAE,cAAc,QAAS,CACvC,IAAMuF,EAAO,KAAK,KAAK,UAAU,IAAID,EAAE,EAAE,EACpCC,IACLI,EAAK,IAAIL,EAAE,GAAIC,CAAI,EACnB/F,EAAQ,KAAK,CAAE,GAAG8F,CAAE,CAAC,EACvB,CACI9F,EAAQ,OAAS,IAAG,KAAK,cAAgB,CAAE,QAASQ,EAAE,cAAc,QAAS,QAAAR,EAAS,KAAAmG,CAAK,EACjG,CAOA,GAFA,KAAK,kBAAoB,KACzB,KAAK,qBAAuB3F,EAAE,sBAAwB,KAClD,KAAK,qBAAsB,CAC7B,IAAM4F,EAAQ,KAAK,KAAK,UAAU,IAAI,KAAK,oBAAoB,EAC/D,KAAK,kBAAoBA,EAAQ,KAAK,aAAaA,CAAK,GAAK,KAAO,KAC/D,KAAK,oBAAmB,KAAK,qBAAuB,KAC3D,CACF,CAKQ,gBAAgBxF,EAAuB,CAC7C,IAAMC,EAAQ,KAAK,KAAK,OAAO,OAAOD,CAAO,EAC7C,GAAI,CAACC,EAAO,MAAM,IAAI,MAAM,kBAAkBD,CAAO,EAAE,EACvD,KAAK,eAAiBA,EACtB,KAAK,MAAMA,CAAO,EAClB,KAAK,UAAUC,CAAK,EACpB,KAAK,WAAWA,EAAM,OAAO,CAC/B,CASQ,WAAWkF,EAA4B,CAE7C,GADA,KAAK,MAAMA,EAAK,EAAE,EACdA,EAAK,OAAS,UAAW,CAAE,KAAK,aAAaA,CAAI,EAAG,MAAQ,CAChE,IAAMM,EAAWN,EAAK,UAAY,MAClC,GAAIM,IAAa,MAAO,CACtB,KAAK,MAAM,KAAK,CAAE,QAAS,KAAK,eAAiB,YAAaN,EAAK,GAAI,MAAO,CAAE,CAAC,EACjF,MACF,CACA,GAAIM,IAAa,SAAU,CAAE,KAAK,YAAYN,CAAI,EAAG,MAAQ,CAC7D,IAAMO,EAAO,KAAK,YAAYP,CAAI,EAC9BO,GAAM,KAAK,WAAWA,CAAI,CAChC,CAGQ,WAAWC,EAAmD,CACpE,IAAMvF,EAAQ,KAAK,KAAK,UAAU,IAAIuF,CAAW,EACjD,GAAIvF,EAAO,OAAOA,EAAM,SACxB,IAAM+E,EAAO,KAAK,KAAK,UAAU,IAAIQ,CAAW,EAChD,GAAIR,GAAQA,EAAK,OAAS,QAAS,OAAOA,EAAK,QAEjD,CAEQ,aAAaS,EAAgC,CACnD,KAAK,WAAWA,EAAQ,OAAO,EAC/B,KAAK,cAAgBA,EACrB,KAAK,UAAY,CACnB,CAEQ,YAAYC,EAA4B,CAC9C,IAAMzG,EAA0B,CAAC,EAC3BmG,EAAO,IAAI,IACXO,EAA8B,CAAC,EACrC,QAAWC,KAASF,EAAM,SAAU,CAIlC,GAAIE,EAAM,WAAa,GAAM,CAAED,EAAU,KAAKC,CAAK,EAAG,QAAU,CAKhE,GAAIA,EAAM,SAAW,KAAS,KAAK,YAAY,IAAIA,EAAM,EAAE,GAAK,IAAM,EAAG,SACzE,IAAMC,EAAW,KAAK,SAASD,CAAK,EAC9BE,EAASF,EAAM,sBAAwB,GACzC,CAACC,GAAYC,IACjB7G,EAAQ,KAAK,CAAE,GAAI2G,EAAM,GAAI,OAAQ,KAAK,UAAUA,CAAK,EAAG,SAAAC,EAAU,SAAUD,EAAM,QAAS,CAAC,EAChGR,EAAK,IAAIQ,EAAM,GAAIA,CAAK,EAC1B,CACA,GAAI3G,EAAQ,OAAS,EAAG,CAAE,KAAK,cAAgB,CAAE,QAASyG,EAAM,GAAI,QAAAzG,EAAS,KAAAmG,CAAK,EAAG,MAAQ,CAK7F,IAAMW,EAAWJ,EAAU,KAAM7D,GAAM,KAAK,SAASA,CAAC,CAAC,EACvD,GAAIiE,EAAU,CAAE,KAAK,WAAWA,CAAQ,EAAG,MAAQ,CAGnD,KAAK,KAAK,cAAcL,EAAM,EAAE,CAClC,CAIQ,YAAYhB,EAA8B,CAG3CA,GACL,KAAK,YAAYA,EAAK,GAAIA,EAAK,OAAS,OAAS,OAAS,MAAM,CAClE,CAQQ,YAAYsB,EAAYC,EAA6B,CAC3D,GAAID,IAAO,MAAO,CAAE,KAAK,UAAY,GAAM,KAAK,MAAQ,CAAC,EAAG,MAAQ,CAEpE,IAAInG,EACA2F,EACE1F,EAAQ,KAAK,KAAK,OAAO,OAAOkG,CAAE,EACxC,GAAIlG,EAAO,CACT,KAAK,gBAAgBkG,CAAE,EACvB,IAAM1B,EAAQxE,EAAM,OAAO,CAAC,EAC5B,GAAI,CAACwE,EAAO,CAAM2B,IAAS,SAAQ,KAAK,MAAQ,CAAC,GAAG,MAAQ,CAC5DpG,EAAUmG,EAAIR,EAAclB,EAAM,EACpC,KAAO,CACL,IAAMhF,EAAM,KAAK,KAAK,WAAW,IAAI0G,CAAE,EACvC,GAAI,CAAC1G,EAAK,MAAM,IAAI,MAAM,0BAA0B0G,CAAE,EAAE,EACpD1G,EAAI,UAAY,KAAK,gBAAgB,KAAK,gBAAgBA,EAAI,OAAO,EACzEO,EAAUP,EAAI,QAASkG,EAAcQ,CACvC,CAEA,KAAK,MAAMR,CAAW,EACtB,IAAMb,EAAoB,CAAE,QAAA9E,EAAS,YAAA2F,EAAa,MAAO,CAAE,EACvDS,IAAS,OAAQ,KAAK,MAAM,KAAKtB,CAAK,EACrC,KAAK,MAAQ,CAACA,CAAK,CAC1B,CAIQ,YAAYe,EAA6C,CAC/D,IAAMG,EAAWH,EAAM,SAAS,OAAQjG,GAAM,KAAK,SAASA,CAAC,CAAC,EAC9D,GAAIoG,EAAS,SAAW,EAAG,OAAO,KAClC,IAAMhC,EAAK,KAAK,cAAc6B,CAAK,EAEnC,OAAQA,EAAM,SAAU,CACtB,IAAK,SACH,OAAOG,EAAS,CAAC,EAEnB,IAAK,WAAY,CACf,IAAMK,EAAQR,EAAM,SAAS,OAAS,aAChCS,EAAUT,EAAM,SAAS,SAAW,OAC1C,OAAOQ,IAAU,UAAY,KAAK,YAAYL,EAAUM,EAAStC,CAAE,EAC/DqC,IAAU,cAAgB,KAAK,gBAAgBL,EAAUM,EAAStC,CAAE,EACpE,KAAK,eAAegC,EAAUM,EAAStC,CAAE,CAC/C,CAIA,QACE,OAAO,IACX,CACF,CAGQ,eAAegC,EAA4BM,EAAiBtC,EAA0C,CAC5G,IAAMuC,EAAMP,EAAS,OACf1F,EAAI0D,EAAG,KAAO,EAEpB,OADAA,EAAG,IAAM1D,EAAI,EACTgG,IAAY,SAAiBN,EAAS1F,EAAIiG,CAAG,EAC7CjG,EAAIiG,EAAYP,EAAS1F,CAAC,EAC1BgG,IAAY,QAAgBN,EAASO,EAAM,CAAC,EACzC,IACT,CAQQ,YAAYP,EAA4BM,EAAiBtC,EAA0C,CACzG,IAAMuC,EAAMP,EAAS,OACfQ,EAAQF,IAAY,QACpBG,EAAO,KAAiBD,EAAQR,EAAS,MAAM,EAAGO,EAAM,CAAC,EAAIP,GAAU,IAAKpG,GAAMA,EAAE,EAAE,EAG5F,GADIoE,EAAG,MAAQ,SAAWA,EAAG,IAAMyC,EAAK,GACpCzC,EAAG,IAAI,SAAW,EAAG,CACvB,GAAIsC,IAAY,OAAQ,OAAO,KAC/B,GAAIE,EAAO,CAAE,IAAME,EAAOV,EAASO,EAAM,CAAC,EAAI,OAAAvC,EAAG,KAAO0C,EAAK,GAAWA,CAAM,CAC9E1C,EAAG,IAAMyC,EAAK,CAChB,CAKA,IAAME,EAAO3C,EAAG,IACVvD,EAAIuD,EAAG,OAAS,QAAa2C,EAAK,OAAS,EAAIA,EAAK,QAAQ3C,EAAG,IAAI,EAAI,GACzE4C,EAAI,KAAK,MAAM,KAAK,IAAI,GAAKnG,GAAK,EAAIkG,EAAK,OAAS,EAAIA,EAAK,OAAO,EACpElG,GAAK,GAAKmG,GAAKnG,GAAGmG,IACtB,IAAM5E,EAAK2E,EAAKC,CAAC,EACjB,OAAAD,EAAK,OAAOC,EAAG,CAAC,EAChB5C,EAAG,KAAOhC,EACHgE,EAAS,KAAMpG,GAAMA,EAAE,KAAOoC,CAAE,CACzC,CAcQ,gBAAgBgE,EAA4BM,EAAiBtC,EAA0C,CAC7G,IAAI2C,EAAOX,EACX,GAAIM,IAAY,SAAU,CACpBtC,EAAG,MAAQ,SAAWA,EAAG,IAAMgC,EAAS,IAAKpG,GAAMA,EAAE,EAAE,GAC3D,IAAMiH,EAAY,IAAI,IAAI7C,EAAG,GAAG,EAEhC,GADA2C,EAAOX,EAAS,OAAQpG,GAAMiH,EAAU,IAAIjH,EAAE,EAAE,CAAC,EAC7C+G,EAAK,SAAW,EAClB,OAAOL,IAAY,SAAWtC,EAAG,OAAS,OACtCgC,EAAS,KAAMpG,GAAMA,EAAE,KAAOoE,EAAG,IAAI,GAAK,KAC1C,IAER,CAGA,IAAI8C,EAAO,GAELC,EADSJ,EAAK,IAAK/G,GAAM,CAAE,IAAMsB,EAAI,KAAK,UAAUtB,CAAC,EAAG,OAAIsB,EAAI4F,IAAMA,EAAO5F,GAAU,CAAE,EAAAtB,EAAG,EAAAsB,CAAE,CAAG,CAAC,EACpF,OAAQ8F,GAAMA,EAAE,IAAMF,CAAI,EAAE,IAAKE,GAAMA,EAAE,CAAC,EAI1DtB,EACJ,GAAIqB,EAAK,SAAW,EAClBrB,EAAOqB,EAAK,CAAC,MACR,CACL,IAAMtG,EAAIuD,EAAG,OAAS,OAAY+C,EAAK,UAAWnH,GAAMA,EAAE,KAAOoE,EAAG,IAAI,EAAI,GACxE4C,EAAI,KAAK,MAAM,KAAK,IAAI,GAAKnG,GAAK,EAAIsG,EAAK,OAAS,EAAIA,EAAK,OAAO,EACpEtG,GAAK,GAAKmG,GAAKnG,GAAGmG,IACtBlB,EAAOqB,EAAKH,CAAC,CACf,CAEA,OAAIN,IAAY,WAAUtC,EAAG,IAAMA,EAAG,IAAK,OAAQhC,GAAOA,IAAO0D,EAAK,EAAE,GACxE1B,EAAG,KAAO0B,EAAK,GACRA,CACT,CAIQ,UAAUP,EAA8B,CAC9C,OAAOA,EAAK,UAAY,KAAK,YAAY,KAAK,aAAaA,EAAK,SAAS,EAAG,EAAI,EAAI,CACtF,CAUQ,YAAYA,EAAgB8B,EAAuB,CAKzD,OAAOC,EAAiB/B,EADQ7E,GAAM6G,GAAOC,EAAS9G,EAAG,KAAK,QAAS+G,CAAa,CAAC,EAC3C,CAAE,KAAAJ,CAAK,CAAC,CACpD,CAGQ,cAAcpB,EAAqC,CACzD,IAAMyB,EAAMzB,EAAM,OAAS,KAAK,KAAK,gBAAkB,KAAK,UACxD7B,EAAKsD,EAAI,IAAIzB,EAAM,EAAE,EACzB,OAAK7B,IAAMA,EAAK,CAAC,EAAGsD,EAAI,IAAIzB,EAAM,GAAI7B,CAAE,GACjCA,CACT,CAIQ,WAAWuD,EAA6C,CAE9D,QAAWC,KAAKD,GAAW,CAAC,EAC1B,KAAK,YAAYC,EAAE,OAAQ,KAAK,SAASA,EAAE,KAAK,CAAC,CAErD,CAEQ,SAASrC,EAA+B,CAC9C,OAAKA,EAAK,UACHgC,GAAO,KAAK,SAAShC,EAAK,SAAS,CAAC,EADf,EAE9B,CAEQ,SAASsC,EAA+B,CAC9C,OAAOL,EAAS,KAAK,aAAaK,CAAI,EAAG,KAAK,QAASJ,CAAa,CACtE,CAIQ,aAAaI,EAA4B,CAC/C,IAAIC,EAAM1I,GAAS,IAAIyI,CAAI,EAC3B,OAAKC,IAAOA,EAAMC,EAAeF,EAAK,GAAG,EAAGzI,GAAS,IAAIyI,EAAMC,CAAG,GAC3DA,CACT,CAGQ,MAAM1F,EAAkB,CAC9B,KAAK,YAAY,IAAIA,GAAK,KAAK,YAAY,IAAIA,CAAE,GAAK,GAAK,CAAC,EAC5D,KAAK,KAAK,aAAa,IAAIA,GAAK,KAAK,KAAK,aAAa,IAAIA,CAAE,GAAK,GAAK,CAAC,CAC1E,CAGiB,IAAM,IAAc,CACnC,GAAI,KAAK,KAAK,UAAW,OAAO,KAAK,KAAK,UAAU,EACpD,IAAM4F,EAAK,KAAK,SAAW,WAAc,EACzC,KAAK,SAAWA,EAChB,IAAI,EAAI,KAAK,KAAKA,EAAKA,IAAM,GAAK,EAAIA,CAAC,EACvC,SAAK,EAAI,KAAK,KAAK,EAAK,IAAM,EAAI,GAAK,CAAC,EAAK,IACpC,EAAK,IAAM,MAAS,GAAK,UACpC,EAIQ,WAAW9E,EAAwB,CAGzC,IAAME,EAAO,KAAK,KAAK,SAAS,IAAIF,EAAK,EAAE,EACrC+E,EAAW7E,GAAQA,EAAK,OAAS,CAAE,KAAAA,CAAK,EAAI,CAAC,EAInD,OAAQF,EAAK,KAAM,CACjB,IAAK,YACH,MAAO,CAAE,KAAM,YAAa,GAAIA,EAAK,GAAI,SAAUA,EAAK,SAAU,GAAG+E,CAAS,EAChF,IAAK,OACH,MAAO,CAAE,KAAM,OAAQ,GAAI/E,EAAK,GAAI,KAAM,KAAK,YAAY,KAAK,cAAcA,EAAK,EAAE,CAAC,EAAG,SAAUA,EAAK,SAAU,GAAG+E,CAAS,EAChI,IAAK,OAAQ,CACX,IAAMC,EAAM,KAAK,cAAchF,EAAK,EAAE,EAKhCiF,EAAM,CAAC,KAAK,KAAK,WAEjBC,EADcD,GAAOjF,EAAK,YAAc,KAAK,KAAK,iBAC7B,GAAK,KAAK,YAAY,KAAK,KAAK,OAAO,OAASgF,EAAM,KAAK,YAAYA,CAAG,CAAC,EAChGG,EAASF,GAAOC,EAAK,SAAW,EACtC,MAAO,CACL,KAAM,OACN,GAAIlF,EAAK,GACT,KAAAkF,EACA,UAAWC,EAAS,OAAYnF,EAAK,UACrC,cAAemF,EAAS,OAAY,KAAK,qBAAqBnF,EAAK,SAAS,EAC5E,UAAWmF,EAAS,OAAYnF,EAAK,UACrC,SAAUA,EAAK,SACf,GAAG+E,CACL,CACF,CACF,CACF,CAOA,YAAYC,EAAqB,CAC/B,OAAOI,GAAYJ,EAAMtF,GAAQ,KAAK,YAAYA,CAAG,CAAC,CACxD,CAQA,cAAcsF,EAAqB,CACjC,OAAOK,GAAcL,EAAK,KAAK,KAAK,YAAa,KAAK,KAAK,YAAY,CACzE,CAIQ,YAAYE,EAAsB,CACxC,OAAO,KAAK,KAAK,WAAaA,EAAO,KAAK,cAAcA,CAAI,CAC9D,CAQQ,UAAU7C,EAAgD,CAChE,IAAMrC,EAAO,KAAK,aAAaqC,CAAI,EACnC,GAAI,CAACrC,EAAM,OACX,IAAMkF,EAAO,KAAK,YAAY,KAAK,cAAclF,EAAK,EAAE,CAAC,EAEzD,OAAOA,EAAK,OAAS,OACjB,CAAE,KAAM,OAAQ,KAAM,KAAK,YAAYkF,CAAI,EAAG,UAAWlF,EAAK,UAAW,cAAe,KAAK,qBAAqBA,EAAK,SAAS,EAAG,UAAWA,EAAK,SAAU,EAC7J,CAAE,KAAM,OAAQ,KAAAkF,CAAK,CAC3B,CAGQ,aAAa7C,EAAuD,CAC1E,OAAIA,EAAK,OAAS,SAAWA,EAAK,OAAeA,EAAK,SACtCA,EAAK,OAAS,UAAYA,EAAO,KAAK,mBAAmBA,EAAK,QAAQ,IACrE,OAAS,CAAC,GAAG,KAAMpC,GAAgCA,EAAE,OAAS,QAAUA,EAAE,OAAS,MAAM,CAC5G,CAGQ,mBAAmBgC,EAAyD,CAClF,IAAIqD,EACJ,OAAA/H,EAA0B0E,EAAWzE,GAAM,CACrC,CAAC8H,GAAS9H,EAAE,OAAS,YAAcA,EAAE,OAAS,CAAC,GAAG,KAAMyC,GAAMA,EAAE,OAAS,QAAUA,EAAE,OAAS,MAAM,IACtGqF,EAAQ9H,EAEZ,CAAC,EACM8H,CACT,CAEQ,cAAcpG,EAAoB,CACxC,GAAI,KAAK,KAAK,QAAS,OAAOA,EAC9B,IAAMqG,EAAS,KAAK,KAAK,QAAQrG,CAAE,EACnC,GAAIqG,IAAW,OAAW,OAAOA,EAIjC,IAAMjF,EAAS,KAAK,KAAK,eAAepB,CAAE,EAC1C,OAAOoB,IAAW,OAAY,kBAAkBpB,CAAE,KAAKoB,CAAM,GAAKpB,CACpE,CAKQ,qBAAqBsG,EAAmD,CAE9E,GADIA,IAAc,QACd,KAAK,KAAK,QAAS,OACvB,IAAM/D,EAAMpB,EAAcmF,CAAS,EACnC,OAAO,KAAK,KAAK,QAAQ/D,CAAG,GAAK,KAAK,KAAK,eAAeA,CAAG,GAAK,KAAK,KAAK,YAAY,IAAI+D,CAAS,CACvG,CAGQ,SAAS9F,EAA8C,CAE7D,IAAI+F,EAAM,KAAK,KAAK,cAAc,IAAI/F,CAAG,EACzC,OAAK+F,IAAOA,EAAM9E,EAASjB,EAAMkB,GAAMA,IAAM,SAAW,KAAK,KAAK,OAAO,IAAIA,CAAC,CAAC,EAAG,KAAK,KAAK,cAAc,IAAIlB,EAAK+F,CAAG,GAC/GA,CACT,CAGQ,YAA4B,CAClC,OAAO,IAAIxH,EAAc,EAAE,YAAY,SAAU,KAAK,KAAK,gBAAgB,CAC7E,CASQ,UAAUd,EAA4B,CAC5C,IAAMa,EAAS,KAAK,KAAK,iBAAiB,IAAIb,EAAM,EAAE,GAAK,IAAI,IAC/D,GAAI,CAAC,KAAK,UAAU,IAAIA,EAAM,EAAE,EAAG,CACjC,IAAM6D,EAAmC,CAAC,EAC1C,QAAW0E,KAAQvI,EAAM,YAAc,CAAC,EAAG,CACzC,IAAMiD,EAAOsF,EAAK,KAAK,YAAY,EAC9B1H,EAAO,IAAIoC,CAAI,IAAGY,EAAIZ,CAAI,EAAIuF,EAAaD,CAAI,EACtD,CACA,KAAK,UAAU,IAAIvI,EAAM,GAAI6D,CAAG,CAClC,CACA,GAAI,CAAC,KAAK,KAAK,UAAU,IAAI7D,EAAM,EAAE,EAAG,CACtC,IAAM6D,EAAmC,CAAC,EAC1C,QAAW0E,KAAQvI,EAAM,YAAc,CAAC,EAAG,CACzC,IAAMiD,EAAOsF,EAAK,KAAK,YAAY,EAC/B1H,EAAO,IAAIoC,CAAI,IAAGY,EAAIZ,CAAI,EAAIuF,EAAaD,CAAI,EACrD,CACA,KAAK,KAAK,UAAU,IAAIvI,EAAM,GAAI6D,CAAG,CACvC,CAIA,QAAW0E,KAAQvI,EAAM,YAAc,CAAC,EAAG,CACzC,GAAI,CAACuI,EAAK,UAAW,SACrB,IAAMtF,EAAOsF,EAAK,KAAK,YAAY,EAC7B1E,EAAMhD,EAAO,IAAIoC,CAAI,EAAI,KAAK,KAAK,UAAU,IAAIjD,EAAM,EAAE,EAAI,KAAK,UAAU,IAAIA,EAAM,EAAE,EAC1F6D,IAAKA,EAAIZ,CAAI,EAAIuF,EAAaD,CAAI,EACxC,CACF,CACF,EASA,SAAS3F,GAAY6F,EAA+C9F,EAAwB,CAC1FvC,EAA0BqI,EAAQpI,GAAM,CACtC,GAAIA,EAAE,OAAS,QAAS,CAClBA,EAAE,QAAQ,OAAS,QAAUA,EAAE,OAAO,WAAWsC,EAAI,IAAItC,EAAE,OAAO,SAAS,EAC/E,MACF,CACA,QAAWwC,KAAQxC,EAAE,OAAS,CAAC,EAAOwC,EAAK,OAAS,QAAUA,EAAK,WAAWF,EAAI,IAAIE,EAAK,SAAS,CACtG,CAAC,CACH,CAGA,SAASe,GAAmByD,EAAmE,CAC7F,IAAM1E,EAAwC,CAAC,EAC/C,OAAW,CAACZ,EAAIgC,CAAE,IAAKsD,EAAK,CAC1B,IAAMnD,EAAsB,CAAC,EACzBH,EAAG,MAAQ,SAAWG,EAAE,IAAMH,EAAG,KACjCA,EAAG,MAAKG,EAAE,IAAM,CAAC,GAAGH,EAAG,GAAG,GAC1BA,EAAG,OAAS,SAAWG,EAAE,KAAOH,EAAG,MACvCpB,EAAIZ,CAAE,EAAImC,CACZ,CACA,OAAOvB,CACT,CAGA,SAASqB,GAAqB0E,EAA+E,CAC3G,IAAMrB,EAAM,IAAI,IAChB,OAAW,CAACtF,EAAImC,CAAC,IAAK,OAAO,QAAQwE,GAAO,CAAC,CAAC,EAAG,CAC/C,IAAM3E,EAAoB,CAAC,EACvBG,EAAE,MAAQ,SAAWH,EAAG,IAAMG,EAAE,KAChCA,EAAE,MAAKH,EAAG,IAAM,CAAC,GAAGG,EAAE,GAAG,GACzBA,EAAE,OAAS,SAAWH,EAAG,KAAOG,EAAE,MACtCmD,EAAI,IAAItF,EAAIgC,CAAE,CAChB,CACA,OAAOsD,CACT,CAGA,SAAS5G,GAAO8H,EAAsC,CACpD,MAAO,CAAE,KAAMA,EAAK,KAAM,KAAMA,EAAK,KAAM,OAAQA,EAAK,OAAQ,OAAQA,EAAK,OAAQ,QAASA,EAAK,OAAQ,CAC7G,CAIA,SAASjF,GAAY1C,EAAkC,CACrD,GAAIA,EAAE,UAAY,OAAW,OAAOA,EAAE,QACtC,OAAQA,EAAE,KAAM,CACd,IAAK,SAAU,MAAO,GACtB,IAAK,SAAU,MAAO,GACtB,IAAK,QAAS,MAAO,CAAC,EACtB,IAAK,OAAQ,OAAOA,EAAE,SAAS,CAAC,GAAK,GACrC,IAAK,UAAW,OAAOA,EAAE,SAAS,CAAC,GAAK,GACxC,QAAS,MAAO,EAClB,CACF,CAGA,SAASO,GAAcoH,EAAuC,CAC5D,MAAO,CAAE,KAAMA,EAAK,KAAM,KAAMA,EAAK,KAAM,OAAQA,EAAK,OAAQ,OAAQA,EAAK,OAAQ,QAASA,EAAK,QAAS,SAAUA,EAAK,QAAS,CACtI,CAGA,SAASI,GAAiBJ,EAAkC,CAC1D,GAAIA,EAAK,UAAY,OAAW,OAAOA,EAAK,QAC5C,OAAQA,EAAK,KAAM,CACjB,IAAK,UAAW,MAAO,GACvB,IAAK,SAAU,MAAO,GACtB,IAAK,SAAU,MAAO,GACtB,IAAK,QAAS,MAAO,CAAC,EACtB,IAAK,OAAQ,OAAOA,EAAK,SAAS,CAAC,GAAK,GACxC,IAAK,UAAW,OAAOA,EAAK,SAAS,CAAC,GAAK,EAC7C,CACF,CAMA,SAASlH,GAAmBH,EAAuC,CAKjE,IAAMoD,EAAOrB,GAAyBA,EAAK,YAAY,EACjDY,EAAM,IAAI,IAChB,QAAWjD,KAAKM,EAAO2C,EAAI,IAAIS,EAAI1D,EAAE,IAAI,EAAG+H,GAAiB/H,CAAC,CAAC,EAC/D,MAAO,CACL,IAAMqC,GAASY,EAAI,IAAIS,EAAIrB,CAAI,CAAC,EAChC,IAAK,CAACA,EAAMI,IAAU,CAAEQ,EAAI,IAAIS,EAAIrB,CAAI,EAAGI,CAAK,CAAG,CACrD,CACF,CAGA,SAASmF,EAAaD,EAAiC,CACrD,GAAIA,EAAK,UAAY,OAAW,OAAOA,EAAK,QAC5C,OAAQA,EAAK,KAAM,CACjB,IAAK,UAAW,MAAO,GACvB,IAAK,SAAU,MAAO,GACtB,IAAK,SAAU,MAAO,GACtB,IAAK,QAAS,MAAO,CAAC,EACtB,IAAK,OAAQ,OAAOA,EAAK,SAAS,CAAC,GAAK,GACxC,IAAK,UAAW,OAAOA,EAAK,SAAS,CAAC,GAAK,EAC7C,CACF,CAEA,SAASrB,GAAOhD,EAAyB,CACvC,OAAI,OAAOA,GAAM,UAAkBA,EAC/B,OAAOA,GAAM,SAAiBA,IAAM,EACpC,OAAOA,GAAM,SAAiBA,IAAM,GACjCA,EAAE,OAAS,CACpB,CCxuDA,IAAM0E,GAAW,CAACC,EAAiBC,IACjCD,EAAE,QAAUC,EAEd,SAASC,EAAkBF,EAAiBC,EAAwC,CAClF,MAAO,CACL,KAAMD,EAAE,KACR,KAAMA,EAAE,KACR,WAAYA,EAAE,UAAY,OAC1B,GAAIA,EAAE,UAAY,OAAY,CAAE,QAASA,EAAE,OAAQ,EAAI,CAAC,EACxD,OAAQD,GAASC,EAAGC,CAAY,CAClC,CACF,CAEA,SAASE,GAAeC,EAAwC,CAC9D,MAAO,CACL,KAAMA,EAAE,KACR,KAAMA,EAAE,KACR,WAAYA,EAAE,UAAY,OAC1B,GAAIA,EAAE,OAAS,CAAE,OAAQ,CAAC,GAAGA,EAAE,MAAM,CAAE,EAAI,CAAC,EAC5C,GAAIA,EAAE,QAAU,CAAE,QAASA,EAAE,OAAQ,EAAI,CAAC,CAC5C,CACF,CAKA,SAASC,GAAWC,EAAsBC,EAA4B,CACpEA,EAAO,SACP,IAAMC,EAAgD,CAAC,GAAGF,EAAM,QAAQ,EACxE,KAAOE,EAAM,QAAQ,CACnB,IAAMC,EAAOD,EAAM,IAAI,EACvB,GAAIC,EAAK,OAAS,QAAS,CACzBF,EAAO,SACHE,EAAK,QAAQF,EAAO,UACxBC,EAAM,KAAK,GAAGC,EAAK,QAAQ,EAC3B,QACF,CACAF,EAAO,WACP,QAAWG,KAAQD,EAAK,OAAS,CAAC,EAChCF,EAAO,QACHG,EAAK,OAAS,aAAaH,EAAO,YAE1C,CACF,CASO,SAASI,GAAeC,EAAmC,CAChE,IAAML,EAAuB,CAC3B,OAAQ,EAAG,OAAQ,EAAG,OAAQ,EAAG,SAAU,EAAG,MAAO,EAAG,QAAS,EAAG,WAAY,EAChF,KAAMK,EAAO,MAAM,QAAU,CAC/B,EAEMC,EAA8B,CAAC,EAC/BC,EAAuC,CAAC,EAC9C,QAAWC,KAAS,OAAO,OAAOH,EAAO,MAAM,EAAG,CAChDL,EAAO,SACP,IAAMS,EAASC,EAAgBF,CAAK,EACpCF,EAAU,KAAK,CACb,OAAAG,EACA,KAAMD,EAAM,KACZ,OAAQA,EAAM,OAAO,IAAKG,IAAO,CAAE,OAAQD,EAAgBC,CAAC,EAAG,KAAMA,EAAE,IAAK,EAAE,CAChF,CAAC,EACD,QAAWZ,KAASS,EAAM,OAAQV,GAAWC,EAAOC,CAAM,EAEtDQ,EAAM,YAAY,QACpBD,EAAW,KAAK,CAAE,OAAAE,EAAQ,WAAYD,EAAM,WAAW,IAAKf,GAAME,EAAkBF,EAAG,EAAK,CAAC,CAAE,CAAC,CAEpG,CAEA,IAAMmB,GAAkCP,EAAO,eAAe,QAAU,CAAC,GAAG,IAAKQ,IAAO,CACtF,MAAOA,EAAE,MACT,SAAUA,EAAE,UAAY,GACxB,OAAQA,EAAE,eAAiB,OAG3B,YAAaA,EAAE,cAAgB,CAAC,GAAG,IAAKpB,GAAME,EAAkBF,EAAmB,EAAI,CAAC,CAC1F,EAAE,EAEIqB,EAA8B,OAAO,QAAQT,EAAO,gBAAkB,CAAC,CAAC,EAC3E,OAAO,CAAC,CAAC,CAAEU,CAAM,KAAOA,GAAQ,QAAU,GAAK,CAAC,EAChD,IAAI,CAAC,CAACC,EAAMD,CAAM,KAAO,CACxB,KAAMC,EACN,QAASD,GAAU,CAAC,GAAG,IAAInB,EAAc,CAC3C,EAAE,EAEJ,MAAO,CACL,SAAU,CACR,OAAQS,EAAO,OACf,QAASA,EAAO,QAAQ,QACxB,GAAIA,EAAO,QAAQ,UAAY,OAAY,CAAE,QAASA,EAAO,QAAQ,OAAQ,EAAI,CAAC,EAClF,GAAIA,EAAO,QAAQ,OAAS,OAAY,CAAE,KAAMA,EAAO,QAAQ,IAAK,EAAI,CAAC,EACzE,GAAIA,EAAO,QAAQ,gBAAkB,OAAY,CAAE,cAAeA,EAAO,QAAQ,aAAc,EAAI,CAAC,EACpG,OAAQA,EAAO,OACf,cAAeA,EAAO,QAAQ,QAC9B,QAAS,CAAC,GAAGA,EAAO,QAAQ,QAAQ,EAGpC,aAAcA,EAAO,cAAc,MAAQ,WAC3C,YAAaA,EAAO,cAAc,aAAe,EACnD,EACA,UAAAC,EACA,WAAAM,EACA,WAAY,CACV,QAASP,EAAO,YAAc,CAAC,GAAG,IAAKZ,GAAME,EAAkBF,EAAG,EAAI,CAAC,EACvE,MAAOc,CACT,EACA,SAAAO,EACA,OAAAd,CACF,CACF,CCpRO,SAASiB,GAAeC,EAAgBC,EAAyC,CACtF,OAAOD,EAAO,iBAAiBC,CAAI,GAAK,CAAC,CAC3C,CAIO,SAASC,EAAcC,EAAyBC,EAA4BC,EAAuB,CACxG,OAAID,GAAQ,OAAO,UAAU,eAAe,KAAKA,EAAMC,CAAI,EAAUD,EAAKC,CAAI,EACvEF,EAAO,KAAMG,GAAMA,EAAE,OAASD,CAAI,GAAG,OAC9C,CAIO,SAASE,GAAkBJ,EAAyBC,EAAsC,CAC/F,IAAMI,EAAgB,CAAC,EACvB,QAAWF,KAAKH,EAAQ,CACtB,IAAMM,EAAIP,EAAcC,EAAQC,EAAME,EAAE,IAAI,EACxCG,IAAM,SAAWD,EAAIF,EAAE,IAAI,EAAIG,EACrC,CACA,OAAW,CAACC,EAAGD,CAAC,IAAK,OAAO,QAAQL,GAAQ,CAAC,CAAC,EAASM,KAAKF,IAAMA,EAAIE,CAAC,EAAID,GAC3E,OAAOD,CACT","names":["src_exports","__export","Engine","Flow","buildTagIndex","describeBundle","effectiveGameData","gameDataFields","gameDataValue","deserialiseAst","node","args","EvalError","message","evaluate","node","ctx","dialect","missingPolicy","s","rec","n","scope","val","arg","ladder","ladderOf","current","stageIndex","def","l","r","left","right","lLadder","rLadder","sameLadder","valueEquals","assertNumbers","a","b","i","op","value","x","CHECK_FLAGS_COUNTING_CALL","node","DEFAULT_COUNTING_CALLS","matchedSpecificity","evalTruthy","opts","countingCalls","walk","want","l","r","rule","c","operands","holds","PropertyBag","_PropertyBag","declarations","opts","n","d","name","defaultFor","value","change","audit","fn","rowFor","c","k","values","v","writable","SAVE_FRAGMENT_VERSION","ScopeRegistry","token","bag","e","resolver","scopeWritable","decls","scope","out","row","host","scopes","qualities","m","properties","blob","vals","fragment","host","h","patterDialect","args","EvalError","next","a","b","lo","hi","flagsCall","flags","readFlags","i","arg","result","idx","idArg","nodeId","splitRef","ref","isScope","parts","nodeId","args","h","fn","v","EvalError","idArg","fnName","first","readFlags","arg","flagsCall","meta","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","dedupe","tags","seen","out","t","buildTagIndex","bundle","index","visit","node","inherited","acc","child","beat","scene","sceneAcc","block","blockAcc","astCache","Engine","_Engine","bundle","options","locale","allStrings","strings","defaultStrings","loc","emitIds","castDisplay","c","nodeIndex","blockIndex","blockById","sceneId","scene","effectiveGameId","blockAddrs","block","walkNodes","n","props","patterSharedDecls","p","toDecl","patterLocalDecls","patterSharedNames","d","shared","ScopeRegistry","hostBound","worldSpec","s","decls","toForeignDecl","spec","selfBackedResolver","sceneSharedNames","names","buildTagIndex","DEFAULT_CAPTION_DELIMITERS","DEFAULT_CAPTION_CHARACTER","snapshot","carryOver","next","fresh","id","f","on","opts","blockId","flow","Flow","existing","ref","beatId","sceneRef","blockRef","out","collectCast","beat","b","tags","info","name","castStringKey","source","scope","value","declDefault","split","splitRef","t","blob","flows","serialiseSelectors","bag","save","st","deserialiseSelectors","snap","v","host","seed","scopes","key","fromDecls","first","played","r","transitions","jump","frame","children","choice","option","o","node","nextId","at","ch","byId","owner","selector","pick","containerId","snippet","group","fallbacks","child","eligible","hidden","fallback","to","mode","order","exhaust","len","stick","fill","last","pool","i","remaining","best","tier","x","want","matchedSpecificity","truthy","evaluate","patterDialect","map","effects","e","expr","ast","deserialiseAst","a","withTags","raw","off","text","silent","interpolate","stripCaptions","found","active","character","hit","decl","sceneDefault","nodes","rec","hostScopeDefault","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"]}
|