@storylet-studio/play-helpers 1.0.1 → 1.0.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../../../../expr/packages/expr/src/own.ts","../../../../expr/packages/expr/src/ast.ts","../../../../expr/packages/expr/src/evaluate.ts","../../../../expr/packages/expr/src/prng.ts","../../../../expr/packages/expr/src/stages.ts","../../../../expr/packages/expr/src/values.ts","../../../../expr/packages/expr-specificity/src/index.ts","../../dialect/src/engine-scopes.ts","../../dialect/src/index.ts","../../../node_modules/@wildwinter/toolkit/src/ids.ts","../../../node_modules/@wildwinter/toolkit/src/gameid.ts","../../../node_modules/@wildwinter/toolkit/src/hash.ts","../../../node_modules/@wildwinter/toolkit/src/find.ts","../../model/src/index.ts","../../../../expr/packages/scoperegistry/src/state-logger.ts","../../../../expr/packages/scoperegistry/src/index.ts","../../runtime/src/engine.ts","../../runtime/src/version.ts","../../runtime/src/describe.ts","../../runtime/src/fields.ts","../src/logger.ts","../src/save.ts","../../../node_modules/@wildwinter/app-shell/src/tooltip.ts","../../../node_modules/@wildwinter/app-shell/src/dom.ts","../../../node_modules/@wildwinter/app-shell/src/value-control.ts","../src/inspector.ts","../src/bundle-inspector.ts","../src/live-link.ts","../src/refresh.ts","../src/world.ts"],"sourcesContent":["// The browser drop-in: ONE classic script, no build step, defining the\n// `StoryletEngine` global with the runtime AND these helpers on it, so a plain\n// page can build an engine, deal, play, and save and load the family's\n// .storyletsave text, all from two script tags (this and Patterplay's).\n//\n// Built by this package rather than the runtime because this is the one\n// package that depends on both: the runtime cannot bundle the helpers without\n// bundling itself twice. The export names of the two do not overlap (checked\n// 2026-09-04), so `export *` from each is exact.\nexport * from \"@storylet-studio/runtime\";\nexport * from \"./index.js\";\n","// ---------------------------------------------------------------------------\n// Own-property reads, for every lookup by a name an author wrote.\n//\n// The language's names come from authors (`@world.constructor`, a function\n// called `toString`), and a plain JavaScript object answers the names it\n// inherits from Object.prototype. Looked up with `record[name]`, the keyword\n// table turned `constructor` into a token that was not an identifier, a static\n// scope bag read a built-in function as a property's value, and a dialect's\n// function table offered `Object` as a function to call (October 2026 review).\n// Every such lookup goes through `own`, which sees a record's own entries only.\n// ---------------------------------------------------------------------------\n\n/** `record[key]` when the record has it as its OWN property, else undefined. */\nexport function own<T>(record: Readonly<Record<string, T>> | undefined, key: string): T | undefined {\n return record !== undefined && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;\n}\n","// ---------------------------------------------------------------------------\n// AST - the in-memory expression tree and its serialised tagged-tuple form.\n//\n// The in-memory `ExprNode` is a discriminated union (kind field). The published\n// `AstNode` is the compact tagged-tuple form that goes into a compiled bundle's\n// { src, ast } envelope - what a runtime walks, never parses.\n//\n// This module is dialect-agnostic: scope tokens and function names are plain\n// strings here; meaning is supplied by a Dialect (see dialect.ts).\n// ---------------------------------------------------------------------------\n\nexport type ScalarValue = boolean | number | string | string[];\n\nexport type BinaryOp =\n | \"==\" | \"!=\" | \">\" | \">=\" | \"<\" | \"<=\"\n | \"+\" | \"-\" | \"*\" | \"/\"\n | \"and\" | \"or\";\n\nexport type UnaryOp = \"not\" | \"neg\";\n\nexport type ExprNode =\n | { kind: \"bool\"; value: boolean }\n | { kind: \"number\"; value: number }\n | { kind: \"string\"; value: string }\n // All property references are scoped: bare `@name` is canonicalised to\n // `@<defaultScope>.name` at parse time. Names are lowercased at parse time.\n | { kind: \"scopedvar\"; scope: string; name: string }\n | { kind: \"call\"; name: string; args: ExprNode[] }\n | { kind: \"unary\"; op: UnaryOp; operand: ExprNode }\n | { kind: \"binary\"; op: BinaryOp; left: ExprNode; right: ExprNode }\n // Produced only by flag-delta function argument parsing (see Dialect\n // `flagDeltaArgs`) - not valid elsewhere.\n | { kind: \"flagdelta\"; sign: \"+\" | \"-\"; name: string };\n\n/**\n * Path into an ExprNode tree. Each segment names the field on the parent node,\n * with numeric indices for array elements (call args).\n * binary.left -> [\"left\"]\n * binary.right.args[0] -> [\"right\", \"args\", 0]\n * top-level node -> []\n */\nexport type AstPath = readonly (string | number)[];\n\n// ---------------------------------------------------------------------------\n// Published tagged-tuple form (JSON arrays, opcode at index 0).\n// ---------------------------------------------------------------------------\n\nexport type AstNode =\n | [\"b\", boolean]\n | [\"n\", number]\n | [\"s\", string]\n | [\"sv\", string, string]\n | [\"u\", UnaryOp, AstNode]\n | [\"bin\", BinaryOp, AstNode, AstNode]\n | [\"call\", string, ...AstNode[]]\n | [\"fd\", \"+\" | \"-\", string];\n\n/** In-memory ExprNode -> published tagged-tuple AstNode. */\nexport function serialiseAst(node: ExprNode): AstNode {\n switch (node.kind) {\n case \"bool\": return [\"b\", node.value];\n case \"number\": return [\"n\", node.value];\n case \"string\": return [\"s\", node.value];\n case \"scopedvar\": return [\"sv\", node.scope, node.name];\n case \"unary\": return [\"u\", node.op, serialiseAst(node.operand)];\n case \"binary\": return [\"bin\", node.op, serialiseAst(node.left), serialiseAst(node.right)];\n case \"call\": return [\"call\", node.name, ...node.args.map(serialiseAst)];\n case \"flagdelta\": return [\"fd\", node.sign, node.name];\n }\n}\n\n/** Published tagged-tuple AstNode -> in-memory ExprNode. */\nexport function deserialiseAst(node: AstNode): ExprNode {\n switch (node[0]) {\n case \"b\": return { kind: \"bool\", value: node[1] };\n case \"n\": return { kind: \"number\", value: node[1] };\n case \"s\": return { kind: \"string\", value: node[1] };\n case \"sv\": return { kind: \"scopedvar\", scope: node[1], name: node[2] };\n case \"u\": return { kind: \"unary\", op: node[1], operand: deserialiseAst(node[2]) };\n case \"bin\": return { kind: \"binary\", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };\n case \"call\": {\n const args = (node.slice(2) as AstNode[]).map(deserialiseAst);\n return { kind: \"call\", name: node[1], args };\n }\n case \"fd\": return { kind: \"flagdelta\", sign: node[1], name: node[2] };\n }\n}\n","// ---------------------------------------------------------------------------\n// Evaluator - walk an ExprNode against an EvalContext, parameterised by Dialect.\n//\n// Operators (binary/unary), short-circuiting, and type-checking are generic.\n// Scope resolution uses the context's scope maps + the Dialect's per-scope\n// missing-property policy. Function calls dispatch to the Dialect's functions.\n//\n// Ported from @storylets/engine (storylets/packages/engine/src/expression.ts),\n// generalised by injecting scopes + functions from the Dialect.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode, ScalarValue } from \"./ast.js\";\nimport type { Dialect, EvalContext, ScopeResolver } from \"./dialect.js\";\nimport { own } from \"./own.js\";\n\nexport class EvalError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EvalError\";\n }\n}\n\nexport function evaluate(node: ExprNode, ctx: EvalContext, dialect: Dialect): ScalarValue {\n // Per-scope missing-property policy, precomputed once per top-level evaluate.\n const missingPolicy = new Map<string, \"false\" | \"throw\">(\n dialect.scopes.map((s) => [s.token, s.missing ?? \"false\"])\n );\n\n const rec = (n: ExprNode): ScalarValue => {\n switch (n.kind) {\n case \"bool\": return n.value;\n case \"number\": return n.value;\n case \"string\": return n.value;\n\n case \"scopedvar\": {\n const scope = own(ctx.scopes, n.scope);\n if (scope === undefined) {\n // Scope context absent -> graceful false. (A scope the dialect knows\n // about but the context didn't populate, or an unknown scope.)\n return false;\n }\n // A scope is either a static bag or a host resolver ({ get }). Bag values\n // are always ScalarValue (never functions), so a `get` function reliably\n // distinguishes a resolver.\n const val = typeof (scope as ScopeResolver).get === \"function\"\n ? (scope as ScopeResolver).get(n.name)\n : own(scope as Record<string, ScalarValue>, n.name);\n if (val === undefined) {\n // Property not declared on the present scope. Policy decides: \"false\"\n // for back-compat scopes, \"throw\" for scopes where a missing key is a\n // bug publish-time validation should have caught.\n if (missingPolicy.get(n.scope) === \"throw\") {\n throw new EvalError(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);\n }\n return false;\n }\n return val;\n }\n\n case \"call\": {\n // `advance` is the language's own, the first core built-in: the next\n // stage in the argument's ladder, saturating at the last. Core rather\n // than dialect because it IS the quality design's insertion mechanism\n // (an outcome that never names its destination routes through an\n // inserted stage automatically), and every dialect should say it the\n // same way. A dialect that defines its own `advance` wins, for\n // back-compat with any dialect that already had one.\n if (n.name === \"advance\" && !own(dialect.functions, n.name)) {\n const arg = n.args[0];\n if (n.args.length !== 1 || arg === undefined) {\n throw new EvalError(`advance() takes exactly 1 argument, got ${n.args.length}`);\n }\n const ladder = ladderOf(arg, ctx);\n if (ladder === undefined) {\n throw new EvalError(\"advance() needs a quality reference (@scope.name of a quality property)\");\n }\n const current = stageIndex(rec(arg), ladder, \"advance\");\n return ladder[Math.min(current + 1, ladder.length - 1)]!;\n }\n const def = own(dialect.functions, n.name);\n if (!def) throw new EvalError(`unknown function '${n.name}'`);\n return def.eval(n.args, { evaluate: rec, ctx });\n }\n\n case \"flagdelta\":\n throw new EvalError(\"flagdelta node is only valid as an argument to a flag-delta function\");\n\n case \"unary\": {\n if (n.op === \"not\") {\n const val = rec(n.operand);\n if (typeof val !== \"boolean\") throw new EvalError(`'not' requires a boolean operand, got ${typeof val}`);\n return !val;\n }\n // neg\n const val = rec(n.operand);\n if (typeof val !== \"number\") throw new EvalError(`unary '-' requires a numeric operand, got ${typeof val}`);\n return -val;\n }\n\n case \"binary\": {\n // Short-circuit operators first\n if (n.op === \"and\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'and' requires boolean operands, left is ${typeof l}`);\n if (!l) return false;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'and' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n if (n.op === \"or\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'or' requires boolean operands, left is ${typeof l}`);\n if (l) return true;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'or' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n\n const left = rec(n.left);\n const right = rec(n.right);\n\n // Quality: when either operand REFERENCES a quality (the node carries\n // the scope+name the channel resolves), ordering compares by ladder\n // position and arithmetic is refused. Everything else is untouched,\n // so a context with no channel behaves exactly as before.\n const lLadder = ladderOf(n.left, ctx);\n const rLadder = ladderOf(n.right, ctx);\n const ladder = lLadder ?? rLadder;\n if (ladder !== undefined) {\n if (lLadder && rLadder && !sameLadder(lLadder, rLadder)) {\n if (n.op === \">\" || n.op === \">=\" || n.op === \"<\" || n.op === \"<=\") {\n throw new EvalError(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);\n }\n }\n switch (n.op) {\n case \">\": return stageIndex(left, ladder, \">\") > stageIndex(right, ladder, \">\");\n case \">=\": return stageIndex(left, ladder, \">=\") >= stageIndex(right, ladder, \">=\");\n case \"<\": return stageIndex(left, ladder, \"<\") < stageIndex(right, ladder, \"<\");\n case \"<=\": return stageIndex(left, ladder, \"<=\") <= stageIndex(right, ladder, \"<=\");\n case \"+\": case \"-\": case \"*\": case \"/\":\n throw new EvalError(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);\n default: break; // == and != fall through to plain value equality\n }\n }\n\n switch (n.op) {\n case \"==\": return valueEquals(left, right);\n case \"!=\": return !valueEquals(left, right);\n case \">\": assertNumbers(left, right, \">\"); return (left as number) > (right as number);\n case \">=\": assertNumbers(left, right, \">=\"); return (left as number) >= (right as number);\n case \"<\": assertNumbers(left, right, \"<\"); return (left as number) < (right as number);\n case \"<=\": assertNumbers(left, right, \"<=\"); return (left as number) <= (right as number);\n case \"+\":\n // Numbers only. Text joined by '+' fixes its word order in the\n // expression, so it cannot be translated: put the value in the text.\n if (typeof left === \"string\" || typeof right === \"string\") throw new EvalError(JOIN_TEXT);\n 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 assertNumbers(left, right, \"/\");\n if ((right as number) === 0) throw new EvalError(\"division by zero\");\n return (left as number) / (right as number);\n }\n }\n }\n };\n\n return rec(node);\n}\n\n/**\n * Equality for `==` / `!=`. Primitives compare by value (JS `===`); arrays\n * (the flags value type) compare as SETS: same members, ORDER IRRELEVANT.\n *\n * Plain `===` on arrays would be reference equality - two distinct arrays with\n * the same contents would never be equal, and a fresh array (from a scope read\n * or a function result) would never equal another. Mixed array/non-array\n * operands are unequal, and never an error.\n *\n * Order was significant until 2026-09-01, and that was wrong: a flags value IS\n * a set, and its stored order is an artefact of the order somebody happened to\n * add things in. `set_flags(@f, +a)` then `+b` compared UNEQUAL to the same two\n * flags added the other way round, which is a difference no author can see and\n * none intends. The Storylet Engine papered over it by sorting in `set_flags`,\n * which only holds while every producer sorts: a bundle's declared default, or\n * a host handing a list in, does not.\n *\n * Compared as MULTISETS (sorted copies), so a duplicated flag still counts. A\n * well-formed flags value has no duplicates, but equality should not be the\n * thing that decides what happens if one appears.\n */\nfunction valueEquals(a: ScalarValue, b: ScalarValue): boolean {\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n const x = [...a].sort();\n const y = [...b].sort();\n for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;\n return true;\n }\n return a === b;\n}\n\n/** Why '+' refuses a string. Word for word the same in every port and the checker. */\nexport const JOIN_TEXT = \"'+' cannot join text: put the value into the text itself, so it can be translated\";\n\nfunction assertNumbers(l: ScalarValue, r: ScalarValue, op: string): void {\n if (typeof l !== \"number\" || typeof r !== \"number\") {\n throw new EvalError(`'${op}' requires numeric operands, got ${typeof l} and ${typeof r}`);\n }\n}\n\n// --- quality (design: storylets-new/design/quality.md) -----------------------\n\n/** The ladder behind an operand NODE, when the context's quality channel says\n * it references a quality. Values are plain strings; the node is what carries\n * the (scope, name) the channel needs. */\nfunction ladderOf(node: ExprNode, ctx: EvalContext): readonly string[] | undefined {\n if (node.kind !== \"scopedvar\" || ctx.qualities === undefined) return undefined;\n return ctx.qualities(node.scope, node.name);\n}\n\n/** Index of a stage in a ladder; an unknown stage is an error naming the\n * value, never a silent pass (a drifted save is exactly what lands here). */\nfunction stageIndex(value: ScalarValue, ladder: readonly string[], op: string): number {\n if (typeof value !== \"string\") {\n throw new EvalError(`'${op}' on a quality compares stages, got ${typeof value}`);\n }\n const i = ladder.indexOf(value);\n if (i < 0) throw new EvalError(`\"${value}\" is not a stage of this quality (stages: ${ladder.join(\", \")})`);\n return i;\n}\n\nconst sameLadder = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((x, i) => x === b[i]);\n","// ---------------------------------------------------------------------------\n// mulberry32 - the contractual PRNG.\n//\n// A fixed, published algorithm that both product families need and neither\n// owns. It lived in `@storylet-studio/runtime` as prng.ts and inline in\n// `@patterkit/runtime`'s engine.ts, plus separately in three of Patterplay's\n// harnesses, plus a hand port in each of six native runtimes: thirteen copies\n// of forty lines. It belongs here, in the package both families already depend\n// on, for the same reason the evaluator does.\n//\n// All arithmetic is unsigned 32-bit, matching JavaScript's `>>> 0` and\n// Math.imul.\n// ---------------------------------------------------------------------------\n\nexport interface Prng {\n /** One draw in [0, 1); advances the state. */\n next(): number;\n /** The persisted state (a uint32; feed back into makePrng to restore). */\n state(): number;\n}\n\n/**\n * ECMA-262 7.1.6 ToUint32, which is what `seed >>> 0` means.\n *\n * Written out rather than spelled `seed >>> 0` because THIS is the obligation a\n * port in a language without JavaScript's shift semantics has to reproduce, and\n * the shorthand hides it. Patterplay's C++ port cast the seed straight to an\n * integer type, which is undefined behaviour outside that type's range and gave\n * two wrong answers before the parity corpus pinned them.\n */\nexport function toUint32(seed: number): number {\n if (Number.isNaN(seed) || !Number.isFinite(seed)) return 0;\n const modded = Math.trunc(seed) % 4294967296;\n return modded < 0 ? modded + 4294967296 : modded;\n}\n\n/**\n * Seed a generator. The state is UNSIGNED throughout (`>>> 0`, never `| 0`):\n * the two produce identical draws and differ only in the sign of the number a\n * save persists, and Patterplay shipped `| 0` for long enough that over half of\n * all its saves carried a negative state its own native ports could not read\n * back.\n */\nexport function makePrng(seed: number): Prng {\n let s = toUint32(seed);\n return {\n next(): number {\n s = (s + 0x6d2b79f5) >>> 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n },\n state(): number {\n return s;\n },\n };\n}\n\n/** The contractual shuffle: Fisher-Yates, descending. Runs of one element\n * consume no draws (the loop never executes for length < 2). */\nexport function shuffleInPlace<T>(arr: T[], prng: Prng): void {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(prng.next() * (i + 1));\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n }\n}\n","// ---------------------------------------------------------------------------\n// Quality ladders, outside an expression.\n//\n// The evaluator knows how to order a quality's stages; nothing else did. Every\n// validator that took a quality declaration wrote its own idea of a valid\n// ladder, and the two product families disagreed (Patter wanted two stages,\n// the Storylet compiler one). The rule is a fact about what a ladder is, so it\n// lives here, beside the evaluator that gives a ladder its meaning.\n//\n// Design: patterkit design/proposals/patter-qualities.md, sections 3 and 6.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode } from \"./ast.js\";\nimport type { Dialect } from \"./dialect.js\";\nimport { EvalError } from \"./evaluate.js\";\nimport { KEYWORD_NAMES, literalSpans, parse } from \"./parser.js\";\nimport { unparse } from \"./unparse.js\";\n\n/** One way a quality declaration breaks the rule. A caller words it. */\nexport type QualityDeclarationProblem =\n /** Fewer than two stages: a ladder with one rung has nothing to order. */\n | { kind: \"too-few-stages\"; count: number }\n /** A stage whose name is empty or only spaces, by its position. */\n | { kind: \"empty-stage\"; index: number }\n /** A stage listed again, once per repeat: \"which position?\" has no answer. */\n | { kind: \"duplicate-stage\"; stage: string }\n /** A default that is given but is not one of the stages. */\n | { kind: \"default-not-a-stage\"; value: unknown };\n\n/**\n * Check a quality declaration against the family's one rule: at least two\n * stages, none empty, none twice; a default is optional (pass `undefined` for\n * none), and when given it must be a stage. Returns the problems in ladder\n * order, then the default; an empty list means the declaration is sound.\n *\n * A host that requires a default checks that it is present itself: this rule\n * only says what a given one must be.\n */\nexport function qualityDeclarationProblems(\n stages: readonly string[] | undefined, defaultValue?: unknown,\n): QualityDeclarationProblem[] {\n const problems: QualityDeclarationProblem[] = [];\n const ladder = stages ?? [];\n if (ladder.length < 2) problems.push({ kind: \"too-few-stages\", count: ladder.length });\n const seen = new Set<string>();\n ladder.forEach((stage, index) => {\n if (typeof stage !== \"string\" || stage.trim() === \"\") {\n problems.push({ kind: \"empty-stage\", index });\n } else if (seen.has(stage)) {\n problems.push({ kind: \"duplicate-stage\", stage });\n } else {\n seen.add(stage);\n }\n });\n if (ladder.length > 0 && defaultValue !== undefined && (typeof defaultValue !== \"string\" || !seen.has(defaultValue))) {\n problems.push({ kind: \"default-not-a-stage\", value: defaultValue });\n }\n return problems;\n}\n\n// --- the ladder outside an expression -----------------------------------------\n//\n// A host holding a quality's value (a field, a game-data value, a property it\n// read) needs the order the evaluator uses, and each game was re-implementing\n// it. These three are that order, stated once (patter-qualities.md section 3).\n// Each takes a bare ladder or the declaration carrying one, so a host writes\n// `compareStages(fieldDecl, card.fields.act, current)`.\n\n/** A quality's ladder, or a declaration with `stages` (a field's, a\n * property's, a game-data field's). A declaration without them has none. */\nexport type StageLadder = readonly string[] | { readonly stages?: readonly string[] };\n\nconst ladderOf = (ladder: StageLadder): readonly string[] =>\n (Array.isArray(ladder) ? ladder : (ladder as { readonly stages?: readonly string[] }).stages ?? []);\n\nconst typeWord = (v: unknown): string =>\n (Array.isArray(v) ? \"flags\" : v === null || v === undefined ? \"nothing\" : typeof v);\n\n/** The position of a stage, or -1 when the value is not one of the stages. */\nexport function stageIndex(ladder: StageLadder, value: unknown): number {\n return typeof value === \"string\" ? ladderOf(ladder).indexOf(value) : -1;\n}\n\n/** The position of a stage, or the evaluator's own error when it is not one. */\nfunction positionOf(stages: readonly string[], value: unknown): number {\n if (typeof value !== \"string\") throw new EvalError(`a stage is a name, got ${typeWord(value)}`);\n const i = stages.indexOf(value);\n if (i < 0) throw new EvalError(`\"${value}\" is not a stage of this quality (stages: ${stages.join(\", \")})`);\n return i;\n}\n\n/** -1, 0 or 1 as `a` is below, at or above `b` on the ladder. An error when\n * either is not a stage: an unknown stage has no position to compare. */\nexport function compareStages(ladder: StageLadder, a: unknown, b: unknown): -1 | 0 | 1 {\n const stages = ladderOf(ladder);\n const d = positionOf(stages, a) - positionOf(stages, b);\n return d < 0 ? -1 : d > 0 ? 1 : 0;\n}\n\n/** One rung up, staying on the top rung, as `advance()` does. An error when\n * the value is not a stage (a drifted save, a deleted stage). */\nexport function nextStage(ladder: StageLadder, value: unknown): string {\n const stages = ladderOf(ladder);\n return stages[Math.min(positionOf(stages, value) + 1, stages.length - 1)]!;\n}\n\n/**\n * The stages either side of `stage`, with it, in ladder order: the one below,\n * the stage, the one above, each where the ladder has it. A coverage driver\n * proposes these for a gate such as `@q >= \"x\"`, so both sides of the gate are\n * played; an editor offers them as the stages nearest a value.\n *\n * A name that is not a stage has no position to be beside (a deleted stage\n * took its place with it), so the answer is then the whole ladder, in order:\n * only the author knows where such a value belongs now.\n */\nexport function stagesEitherSide(ladder: StageLadder, stage: unknown): string[] {\n const stages = ladderOf(ladder);\n const i = stageIndex(stages, stage);\n if (i < 0) return [...stages];\n return stages.slice(Math.max(0, i - 1), i + 2);\n}\n\n// --- a stage literal in an expression (editors) --------------------------------\n//\n// A quality's values are stored by stage NAME, so a rename is the one edit that\n// has to reach every literal naming the stage (patter-qualities.md section 2),\n// and a reorder the one that changes what a gate means without changing any\n// text. Both editors walk their own formats for the expressions; these say what\n// happens inside one.\n\n/** Does `(scope, name)`, read in an expression, name the quality in question? */\nexport type StageTargetRef = (scope: string, name: string) => boolean;\n\nconst COMPARISONS = new Set([\"==\", \"!=\", \"<\", \"<=\", \">\", \">=\"]);\nconst ORDERING = new Set([\"<\", \"<=\", \">\", \">=\"]);\nconst BARE_WORD = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Rewrite the stage literal `from` to `to` wherever `src` compares it with the\n * quality `isTarget` names, or, with `assigned`, where the whole expression is\n * that literal (an effect's value assigned to the quality). Undefined when\n * nothing changed or the source does not parse.\n *\n * The author's own text is kept where that says exactly the rewritten\n * expression: of the occurrences of `from` (quoted either way, or a bare word),\n * the set whose replacement parses to the rewritten tree. Every one first, as\n * that is the usual case, then each smaller choice, while there are few enough\n * to try; so a condition that also compares another quality with the same word\n * keeps that one as it was. Otherwise the parser's form of the rewritten tree.\n */\nexport function renameStageInExpression(\n src: string, dialect: Dialect, isTarget: StageTargetRef, from: string, to: string, assigned = false,\n): string | undefined {\n let ast: ExprNode;\n try { ast = parse(src, dialect); } catch { return undefined; }\n let hits = 0;\n const isRef = (n: ExprNode): boolean => n.kind === \"scopedvar\" && isTarget(n.scope, n.name);\n const walk = (n: ExprNode): ExprNode => {\n switch (n.kind) {\n case \"binary\": {\n let left = walk(n.left);\n let right = walk(n.right);\n if (COMPARISONS.has(n.op)) {\n if (isRef(left) && right.kind === \"string\" && right.value === from) { right = { kind: \"string\", value: to }; hits++; }\n if (isRef(right) && left.kind === \"string\" && left.value === from) { left = { kind: \"string\", value: to }; hits++; }\n }\n return { ...n, left, right };\n }\n case \"unary\": return { ...n, operand: walk(n.operand) };\n case \"call\": return { ...n, args: n.args.map(walk) };\n default: return n;\n }\n };\n let next = walk(ast);\n if (assigned && next.kind === \"string\" && next.value === from) { next = { kind: \"string\", value: to }; hits++; }\n if (hits === 0) return undefined;\n\n const wanted = JSON.stringify(next);\n const quotedTo = (q: string): string => `${q}${to.replace(/\\\\/g, \"\\\\\\\\\").replace(new RegExp(q, \"g\"), `\\\\${q}`)}${q}`;\n const bareTo = BARE_WORD.test(to) && !KEYWORD_NAMES.includes(to) ? to : quotedTo('\"');\n // The candidates are the tokeniser's: a string literal or a bare word that IS `from`, never text inside\n // another literal, so a long note mentioning the stage neither counts nor changes.\n const found = literalSpans(src).filter((t) => t.value === from)\n .map((t) => ({ at: t.at, length: t.end - t.at, text: t.quote !== undefined ? quotedTo(t.quote) : bareTo }));\n const says = (text: string): boolean => {\n try { return JSON.stringify(parse(text, dialect)) === wanted; } catch { return false; }\n };\n const replaced = (mask: number): string => {\n let text = \"\";\n let at = 0;\n found.forEach((f, i) => {\n if (!(mask & (1 << i))) return;\n text += src.slice(at, f.at) + f.text;\n at = f.at + f.length;\n });\n return text + src.slice(at);\n };\n if (found.length > 0 && found.length <= 8) {\n for (let mask = (1 << found.length) - 1; mask > 0; mask--) if (says(replaced(mask))) return replaced(mask);\n }\n return unparse(next, { defaultScope: dialect.defaultScope });\n}\n\n/**\n * How many gates in `src` order the quality `isTarget` names against something\n * (`<`, `<=`, `>`, `>=` with the quality on one side), counting each one: the\n * gates whose meaning a reorder of the ladder changes, though no value does.\n * Zero for a source that does not parse, which gates nothing.\n */\nexport function stageGateCount(src: string, dialect: Dialect, isTarget: StageTargetRef): number {\n let ast: ExprNode;\n try { ast = parse(src, dialect); } catch { return 0; }\n let gates = 0;\n const count = (n: ExprNode): void => {\n switch (n.kind) {\n case \"binary\":\n if (ORDERING.has(n.op)\n && [n.left, n.right].some((s) => s.kind === \"scopedvar\" && isTarget(s.scope, s.name))) gates++;\n count(n.left); count(n.right);\n return;\n case \"unary\": count(n.operand); return;\n case \"call\": n.args.forEach(count); return;\n default: return;\n }\n };\n count(ast);\n return gates;\n}\n","// ---------------------------------------------------------------------------\n// Does a value fit its declaration?\n//\n// Every place a typed value is held outside an expression asks it: a compiler\n// checking a field's value or a given default, a load checking a save written\n// against an older build (a stage, an enum value or a flag since deleted). Both\n// product families had written it, Storylets twice in its compiler and once in\n// its engine, with the vocabulary rule disagreeing between the copies. It is a\n// fact about the family's types, so it lives here, beside the ladder helpers.\n//\n// Design: patterkit design/proposals/patter-qualities.md, sections 1 and 7.\n// ---------------------------------------------------------------------------\n\n/** A declaration as this check reads it: a property's, a field's or a game-data\n * field's. `type` is the family's word (`text`, `multiline`, `number`,\n * `boolean`, `enum`, `flags`, `quality`, and a property's `string`). */\nexport interface ValueDecl {\n readonly type: string;\n /** An enum's values, or the flags a flags value may hold. */\n readonly values?: readonly string[];\n /** A quality's ladder. */\n readonly stages?: readonly string[];\n}\n\nexport interface ValueFitsOptions {\n /** An enum may hold `\"\"`, none chosen: an editor writes that for \"(none)\".\n * Only an enum; a quality is always on a stage. */\n readonly noneChosen?: boolean;\n}\n\n/**\n * Whether `value` fits `decl`: the type first, then the declaration's own\n * vocabulary. An enum value or a stage that is no longer listed is still a\n * string, and still no longer a legal value. A declaration that lists no\n * vocabulary (`values` or `stages` absent, or not a list: a declaration read\n * from JSON as it came) constrains nothing past the type, as in every port.\n *\n * A type this build does not know constrains nothing: a newer build's\n * declaration is the compiler's to report, not a reason to throw a value away.\n */\nexport function valueFits(decl: ValueDecl, value: unknown, opts?: ValueFitsOptions): boolean {\n switch (decl.type) {\n case \"boolean\": return typeof value === \"boolean\";\n case \"number\": return typeof value === \"number\";\n case \"string\":\n case \"text\":\n case \"multiline\": return typeof value === \"string\";\n case \"enum\":\n if (typeof value !== \"string\") return false;\n if (value === \"\" && opts?.noneChosen) return true;\n return !Array.isArray(decl.values) || decl.values.includes(value);\n case \"flags\":\n return Array.isArray(value)\n && value.every((f) => typeof f === \"string\" && (!Array.isArray(decl.values) || decl.values.includes(f)));\n case \"quality\":\n return typeof value === \"string\" && (!Array.isArray(decl.stages) || decl.stages.includes(value));\n default: return true;\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","// GENERATED - vendored from expr/family/engine-scopes.json by scripts/sync-conformance.mjs.\n// Do not edit here; edit the shared list and re-run the script.\n\n/** Every engine's game-wide scope token across the family. A compiler accepts every token here\n * that is not its own engine's, unchecked: the other engine owns those names and types. */\nexport interface EngineScope {\n token: string;\n engine: string;\n means: string;\n}\n\nexport const ENGINE_SCOPES: readonly EngineScope[] = [\n { token: \"patter\", engine: \"Patterplay\", means: \"Patter's shared globals\" },\n { token: \"story\", engine: \"Storylet Engine\", means: \"the Storylet Engine's shared @story properties\" },\n];\n","// ---------------------------------------------------------------------------\n// @storylet-studio/dialect - the storylets expression dialect.\n//\n// One dialect drives runtime eval AND publish-time validation (schema 6.1).\n// Five scopes, fixed (Reboot 3); bare `@name` is `@story.name` (schema 6.2,\n// resolved 2026-07-19). Function eval semantics carry from the old engine's\n// storylets-dialect.ts where the function survives (random, check_flags,\n// set_flags); the play-history functions are the new set pinned in schema 6.3.\n// ---------------------------------------------------------------------------\n\nimport { EvalError } from \"@wildwinter/expr\";\nimport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport type { EngineScope } from \"./engine-scopes.js\";\nimport type { Dialect, EvalHelpers, ExprNode, ScalarValue } from \"@wildwinter/expr\";\n\n/** turns_since_played / _in when the card / value has never been played. */\nexport const NEVER_PLAYED = 9999;\n\n/**\n * Host callbacks the dialect's functions read from `EvalContext.host`. The\n * runtime supplies these (its PRNG, its play log); the compiler validates\n * without them.\n */\nexport interface StoryletsHost {\n /** One PRNG draw in [0, 1) - the session mulberry32 (schema 3.3). */\n nextRandom(): number;\n /** Plays of the card (gameId) from the play log. */\n countPlayed(card: string): number;\n /** Turns since the card (gameId) last played; NEVER_PLAYED when never. */\n turnsSincePlayed(card: string): number;\n /** Plays of cards belonging to the dimension value (both by gameId). */\n countPlayedIn(dimension: string, value: string): number;\n /** As above; NEVER_PLAYED when never. */\n turnsSincePlayedIn(dimension: string, value: string): number;\n}\n\nconst host = (h: EvalHelpers): Partial<StoryletsHost> =>\n (h.ctx.host ?? {}) as Partial<StoryletsHost>;\n\nconst stringArg = (fn: string, args: ExprNode[], h: EvalHelpers, i: number): string => {\n const v = h.evaluate(args[i]!);\n if (typeof v !== \"string\" || v === \"\") {\n throw new EvalError(`${fn}() argument ${i + 1} must be a non-empty string`);\n }\n return v;\n};\n\n/** Resolve the first argument of check_flags / set_flags to a flag set. */\nconst flagsArg = (fn: string, args: ExprNode[], h: EvalHelpers): string[] => {\n if (args.length === 0) {\n throw new EvalError(`${fn}() requires at least one argument (the flags property)`);\n }\n const v = h.evaluate(args[0]!);\n if (Array.isArray(v)) return v as string[];\n // An unset flags property may surface as false; treat as the empty set\n // (carried from the old dialect). Anything else is a type error.\n if (v === false) return [];\n throw new EvalError(`${fn}() first argument must be a flags property`);\n};\n\n/** The Storylet Engine's own five scope tokens. */\nexport const OWN_SCOPES = [\"story\", \"world\", \"box\", \"deck\", \"hand\"] as const;\n\n/**\n * Other engines' game-wide scopes a card may name, from the family's shared list\n * (expr/family/engine-scopes.json): `@patter.gold` reads Patterplay's shared\n * globals in a game that runs both. On by default, with no project setting, so\n * combining the engines needs no wiring. The compiler lets these through\n * unchecked (the other engine owns its names and types), and the engine reports\n * at run time when the game has not registered one.\n */\nexport const EXTERNAL_SCOPES: readonly string[] = ENGINE_SCOPES\n .map((s) => s.token)\n .filter((t) => !(OWN_SCOPES as readonly string[]).includes(t));\n\nexport const storyletsDialect: Dialect = {\n // A missing property in a PRESENT scope is always an error: every property\n // is declared with a default, so absence means a publish bug, a drifted\n // save, or a foreign scope the host never fed (schema 6.2). The same holds\n // for another engine's scope: `@patter.glod` is a typo, and it says so the\n // first time the card is evaluated rather than quietly reading false.\n scopes: [\n ...OWN_SCOPES.map((token) => ({ token, missing: \"throw\" as const })),\n ...EXTERNAL_SCOPES.map((token) => ({ token, missing: \"throw\" as const })),\n ],\n defaultScope: \"story\",\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 nextRandom = host(h).nextRandom;\n if (!nextRandom) throw new EvalError(\"random() called without a PRNG in context\");\n const a = h.evaluate(args[0]!);\n const b = h.evaluate(args[1]!);\n if (typeof a !== \"number\" || typeof b !== \"number\") {\n throw new EvalError(\"random(a, b) arguments must be numbers\");\n }\n if (!Number.isInteger(a) || !Number.isInteger(b)) {\n throw new EvalError(\"random(a, b) arguments must be integers\");\n }\n const lo = Math.min(a, b);\n const hi = Math.max(a, b);\n return Math.floor(nextRandom() * (hi - lo + 1)) + lo;\n },\n },\n check_flags: {\n minArgs: 1, returnType: \"boolean\", flagDeltaArgs: true,\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const flags = flagsArg(\"check_flags\", args, h);\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") {\n throw new EvalError(\"check_flags() flag args must be +flagName or -flagName\");\n }\n if (arg.sign === \"+\" ? !flags.includes(arg.name) : flags.includes(arg.name)) {\n return false;\n }\n }\n return true;\n },\n },\n set_flags: {\n minArgs: 1, returnType: \"flags\", flagDeltaArgs: true,\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const result = [...flagsArg(\"set_flags\", args, h)];\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") {\n throw new EvalError(\"set_flags() flag args must be +flagName or -flagName\");\n }\n if (arg.sign === \"+\") {\n if (!result.includes(arg.name)) result.push(arg.name);\n } else {\n const idx = result.indexOf(arg.name);\n if (idx >= 0) result.splice(idx, 1);\n }\n }\n // Sorted so a SAVE is deterministic: the same flags reached by different routes\n // serialise to the same bytes, which keeps save diffs and cross-runtime byte\n // comparisons stable. It is no longer what makes equality work - flags compare\n // as a SET since 2026-09-01 - so this is now about the stored form only, and\n // Patterplay not sorting is a difference that costs nothing.\n return result.sort();\n },\n },\n count_played: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const card = stringArg(\"count_played\", args, h, 0);\n const fn = host(h).countPlayed;\n if (!fn) throw new EvalError(\"count_played() called without a play log in context\");\n return fn(card);\n },\n },\n turns_since_played: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const card = stringArg(\"turns_since_played\", args, h, 0);\n const fn = host(h).turnsSincePlayed;\n if (!fn) throw new EvalError(\"turns_since_played() called without a play log in context\");\n return fn(card);\n },\n },\n count_played_in: {\n minArgs: 2, maxArgs: 2, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const dimension = stringArg(\"count_played_in\", args, h, 0);\n const value = stringArg(\"count_played_in\", args, h, 1);\n const fn = host(h).countPlayedIn;\n if (!fn) throw new EvalError(\"count_played_in() called without a play log in context\");\n return fn(dimension, value);\n },\n },\n turns_since_played_in: {\n minArgs: 2, maxArgs: 2, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const dimension = stringArg(\"turns_since_played_in\", args, h, 0);\n const value = stringArg(\"turns_since_played_in\", args, h, 1);\n const fn = host(h).turnsSincePlayedIn;\n if (!fn) throw new EvalError(\"turns_since_played_in() called without a play log in context\");\n return fn(dimension, value);\n },\n },\n },\n};\n\n/**\n * The dialect, also accepting the game-wide scopes `tokens` names: every token a game's\n * shared scopes folder declares (`game-scopes/`, patterkit design/shared-scopes.md), so a\n * game's own scope such as `@player` parses where the shared vocabulary alone would not. A\n * token the dialect already has keeps its place and its policy; each new one gets the policy\n * another engine's scope has, since whoever declared it owns its names. With nothing new to\n * add this is `storyletsDialect` itself, so a project with no shared folder compiles exactly\n * as before. For editing and publishing only: a runtime evaluates a compiled bundle, which\n * names its scopes already.\n */\nexport function storyletsDialectWith(tokens: readonly string[]): Dialect {\n const known = new Set(storyletsDialect.scopes.map((s) => s.token));\n const added = [...new Set(tokens)].filter((t) => !known.has(t));\n if (added.length === 0) return storyletsDialect;\n return {\n ...storyletsDialect,\n scopes: [...storyletsDialect.scopes, ...added.map((token) => ({ token, missing: \"throw\" as const }))],\n };\n}\n","// ---------------------------------------------------------------------------\n// Stable, immutable, opaque ids.\n//\n// Generated at creation, never derived from content or position, so an id\n// survives moving, renaming and reordering the thing it names. A short,\n// collision-resistant base-36 token with an optional type prefix that aids\n// debugging but carries no meaning.\n// ---------------------------------------------------------------------------\n\nconst ALPHABET = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n\n/** Generate a new opaque id, e.g. `newId(\"scn\") -> \"scn_8f3kq2z1\"`. */\nexport function newId(prefix = \"\", length = 8): string {\n // Rejection-sample so every alphabet character is equally likely: a plain\n // `byte % 36` over-weights the first four characters.\n const limit = 256 - (256 % ALPHABET.length); // 252\n let token = \"\";\n while (token.length < length) {\n const bytes = new Uint8Array(length * 2);\n globalThis.crypto.getRandomValues(bytes);\n for (const b of bytes) {\n if (b >= limit) continue;\n token += ALPHABET[b % ALPHABET.length];\n if (token.length === length) break;\n }\n }\n return prefix ? `${prefix}_${token}` : token;\n}\n\n/** A url-ish slug from a human label; `\"project\"` when nothing usable is left. */\nexport function slug(name: string, fallback = \"project\"): string {\n const s = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n return s || fallback;\n}\n","// ---------------------------------------------------------------------------\n// Game IDs: the author-editable, host-facing address a game uses for a thing\n// (a Patter scene or block, a Storylet box, deck, card or outcome), distinct\n// from the opaque immutable id. A hyphen-slug, derived from the thing's name\n// until an author pins one.\n//\n// Both families had this rule word for word (Patter's model, and Storylets',\n// adopted from Patter's on 2026-07-20), and Storyletter matches a card to its\n// Patter scene by it, so the two copies must never drift. What each family\n// derives an address FROM (a name; a title, else the id) stays with the family.\n// ---------------------------------------------------------------------------\n\n/**\n * Turn a name into a Game ID: lower case, apostrophes (straight and curly) dropped,\n * every run of anything but a-z, 0-9 and hyphens made one hyphen, repeated hyphens\n * collapsed, and none at either end. Can be empty (`\"!!!\"`, or a name with no ASCII\n * letters or digits); a caller falls back as it sees fit.\n */\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: lower-case letters, digits and hyphens, with no hyphen at either end. */\nexport function isValidGameId(gameId: string): boolean {\n return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);\n}\n","// ---------------------------------------------------------------------------\n// FNV-1a, 32-bit.\n//\n// A published, fixed algorithm. Not cryptographic: it is for short stable\n// handles and change detection, where the properties that matter are that it is\n// the same everywhere and does not move between runs.\n// ---------------------------------------------------------------------------\n\n/** FNV-1a over a string's UTF-16 code units, as an unsigned 32-bit integer. */\nexport function fnv32(input: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n","// ---------------------------------------------------------------------------\n// Find and replace over authored text.\n// ---------------------------------------------------------------------------\n\nexport interface FindOptions {\n query: string;\n caseSensitive?: boolean;\n wholeWord?: boolean;\n}\n\n/**\n * Build the find matcher, or null for an empty query. Global, so every\n * occurrence in a string is replaced rather than only the first.\n *\n * The query is escaped: a person typing `a.b` means those three characters, not\n * \"a, anything, b\".\n */\nexport function findMatcher(opts: FindOptions): RegExp | null {\n if (!opts.query) return null;\n const esc = opts.query.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const body = opts.wholeWord ? `\\\\b${esc}\\\\b` : esc;\n return new RegExp(body, opts.caseSensitive ? \"g\" : \"gi\");\n}\n\n// ---------------------------------------------------------------------------\n// Find by field value: the Field tab of both families' Find windows\n// (patterkit design/family-parity-2026-10.md item 16). Each app walks its own\n// project for the values (Storylets' card and outcome fields, Patter's\n// gameData) and asks these two what a query means, so `mood = tense` reads the\n// same in both.\n// ---------------------------------------------------------------------------\n\n/** A parsed Field query: a field, and optionally a value it must (`=`) or must\n * not (`!=`) hold. With no `op` the query lists every value of the field. */\nexport interface FieldQuery {\n /** As typed, folded to lower case: `mood`, or `extension.field`. */\n field: string;\n op?: \"=\" | \"!=\";\n /** As typed, trimmed, one pair of surrounding quotes taken off. May be `\"\"`\n * (`mood =` asks for the field left empty). */\n value?: string;\n}\n\n/**\n * `mood`, `mood = tense`, `mood != tense`, `ext.field = \"two words\"`. Null when\n * the text before the operator is not a field name (a letter or `_`, then\n * letters, digits, `_` or `-`; up to three of those joined by dots).\n */\nexport function parseFieldQuery(query: string): FieldQuery | null {\n const m = /^\\s*([a-z_][\\w-]*(?:\\.[a-z_][\\w-]*){0,2})\\s*(?:(!=|=)\\s*(.*?))?\\s*$/i.exec(query);\n if (!m) return null;\n const field = m[1]!.toLowerCase();\n if (m[2] === undefined) return { field };\n let value = m[3]!;\n const q = /^([\"'])(.*)\\1$/.exec(value);\n if (q) value = q[2]!;\n return { field, op: m[2] as \"=\" | \"!=\", value };\n}\n\n/** Does this field name answer the query's? Names compare without case. */\nexport const fieldNameMatches = (name: string, query: FieldQuery): boolean => name.toLowerCase() === query.field;\n\n/**\n * Does a field's value satisfy the query, read by its type (the family's list:\n * text, multiline, number, boolean, enum, flags, quality; `string` is text)?\n * Pass the value that HOLDS: the default when the node sets none, so\n * `mood = calm` finds the cards that leave a calm mood alone.\n *\n * - text, multiline, enum, quality, and any type this does not know: equal,\n * ignoring case and the ends' whitespace\n * - flags: the list includes that flag (`= \"\"` is no flags set)\n * - number: numerically equal (`= 1.0` finds 1)\n * - boolean: `true` or `false`, ignoring case\n *\n * `!=` is the opposite of `=`. A query with no operator matches any value.\n */\nexport function fieldValueMatches(type: string, value: unknown, query: FieldQuery): boolean {\n if (query.op === undefined || query.value === undefined) return true;\n const holds = valueIs(type, value, query.value);\n return query.op === \"=\" ? holds : !holds;\n}\n\nfunction valueIs(type: string, value: unknown, wanted: string): boolean {\n const want = wanted.trim().toLowerCase();\n if (type === \"flags\") {\n const flags = Array.isArray(value) ? value.map((f) => String(f).toLowerCase()) : [];\n return want === \"\" ? flags.length === 0 : flags.includes(want);\n }\n if (type === \"number\") {\n if (want === \"\") return value === undefined || value === null || value === \"\";\n const n = Number(want);\n return !Number.isNaN(n) && typeof value === \"number\" && value === n;\n }\n if (type === \"boolean\") return (value === true && want === \"true\") || (value === false && want === \"false\");\n return fieldValueText(type, value).trim().toLowerCase() === want;\n}\n\n/** A field's value as a row shows it: flags joined with commas, everything\n * else as written, nothing for no value. */\nexport function fieldValueText(type: string, value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (type === \"flags\" && Array.isArray(value)) return value.join(\", \");\n return typeof value === \"string\" ? value : JSON.stringify(value);\n}\n","// ---------------------------------------------------------------------------\n// @storylet-studio/model - the shape source-of-truth.\n//\n// Transcribes design/storylets-schema.md (bundle, save) and\n// design/storylets-source.md (shards). Entity shapes are generic over their\n// expression representation E: source shards use plain `src` strings\n// (Card<string>), the compiled bundle uses { src, ast } envelopes\n// (Card<Expression>). No behaviour lives here.\n// ---------------------------------------------------------------------------\n\nimport type { Expression, ScalarValue } from \"@wildwinter/expr\";\nimport { gameIdify, isValidGameId } from \"@wildwinter/toolkit\";\n\nexport type { Expression, ScalarValue, AstNode } from \"@wildwinter/expr\";\n\n// --- shared declarations -----------------------------------------------------\n\nexport type PropertyType = \"boolean\" | \"number\" | \"string\" | \"enum\" | \"flags\" | \"quality\";\n\n/** A property declaration: @world / @story / @box / @deck / tag / hand\n * state. A declared property always has a value; referencing an undeclared\n * property is a publish-time error. */\nexport interface PropertyDecl {\n name: string;\n type: PropertyType;\n /** Optional in source: left out, the type's default holds (false, 0, \"\",\n * the first enum value, no flags, the first stage), by the kernel's\n * `defaultFor`. The compiler writes the resolved default onto every\n * declaration in the bundle, so a bundle's declaration always has one. */\n default?: ScalarValue;\n values?: string[];\n /**\n * A quality's ordered ladder of stage names (design/quality.md). Order IS\n * the meaning: `>=` compares by position here, and `advance()` steps along\n * it. The one order-semantic list in the format, accepted as such: it is a\n * declaration, and inserting a stage mid-ladder is the design's whole point.\n */\n stages?: string[];\n /**\n * Host scopes only (`@world`, `@player`, ...; see `HostScopeDecl`). `false`\n * makes the property read-only TO THE STORY: a condition may read it, an\n * outcome that writes it is a compile error. The game still moves it through\n * its resolver; this is the story's statement of intent, not the game's\n * policy. Mirrors Patter's `HostScopeDecl.writable` name for name (Reboot.md\n * 10, ruled 2026-09-03). Ignored on every other scope. Absent = writable.\n */\n writable?: boolean;\n /**\n * The sharing axis (design/flows.md, Patter's flag adopted): is this\n * property's value one world value across all flows, or a copy per flow?\n * It does NOT change reference syntax - sharing is set here, on the\n * declaration, not by a different scope token. Absent = the scope\n * default: `@story` shared; box, deck, hand and tag properties per-flow.\n * On a `@world` declaration the flag is a validation error - `@world` is\n * the game's own state, always engine-level, never per-flow.\n */\n shared?: boolean;\n /**\n * The durability axis (design/engine-server.md 4.2), valid wherever `shared`\n * is valid and orthogonal to it: `shared` says whose value this is WITHIN a\n * run, `durable` says whether the value survives the run at all. A durable\n * shared property is the installation's memory (\"trolls defeated since we\n * opened\"); a durable per-flow one is the player's pocket (visits,\n * allegiance, what they earned).\n *\n * The engine partitions by `shared` alone; this flag only decides what the\n * durable halves carry across a run boundary: `engine.saveDurable()` and\n * `loadDurable` for a shared one, `flow.saveDurable()` and `openFlow`'s\n * `durable` for a per-flow one.\n *\n * On a `@world` declaration the flag is a validation error, for the reason\n * `shared` is: @world is the game's own state, and how long the game keeps it\n * is the game's business.\n */\n durable?: boolean;\n purpose?: string;\n}\n\n/**\n * A property of a host scope (`@world`, `@player`, ...): the game's own state,\n * declared by the project. The same shape as Patter's `HostScopeDecl` and the\n * `@wildwinter/scoperegistry` `ScopeDeclaration`. `default` seeds the\n * standalone engine's self-backed bag; `writable: false` makes the property\n * read-only to the story (an outcome may not write it), never to the game.\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). */\n purpose?: string;\n}\n\n/** One scope (`token`) in a project's host-scope registry. Patter's shape. */\nexport interface HostScopeSpec {\n /** The scope token after `@` (`\"world\"`, `\"player\"`). Never one of the\n * Storylet Engine's own (`story`, `box`, `deck`, `hand`). */\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/** A project's host-scope registry: every scope the game provides itself.\n * Structurally scoperegistry's `ScopeRegistrySpec`, and Patter's. */\nexport interface HostScopeRegistry {\n version: number;\n scopes: HostScopeSpec[];\n}\n\n/** The `world` block of a bundle @1 or @0, and of a project shard before\n * host scopes: @world's declarations, the one game scope there was. */\nexport interface LegacyWorldBlock {\n properties: PropertyDecl[];\n /** Never read. */\n registry?: unknown;\n}\n\n/**\n * The host scopes a bundle or a project shard declares, in order: its\n * `scopeRegistry`'s, or, for a bundle @1 or @0 and a project not yet\n * formatted, its `world` block read as the scope `world` (none when that block\n * declares nothing, which is what an empty `world` always meant).\n */\nexport function bundleHostScopes(of: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }): HostScopeSpec[] {\n if (of.scopeRegistry !== undefined) return of.scopeRegistry.scopes;\n const legacy = of.world?.properties;\n return legacy !== undefined && legacy.length > 0 ? [{ token: \"world\", declarations: legacy }] : [];\n}\n\n/** One host scope by token, case-sensitive, from `bundleHostScopes`. */\nexport function hostScopeSpec(of: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }, token: string): HostScopeSpec | undefined {\n return bundleHostScopes(of).find((s) => s.token === token);\n}\n\n/** A host scope's declarations by token (empty when it declares none, is\n * opaque, or isn't there). `hostScopeDecls(bundle, \"world\")` is what\n * `bundle.world.properties` was. */\nexport function hostScopeDecls(of: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }, token: string): HostScopeDecl[] {\n return hostScopeSpec(of, token)?.declarations ?? [];\n}\n\n/**\n * A project shard with one host scope's declarations replaced (the scope\n * added, last, when it is new), in place: the legacy `world` block moves into\n * `scopeRegistry` first, so a shard is never written with both. The scope's\n * own `writable` is kept.\n */\nexport function setHostScopeDecls(shard: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }, token: string, declarations: HostScopeDecl[]): void {\n migrateLegacyWorld(shard);\n const registry = shard.scopeRegistry ?? { version: 1, scopes: [] };\n const at = registry.scopes.findIndex((s) => s.token === token);\n if (at >= 0) registry.scopes[at] = { ...registry.scopes[at]!, declarations };\n else registry.scopes.push({ token, declarations });\n shard.scopeRegistry = registry;\n}\n\n/**\n * Move a project shard's legacy `world` block into `scopeRegistry`, in place\n * (what `storyletengine format` does): `world.properties` becomes the scope\n * `world`, its `shared` and `durable` flags dropped, since neither means\n * anything on a game's scope. An empty block just goes. Returns whether\n * anything moved.\n */\nexport function migrateLegacyWorld(shard: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }): boolean {\n if (shard.world === undefined) return false;\n const properties = shard.world.properties ?? [];\n delete shard.world;\n if (properties.length === 0 || shard.scopeRegistry?.scopes.some((s) => s.token === \"world\")) return true;\n const declarations: HostScopeDecl[] = properties.map((d) => {\n const { shared: _shared, durable: _durable, ...rest } = d;\n return rest;\n });\n shard.scopeRegistry = {\n version: shard.scopeRegistry?.version ?? 1,\n scopes: [{ token: \"world\", declarations }, ...(shard.scopeRegistry?.scopes ?? [])],\n };\n return true;\n}\n\n/**\n * A field's type: the family's one list, shared with Patter's game data\n * (patterkit design/family-parity-2026-10.md item 9). `text` is the spelling;\n * `string` is read as a synonym, the spelling fields had before 2026-10, and\n * means exactly what `text` does. `multiline` is text a writer expects to run\n * to more than one line. A `quality` field is a stage of its own ladder\n * (`stages`), compared by position by a host, never by the engine.\n */\nexport type FieldType = \"text\" | \"multiline\" | \"string\" | \"number\" | \"boolean\" | \"enum\" | \"flags\" | \"quality\";\n\n/** Every field type, in the order an editor offers them (`string` left out:\n * it is read, never offered). */\nexport const FIELD_TYPES: readonly FieldType[] = [\"text\", \"multiline\", \"number\", \"boolean\", \"enum\", \"flags\", \"quality\"];\n\n/** Is this field type text of either shape (`text`, `multiline`, `string`)? */\nexport const isTextFieldType = (type: string): boolean => type === \"text\" || type === \"multiline\" || type === \"string\";\n\n/** A template field (box-defined), of the card template or of the outcome\n * fields. Data for the host; the engine never interprets fields and they are\n * not addressable from expressions. The bundle carries the declaration as\n * written, `string` included. */\nexport interface FieldDecl {\n name: string;\n type: FieldType;\n /** Optional in source: left out, the type's default holds, as for a\n * property, and the compiler writes it into the bundle. When given: for an\n * enum, one of `values` or `\"\"` (none chosen); for flags, a subset of\n * `values`; for a quality, one of `stages`. */\n default?: ScalarValue;\n values?: string[];\n /** A quality's ordered ladder, by the family's one rule (at least two\n * stages, none empty, none twice; `@wildwinter/expr`). */\n stages?: string[];\n purpose?: string;\n}\n\n/** Cooldown policy, in turns (schema 3.4). */\nexport type RedrawPolicy = \"always\" | \"never\" | number;\n\n// --- gameId derivation (Patter's effectiveGameId, adopted 2026-07-20) --------\n//\n// gameId is the renameable host-facing address; it is OPTIONAL in source and\n// derived from the entity's title until the author pins one, so a rename of\n// the title carries the address with it (no \"new-deck\" stuck placeholder).\n// The compiler fills a concrete gameId into every bundle entity.\n\n// `gameIdify` (a human label slugified into a filename- and address-safe gameId) and\n// `isValidGameId` are the family's one rule, shared with Patter from `@wildwinter/toolkit`\n// (step 6, Q19), and re-exported here so this package's API is unchanged.\nexport { gameIdify, isValidGameId };\n\n// --- property names (adopted 2026-08-18, with Patter, from one rule) ---------\n//\n// Design and argument: `@wildwinter/app-shell` src/property-names.ts. Not house\n// style: the rule is what `@wildwinter/expr` can parse. Its lexer takes an\n// identifier as /[a-zA-Z_][a-zA-Z0-9_]*/ and folds it to lower case, so\n// `@story.isNight` reaches a property called `isnight`, `@story.9lives` and\n// `@story.not` are parse errors, and `@story.is-night` is not an error at all: it\n// compiles to `@story.is` MINUS the string \"night\". That last one is why the rule\n// is enforced rather than trusted - it is the only violation that silently means\n// something else.\n//\n// Here rather than behind an import of the UI kit because the compiler, the CLI\n// and the embedded runtime resolve state by these. `property-name-parity.test.ts`\n// holds them to the shell's, and `property-name-grammar.test.ts` holds them to the\n// parser they came from.\n\n/** The words `@wildwinter/expr` lexes as keywords, so no property may be called one. */\n// A legal property NAME is a fact about the expression language, not about this\n// model: `not` is reserved because the tokeniser reads it as an operator. Both\n// families kept their own copy of the rule AND of the keyword list, a list\n// neither owned. @wildwinter/expr derives the list from its own tokeniser, so a\n// keyword added there cannot leave a stale copy here.\n//\n// Re-exported so nothing that imports them has to move.\nexport {\n propertyNameify, isValidPropertyName, isCaseOnlyPropertyName, RESERVED_PROPERTY_NAMES,\n} from \"@wildwinter/expr\";\n/** The effective address: a pinned gameId, else derived from the title, else\n * the immutable id (so there is always something addressable). */\nexport function effectiveGameId(entity: { gameId?: string; title?: string; id: string }): string {\n const pinned = entity.gameId?.trim();\n if (pinned) return pinned;\n const fromTitle = entity.title ? gameIdify(entity.title) : \"\";\n return fromTitle || entity.id;\n}\n\n// --- the value scope's owner segment (design/engine-server.md 4.4) -----------\n//\n// Every other owned scope names its owner with a gameId that is unique across\n// the bundle: box, deck, hand and card. A TAG's gameId is unique only within\n// its group, and a group's only within its box, so two boxes may each name a\n// tag \"docks\" - as ordinary as two boxes each having a \"zone\" group - and\n// `value.docks.danger` then names two stores.\n//\n// So the value scope's owner segment is box-qualified where it has to be:\n// `value.<boxGameId>/<tagGameId>.<name>`. The slash sits INSIDE the owner\n// segment, so the address still splits into three on the dot and no parser\n// changes shape. The qualified form is always accepted; the short form is\n// accepted while exactly one tag in the bundle carries that gameId, and\n// refused when more do, naming the qualified candidates. What a runtime\n// PRINTS - `listProperties`, a write on the trace, a load report, an\n// examiner - is the short form except where the gameId repeats.\n//\n// One definition, because the Board draws these addresses from the bundle\n// while the engine builds them from its own index, and an address the editor\n// shows that the engine will not take is the fault 4.4 was fixing.\n\n// A ZONE of the project map (design/project-map-contract.md 3.4) is the one\n// tag with no qualified form: it belongs to no box, so it prints and is\n// accepted as `value.<zoneGameId>.<name>` whichever boxes use it. Nothing can\n// make that ambiguous, because no box tag may share a zone's gameId (refused\n// by the compiler and again by the engine at load). A box-qualified form that\n// names a zone is REFUSED rather than accepted for old times' sake: there is\n// no old project to be kind to, and the qualified form would assert a box the\n// zone does not have.\n\n/** The answers a value address needs, all derived from the bundle. */\nexport interface ValueAddresses {\n /** Tag internal id -> the owner segment an address PRINTS for it. */\n print: Map<string, string>;\n /** Every owner segment a value address ACCEPTS -> the tag's internal id.\n * Holds the qualified form for every box tag, the short form only for a\n * gameId no other box tag shares, and the short form of every zone. */\n accept: Map<string, string>;\n /** A tag gameId more than one box uses -> its qualified forms, in bundle\n * order. Empty for the overwhelming majority of projects, and what a\n * refusal lists. Never holds a zone. */\n repeated: Map<string, string[]>;\n /** A box-qualified segment that names a project-map zone\n * (`\"box/quay\"`) -> the zone's own segment (`\"quay\"`), the one an\n * address must use instead. One entry per box and zone, so a refusal can\n * name the address that works without working it out again. Empty when\n * the bundle has no map. */\n zoneQualified: Map<string, string>;\n}\n\n/** The owner segment of every tag in the bundle, both ways round: the boxes'\n * tags, then the project map's zones. */\nexport function valueAddresses(bundle: {\n boxes: readonly { id: string; gameId?: string; title?: string; tagGroups: readonly TagGroup[] }[];\n map?: { group: TagGroup };\n}): ValueAddresses {\n const tags: { id: string; gameId: string; qualified: string }[] = [];\n for (const box of bundle.boxes) {\n const boxGameId = effectiveGameId(box);\n for (const group of box.tagGroups) {\n for (const tag of group.tags) {\n const gameId = effectiveGameId(tag);\n tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });\n }\n }\n }\n // Distinct qualified forms per gameId. Distinct rather than a count: two\n // groups in ONE box may also name a tag the same way, and a refusal that\n // offered the same address twice would be no help at all. Those two share\n // the qualified segment, and the first in bundle order answers to it, which\n // is what the short form did for everything before this rule.\n //\n // That last case is closing at the source rather than here (question 16,\n // ruled 2026-09-06): the compiler WARNS that a tag gameId must be unique\n // within its box, across all of that box's groups, and refuses it from the\n // next release. So this stays as the reading rule for a bundle built before\n // that, and a bundle built after it has no repeated qualified form to read.\n const forms = new Map<string, string[]>();\n for (const tag of tags) {\n const list = forms.get(tag.gameId) ?? [];\n if (!list.includes(tag.qualified)) list.push(tag.qualified);\n forms.set(tag.gameId, list);\n }\n const print = new Map<string, string>();\n const accept = new Map<string, string>();\n const repeated = new Map<string, string[]>();\n const zoneQualified = new Map<string, string>();\n for (const tag of tags) {\n const candidates = forms.get(tag.gameId) ?? [tag.qualified];\n const ambiguous = candidates.length > 1;\n print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);\n if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);\n if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);\n if (ambiguous) repeated.set(tag.gameId, candidates);\n }\n // The zones, after the box tags and never in `repeated`. A zone whose\n // gameId a box tag also uses is a bundle the engine refuses at load; should\n // one reach here anyway (the Board reads bundles the engine never loaded),\n // the box tag keeps the short form it already had and the zone does not\n // take it over silently.\n for (const tag of bundle.map?.group.tags ?? []) {\n const gameId = effectiveGameId(tag);\n print.set(tag.id, gameId);\n if (!accept.has(gameId)) accept.set(gameId, tag.id);\n for (const box of bundle.boxes) zoneQualified.set(`${effectiveGameId(box)}/${gameId}`, gameId);\n }\n return { print, accept, repeated, zoneQualified };\n}\n\n/** What a box-qualified address naming a project-map zone is told: why the\n * form is wrong, and the address that works (design/project-map-contract.md\n * 3.4). */\nexport function zoneQualifiedValueAddressMessage(segment: string, zone: string, name: string): string {\n return `\"value.${segment}.${name}\": \"${zone}\" is a zone of the project map, which belongs to no box; `\n + `write \"value.${zone}.${name}\"`;\n}\n\n/** What an ambiguous short-form value address is told: the candidates, in\n * full, because \"that names two tags\" without them leaves a host reading a\n * bundle it did not write to find out which boxes. */\nexport function ambiguousValueAddressMessage(\n segment: string, name: string, candidates: readonly string[],\n): string {\n const forms = candidates.map((q) => `\"value.${q}.${name}\"`);\n const list = forms.length <= 1 ? (forms[0] ?? \"\")\n : `${forms.slice(0, -1).join(\", \")} or ${forms[forms.length - 1]}`;\n return `\"value.${segment}.${name}\" names a tag in ${candidates.length} boxes; write ${list}`;\n}\n\n/**\n * The first free gameId of the form `base`, `base-2`, `base-3`, ... not already\n * in `taken`.\n *\n * A gameId is API - `deal()` and the play log speak it - so a name minted for a\n * new or duplicated entity must not collide with an existing one. This lived in\n * two copies, one in the editor and one in the CLI's kit scaffolder, character\n * for character the same; two copies of an addressing rule can drift, and a\n * drift here means the same act produces different addresses depending on which\n * program did it. It is here, beside `gameIdify`, because both programs need it\n * and no UI touches it.\n */\nexport function freeGameId(base: string, taken: ReadonlySet<string>): string {\n let gameId = base;\n for (let n = 2; taken.has(gameId); n++) gameId = `${base}-${n}`;\n return gameId;\n}\n\n/**\n * The first free TITLE of the form `base`, `base 2`, `base 3`, ... whose\n * derived gameId is not already in `taken`.\n *\n * The sibling of `freeGameId` for the \"New box\", \"New deck\" case, where the\n * author is given a title and the address follows from it. The dedupe is on the\n * DERIVED gameId rather than the title, because two titles that slug to one\n * address are the collision that matters.\n */\n/**\n * An id-sorted collection in the order a person should SEE it.\n *\n * Storage is sorted by immutable id (source rule 5) so that two authors adding\n * one item each never touch the same line. That makes array position useless as\n * display order, so the order the author arranged rides in a sparse `order`\n * field, with position as the fallback and id to break a tie.\n *\n * One definition, because this rule has to give the same answer in four places\n * that a reader compares side by side: the compiler (what the bundle carries),\n * the editor (what the card document lists), the exports, and Find. When they\n * disagree, the editor shows one order and the game plays another.\n */\nexport function byDisplayOrder<T extends { id?: string; order?: number }>(items: readonly T[]): T[] {\n return items\n .map((x, i) => ({ x, key: x.order ?? i, i }))\n .sort((a, b) => a.key - b.key || ((a.x.id ?? \"\") < (b.x.id ?? \"\") ? -1 : (a.x.id ?? \"\") > (b.x.id ?? \"\") ? 1 : a.i - b.i))\n .map((e) => e.x);\n}\n\nexport function freeTitle(base: string, taken: ReadonlySet<string>): string {\n let title = base;\n for (let n = 2; taken.has(gameIdify(title)); n++) title = `${base} ${n}`;\n return title;\n}\n\n/**\n * A count of a TIMED box's turns, said as time (design/engine-server.md 4.8):\n * `turnSpan(30, 60)` is \"30 min\", and `turnSpan(30, 60, true)` is \"30 minutes\".\n *\n * One definition, because the conversion appears wherever a designer might\n * otherwise have to do it in their head: the card editor's Redraw field, the\n * box page, the Board's advance buttons, and the coverage report's turn\n * budget. Two of those want the unit spelled out and two want it short, which\n * is the whole of `long`.\n */\nexport function turnSpan(turns: number, seconds: number, long = false): string {\n const total = Math.max(0, Math.round(turns * seconds));\n const say = (n: number, short: string, one: string, many: string): string =>\n long ? `${n} ${n === 1 ? one : many}` : `${n} ${short}`;\n if (total < 60) return long ? say(total, \"s\", \"second\", \"seconds\") : `${total}s`;\n if (total < 3600) {\n const minutes = total % 60 === 0 ? total / 60 : Math.round(total / 6) / 10;\n return say(minutes, \"min\", \"minute\", \"minutes\");\n }\n let hours = Math.floor(total / 3600);\n let rest = Math.round((total % 3600) / 60);\n if (rest === 60) { hours += 1; rest = 0; }\n const said = say(hours, \"hr\", \"hour\", \"hours\");\n return rest === 0 ? said : `${said} ${say(rest, \"min\", \"minute\", \"minutes\")}`;\n}\n\n// --- entities (generic over expression representation E) ---------------------\n\nexport interface Outcome<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Unlike `Card.order` this one IS compiled into the bundle:\n * which option is offered first is authorial, and a host reading a dealt\n * card's outcomes is building the player's menu. */\n order?: number;\n /** Gating; availability is always evaluated against current state. */\n condition?: E;\n /** Target (\"@scope.name\") -> expression; all right-hand sides evaluate\n * against pre-play state (schema 3.7). */\n changes: Record<string, E>;\n /** Template data, as `Card.fields` is: field name -> value, declared by\n * the box's `outcomeFields`, validated at publish, and handed to the host\n * with the outcome. The engine never reads it: a press can say one line\n * (\"The notice is in your pocket\") without spending a card on it. */\n fields?: Record<string, ScalarValue>;\n}\n\n/** A box's defaults for its cards' dealing rules (its Card template). */\nexport interface CardDefaults {\n redraw?: RedrawPolicy;\n weight?: number;\n inOneHand?: number;\n acrossHands?: \"one\" | \"any\";\n}\n\nexport interface CardFields<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order within the deck (sparse; a card without one falls\n * back to its id position). Merges as a per-card value, so id-sorted storage\n * stays merge-clean (Reboot 7.4); dropped from the compiled bundle. */\n order?: number;\n condition?: E;\n /** Default 0; an expression must evaluate to a number. */\n priority: number | E;\n /** Always present in a bundle. A source card may leave it out, and then\n * takes its box's Card template default (or \"always\"). */\n redraw?: RedrawPolicy;\n /** Tags: tag group id -> tag ids. An absent group is a wildcard (matches\n * any binding of it), except the reserved home group, whose default\n * inverts (schema 2.4). Editors and fixtures speak gameIds; stored\n * references are ids. */\n tags?: Record<string, string[]>;\n /** How often this card comes up beside cards of equal rank: a positive\n * number, default 1 (the hands-on review, 2026-10: weights in place of\n * Copies). Priority, then specificity, still decide between ranks; within\n * a run of equal cards a card of weight 2 is drawn ahead twice as often as\n * one of weight 1. A run whose weights are all equal shuffles exactly as it\n * always has. */\n weight?: number;\n /** How many slots of ONE hand this card may fill: integer >= 1, default 1\n * (an encounter comes up once; a pickup or a piece of equipment can be up\n * to 3). */\n inOneHand?: number;\n /** Whether several hands may hold this card at once, within a flow:\n * \"one\" (the default, a one-off can't be offered in two places) or \"any\"\n * (interchangeable filler). Replaces `copies`, which a shard from before it\n * may still hold: the compiler refuses it, and `storyletengine format`\n * rewrites it as this (1 is \"one\", more is \"any\"). */\n acrossHands?: \"one\" | \"any\";\n /** Scarcity across flows (design/shared-scarcity.md). Absent takes the\n * deck's flag; set here it overrides the deck, so a single unique card can\n * stay in the content it belongs to. A shared card's claims count every\n * flow's board, and a shared `redraw: \"never\"` is spent for everyone the\n * first time anyone plays it.\n *\n * A finite `redraw` stays PER FLOW even when shared: a cooldown is an\n * absolute turn of the card's box clock and clocks are per flow, so\n * \"3 turns of whose clock?\" has no answer. A world-wide timer is a @world\n * question, not an engine one (shared-scarcity 9.3.3). */\n shared?: boolean;\n /** The world cap: how many hands ACROSS EVERY FLOW may hold this at once.\n * Read only when the card is shared, and defaults to the per-flow limit\n * (one hand, or no limit under `acrossHands: \"any\"`), so \"five in the\n * world, one to a customer\" is `sharedCopies: 5`. */\n sharedCopies?: number;\n /** Does this card's `redraw: \"never\"` spend survive the run\n * (design/engine-server.md 4.2)? Absent takes the deck's flag, set here it\n * overrides the deck, exactly as `shared` does. `shared` decides who a\n * spend counts for WITHIN a run; this decides whether it outlives one.\n *\n * Only `\"never\"` crosses the run boundary, for the reason only `\"never\"`\n * crosses the flow boundary (shared-scarcity 9.3.2): a finite cooldown is\n * an absolute turn of a box clock, and the clock resets with the run. On\n * any other redraw the flag is a compile warning.\n *\n * Like the declaration flag, it only decides what the durable halves carry:\n * a per-flow spend rides the flow's half (its `never` cooldown), a shared\n * one the engine's half (its spent set), each by card gameId. */\n durable?: boolean;\n /** Card-template data: field name -> value, validated at publish. */\n fields?: Record<string, ScalarValue>;\n outcomes: Outcome<E>[];\n}\n\n/** A bundle card from before `acrossHands`: the one field a runtime still\n * reads for it. How many hands of a flow may hold the card at once (schema\n * 3.5), integer >= 1, default 1, read only when `acrossHands` is absent. Never\n * written any more, and not part of the source model: a shard that still says\n * `copies` is refused by the compiler and rewritten by `storyletengine format`. */\nexport interface LegacyBundleCard {\n /** @deprecated Read from an old bundle only; see `LegacyBundleCard`. */\n copies?: number;\n}\n\n/** A card. Source cards hold their expressions as strings (`Card<string>`);\n * bundle cards as `{ src, ast }` envelopes (`Card<Expression>`), which alone\n * may carry an old bundle's `copies`. */\nexport type Card<E> = CardFields<E> & (E extends string ? unknown : LegacyBundleCard);\n\nexport interface Deck<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** The deck gate, evaluated once per draw in the draw's environment. */\n condition?: E;\n /** This pile is scarce across flows (design/shared-scarcity.md): every card\n * in it is shared unless the card says otherwise. The container is where\n * Patter puts its own shared-memory flag, and the deck is our container. */\n shared?: boolean;\n /** Every `redraw: \"never\"` card in this pile is spent for good, past the end\n * of the run, unless the card says otherwise (design/engine-server.md 4.2).\n * The container carries the flag for the reason `shared` is carried here:\n * a pile is what an author reaches for when a rule is true of all of it. */\n durable?: boolean;\n properties: PropertyDecl[];\n cards: Card<E>[];\n}\n\nexport interface Tag {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n /**\n * This tag's own starting values for properties its GROUP declares\n * (design/hand-typing.md). The group says what the property IS; a tag says\n * only where it starts, so \"every zone has a haunting level\" is written once\n * and \"the cave starts at 2\" is written where it belongs.\n *\n * A name here that the group does not declare is an error: it would be a\n * value for nothing.\n */\n values?: Record<string, ScalarValue>;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Merges as a per-item value, so id-sorted storage stays\n * merge-clean (Reboot 7.4). */\n order?: number;\n properties?: PropertyDecl[];\n /** Template-of-play extras (e.g. spatial geometry). Source only: preserved\n * in shards, never compiled into the bundle. */\n templates?: Record<string, unknown>;\n}\n\n/** A named axis for cross-cutting cards (schema 2.4, renamed from\n * Dimension). Tags are declared, not freeform. */\nexport interface TagGroup {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n purpose?: string;\n /**\n * A property reference (`\"@story.act\"`) whose value names a tag in this group\n * by gameId. The engine reads it at every ask and binds the group, exactly as\n * if the asking hand had chosen that tag; a hand's own binding wins.\n *\n * For an axis driven by STATE rather than by place: acts, chapters, a\n * difficulty band. Without it, only a hand can bind a group, so such an axis\n * had nowhere to gate and every card needed its own condition.\n *\n * A reference rather than an expression on purpose (design/where-and-\n * selectors.md Part B): a computed binding belongs in a property the outcomes\n * maintain, and an expression here would make this type generic for no gain.\n */\n boundBy?: string;\n /**\n * What omitting this group means for a card. Default false: omission is a\n * wildcard, so the card matches whatever the group is bound to. True inverts\n * it, so a card that names no tag here is unavailable wherever the group IS\n * bound (and unaffected where it is not).\n *\n * `place` is the built-in instance of this pair: bound to the asking hand,\n * and inverted per card rather than per group.\n */\n required?: boolean;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Merges as a per-item value, so id-sorted storage stays\n * merge-clean (Reboot 7.4). */\n order?: number;\n /**\n * Properties EVERY tag in this group has (design/hand-typing.md). The\n * declaration lives here and each tag carries only its own starting value in\n * `Tag.values`, which is the separation the format was missing: a tag's own\n * `properties` entry has to restate the type on every tag purely in order to\n * say the value, and a tag added later silently arrives without it.\n *\n * Compiled by FLATTENING onto each tag, so the bundle keeps its per-tag\n * shape and no runtime, port or bundle schema changes: source is where the\n * author works and where merges happen, the bundle is a compiled artefact\n * that can afford to be explicit.\n *\n * A tag may still declare its own `properties` for a group whose tags\n * genuinely differ. Declaring the same NAME both ways is an error.\n */\n properties?: PropertyDecl[];\n tags: Tag[];\n /** Template-of-play extras for the GROUP, the same bag its tags carry: this is\n * where a group is marked spatial and where that template keeps its own\n * group-level configuration. Source only, preserved but never compiled.\n *\n * A bag rather than a `spatial: true` flag because the marker and the\n * configuration are one thing (see model/spatial.ts), and because core is not\n * meant to grow a field per template of play. */\n templates?: Record<string, unknown>;\n}\n\n/** The reserved tag group (schema 2.4): present in every box without\n * declaration, its tags the box's hand ids. Every hand implicitly binds it to\n * itself; a card that names a place is available only at that place.\n *\n * Called `place` rather than `home` since 2026-08-21: one word for one thing\n * across the format, the editor and the exports. \"Where\" is the QUESTION a\n * card answers (at a place, or anywhere in a region); \"place\" is the direct\n * half of that answer. `home` was a metaphor an author had to learn, and it\n * leaked into hand-edited shards and the docs. */\nexport const PLACE_GROUP = \"place\";\n\n/** The scopes a movable hole may be filled from (design/engine-server.md 4.6):\n * the two `boundBy` already allows, plus `@hand` - the asking hand's OWN\n * declared property, resolved before tag composition so a movable hole can\n * never depend on the tags it is choosing. */\nexport type HoleRefScope = \"hand\" | \"story\" | \"world\";\n\n/** A parsed hole reference: `@hand.zone` -> `{ scope: \"hand\", name: \"zone\" }`. */\nexport interface HoleRef {\n scope: HoleRefScope;\n name: string;\n}\n\nconst HOLE_REF = /^@(hand|world|story)\\.([a-z][a-z0-9_-]*)$/;\n\n/**\n * Is this `chosen` / binding value MEANT as a property reference rather than a\n * tag id?\n *\n * The test is the leading `@` alone, deliberately: a value that starts with\n * one and does not parse is a mistyped reference, which the compiler should\n * name as such, not a tag id that happens to look odd. Tag ids never begin\n * with `@`.\n */\nexport const isHoleRef = (value: string): boolean => value.startsWith(\"@\");\n\n/** Parse a hole reference, or undefined when it is not one. The on-disk form\n * stays a plain string, so the canonical serialiser and the shard merge need\n * no change at all: a hole is still one group name against one value. */\nexport const parseHoleRef = (value: string): HoleRef | undefined => {\n const m = HOLE_REF.exec(value);\n return m === null ? undefined : { scope: m[1] as HoleRefScope, name: m[2]! };\n};\n\n/** A declared kind of hand (schema 2.6): live-inherited, author-side only,\n * never called from game code. One condition governs every instance. */\nexport interface HandTemplate<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Merges as a per-item value, so id-sorted storage stays\n * merge-clean (Reboot 7.4). */\n order?: number;\n\n /** Fixed tag bindings: tag group id -> tag id. Literal tags only: what a\n * template FIXES is the same for every instance, and a hole that moves is\n * the instance's own business (`Hand.chosen`, 4.6). */\n bindings?: Record<string, string>;\n /** The holes: tag group ids each instance fills (one tag each, or one\n * property reference: 4.6). */\n chooses?: string[];\n /** Shared availability condition, ANDed in (schema 3.1); evaluated per\n * instance against that instance's composed @hand. */\n condition?: E;\n /** Default slot cap. */\n slots: number | \"unbounded\";\n /** Declared @hand state every instance carries. */\n properties: PropertyDecl[];\n}\n\n/** A standalone hand's inline rule (schema 2.6): owned by the hand. */\nexport interface HandRule<E> {\n /**\n * Tag group id -> tag id, or a PROPERTY REFERENCE (`\"@hand.zone\"`,\n * `\"@story.where\"`, `\"@world.place\"`) the runtime resolves at ask time\n * (design/engine-server.md 4.6, the hand that moves). Still a plain string\n * on disk, so the canonical serialiser and the merge are untouched; what\n * widened is the meaning, and `parseHoleRef` is where it is read.\n *\n * `place` is never fillable this way: it is the hand's own name, not an axis.\n */\n bindings?: Record<string, string>;\n condition?: E;\n slots: number | \"unbounded\";\n}\n\n/** A hand (schema 2.6): a template instance (template + chosen) or a\n * standalone hand (rule). Exactly one of template / rule. Fully concrete:\n * deal is name-only. */\nexport interface Hand<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n /** The name deal() is called with; a rename is a breaking change\n * (Reboot 7.4). */\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Hand template id (not gameId). */\n template?: string;\n /**\n * Template instances: tag group id -> tag id, one per `chooses` hole.\n *\n * A value may instead be a PROPERTY REFERENCE (`\"@hand.zone\"`,\n * `\"@story.where\"`, `\"@world.place\"`), which makes the hole MOVABLE: the\n * runtime resolves the reference at ask time and binds the hole to the tag\n * the value names, so moving the Elder to the forest is `setProperty` and\n * nothing else (design/engine-server.md 4.6). Still a plain string on disk,\n * so the canonical serialiser and the shard merge need no change; read it\n * with `parseHoleRef`.\n */\n chosen?: Record<string, string>;\n /** Standalone hands: the inline rule. */\n rule?: HandRule<E>;\n /** Override; defaults to the template's / rule's slots. The ONLY template\n * field an instance may override (schema 2.6). */\n slots?: number;\n /** Standalone hands' own @hand state (template instances inherit the\n * template's declarations). */\n properties?: PropertyDecl[];\n /** Authored display order within the box (sparse; authoring-only, never\n * compiled into the bundle - the compiler's explicit field list drops it). */\n order?: number;\n /** Template-of-play extras (e.g. a spatial pin). Source only. */\n templates?: Record<string, unknown>;\n}\n\nexport interface Box<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** The only per-box ranking policy (Reboot 2.2). */\n ranking: { specificity: boolean };\n /**\n * Who moves the box's clock. Absent is the ordinary box, whose clock a\n * play moves. Present, the GAME moves it: with `seconds` it is a TIMED box,\n * counting real time, one turn every `seconds` of the run\n * (design/engine-server.md 4.8); without, it is a calendar or any clock the\n * game ticks on its own beat (the hands-on review, 2026-10: the three-way\n * box clock).\n *\n * Two things follow, and only two. In the ENGINE, a play in this box\n * defaults to advancing nothing: `settings.playAdvancesTurns` does not\n * apply, so a designer cannot declare the convention and then forget to\n * switch play-advance off. Everywhere else it is what the tools SAY: the\n * host ticks the box (the runtime has no clock and gains none here), and a\n * card's `redraw: N` reads as N x `seconds`, which the editors, the bundle\n * inspectors and the coverage report spell out rather than leaving a\n * designer to know that 30 meant minutes.\n *\n * The number itself is inert to the runtime, which never reads it.\n */\n turn?: { seconds?: number };\n /** The card template: what every card in this box carries. */\n fields: FieldDecl[];\n /** What every outcome in this box may carry, declared the same way.\n * Absent when the box declares none, so a bundle without them is byte for\n * byte what it was. */\n outcomeFields?: FieldDecl[];\n properties: PropertyDecl[];\n /**\n * The box has opted in to the PROJECT MAP (design/project-map-contract.md\n * 1.2, 2.2): its hands may bind the map's zone group and its cards may be\n * tagged with it, and it sees the group's name beside its own groups' names.\n * Absent is \"not on the map\", and a box that is not may not reference the\n * group at all: the engine refuses such a bundle at load.\n *\n * Opting in changes nothing about what the box deals FROM: a hand still\n * deals only from its own box's decks. What the boxes on the map share is\n * the zones' values (one bag per zone), never their cards or their history.\n */\n usesMap?: true;\n /** The box's OWN tag groups. The project map's group is never here, even in\n * a box that uses it: it is the bundle's, in `Bundle.map` (`groupsOfBox`\n * gives the two together). */\n tagGroups: TagGroup[];\n decks: Deck<E>[];\n handTemplates: HandTemplate<E>[];\n hands: Hand<E>[];\n}\n\n// --- the compiled bundle (.storyletsc) ---------------------------------------\n\n/**\n * The bundle schema this build of the format writes: @2 since host scopes\n * (2026-10-28), the bundle whose game scopes are `scopeRegistry` and which has\n * no `world` block. @1 was the project map's (design/project-map-contract.md\n * 2.4), the bundle that can carry `map` and `Box.usesMap`.\n *\n * @2 is a new number because the `world` block is GONE, not added to: a runtime\n * that reads only @1 would reach for `world.properties` and fail as a raw\n * error, where a schema it does not know is refused by name.\n *\n * Every runtime READS @0, @1 and @2 and refuses anything else, by name. Reading @0\n * is not a compatibility branch: an @0 bundle cannot carry a map, so it is the\n * same shape with less in it. The check exists so that from this release on a\n * runtime meeting a schema it does not know says so, rather than half-working\n * the way an @0 runtime does on a map bundle (tag matching is by id, so its\n * deals look right while every zone value is missing).\n */\nexport const BUNDLE_SCHEMA = \"storylets/bundle@2\";\n/** The project map's schema, before host scopes: the game's scopes were one\n * `world` block. Still read by every runtime, its `world.properties` as the\n * host scope `world`. */\nexport const BUNDLE_SCHEMA_V1 = \"storylets/bundle@1\";\n/** The schema before the project map, still read by every runtime. */\nexport const BUNDLE_SCHEMA_V0 = \"storylets/bundle@0\";\n/** Every bundle schema a runtime accepts. */\nexport const BUNDLE_SCHEMAS: readonly string[] = [BUNDLE_SCHEMA_V0, BUNDLE_SCHEMA_V1, BUNDLE_SCHEMA];\n\n/** Binds bundles to shards (staleness gate) and saves to bundles. */\nexport interface BundleContent {\n project: string;\n version: string;\n /** hash32 over the canonical source shards (schema 2.8). */\n hash: string;\n}\n\nexport interface BundleSettings {\n playAdvancesTurns: number;\n}\n\n/**\n * The play ladder (design/engine-server.md 4.10): how much of itself\n * Storyletter shows this project, in one setting with three rungs rather than\n * a set of toggles, because the features nest.\n *\n * solo one player, one flow: no sharing, no durability, no venue features\n * shared several players over one world: sharing appears\n * venue a production: nothing is hidden\n *\n * EDITOR-SIDE ONLY. It stays in the project shard beside `coverage` and\n * `export` and is never compiled: a solo project plays on the same Engine as a\n * venue one. Hidden is hidden rather than disabled, so going DOWN a rung is\n * refused when the project already contains what the rung would hide, and a\n * hand-edited shard above its rung is a compile warning.\n */\nexport type PlayRung = \"solo\" | \"shared\" | \"venue\";\n\n/** The default rung: a project shard that says nothing is a solo game. */\nexport const DEFAULT_PLAY_RUNG: PlayRung = \"solo\";\n\n/** The project shard's settings block: what the bundle carries, plus the\n * authoring-side play rung that it does not. */\nexport interface ProjectSettings extends BundleSettings {\n /** The play ladder rung (see `PlayRung`). Absent = \"solo\". */\n play?: PlayRung;\n}\n\n/**\n * The PROJECT MAP (design/project-map-contract.md 2.1): at most one per project,\n * above the boxes. Two halves, on purpose.\n *\n * `group` is SEMANTIC and always ships when the project declares a map: the zone\n * group, compiled exactly as a box's group is (the group's properties flattened\n * onto each tag), which opted-in boxes' hands bind and cards are tagged with BY\n * ID, as with any group. It is in no box's `tagGroups`. Each zone is one tag and\n * so one value bag, whichever boxes' hands are dealt to it.\n *\n * `geometry` is INERT PAYLOAD, as the per-box map block before it was: nothing\n * in the engine reads it, and it is absent unless the project asked for it\n * (`export.map`), so a shipping build carries no shapes. GAME IDS throughout,\n * because a host matches these against the names it passes to `peek`.\n *\n * SITES ARE CONTENT. Until 2026-09-05 they were deliberately left out: a site\n * was where an author parked a hand while working. That held for a game, where a\n * hand's zone is its only real-world meaning; it does not hold for a physical\n * experience (design/engine-server.md 4.3), where the position is where the kiosk\n * stands. They stay per box, because a hand belongs to one box.\n */\nexport interface ProjectMap {\n /** The zone group. The engine reads this. */\n group: TagGroup;\n /** Drawing data, only under `export.map`. The engine never reads it. */\n geometry?: {\n /** Drawn zones only, by tag gameId, in group order. A tag with no polygon\n * is not a place yet and is left out rather than shipped as an empty\n * shape. */\n zones: { tag: string; polygon: ViewPoint[] }[];\n /** Visible background pictures, back to front, as bundle-relative paths. */\n backgrounds?: BundleBackground[];\n /** Box gameId -> where that box's placed hands stand, sorted by hand\n * gameId so the bytes do not depend on authoring order. Only opted-in\n * boxes; a box with no site has no key, and no key at all when nothing\n * is placed. The zone a site sits in is NOT repeated: the hand's own\n * binding is what the runtime deals from. */\n sites?: Record<string, { hand: string; x: number; y: number }[]>;\n };\n}\n\n/** One shipped picture. `locked` and `hidden` are authoring state and do not\n * travel; the draw order is the array order. */\nexport interface BundleBackground {\n /** Where the file sits relative to the bundle (\"assets/<file>\"). */\n file: string;\n x: number;\n y: number;\n width: number;\n height: number;\n opacity?: number;\n}\n\nexport interface Bundle {\n schema: typeof BUNDLE_SCHEMA | typeof BUNDLE_SCHEMA_V1 | typeof BUNDLE_SCHEMA_V0;\n content: BundleContent;\n metadata: \"full\" | \"stripped\";\n settings: BundleSettings;\n /** The game's own scopes the project declares (`@world`, `@player`, ...),\n * Patter's shape: each one's token, its scope-level `writable` and its\n * declarations, every default resolved (absent declarations: an opaque\n * scope). A standalone engine self-backs each from its defaults; a game\n * binds its own store for any of them (`hostScopes`). Absent when the\n * project declares none. */\n scopeRegistry?: HostScopeRegistry;\n /** Bundle @1 and @0 only: the one game scope there was, read as the host\n * scope `world` (see `bundleHostScopes`). A bundle @2 has none. */\n world?: LegacyWorldBlock;\n story: {\n properties: PropertyDecl[];\n };\n boxes: Box<Expression>[];\n /** The project map, when the project declares one (see `ProjectMap`). The\n * per-box `maps` array it replaces is gone, not kept as a one-element list:\n * zones are no longer a box's. */\n map?: ProjectMap;\n /** Other engines' game-wide scopes the content names (`patter`), sorted: the\n * family's shared vocabulary, let through unchecked by the compiler. The\n * engine reports when the game has not registered one. Absent when none.\n * A token the project declares (`scopeRegistry`) is the game's, never here. */\n externalScopes?: string[];\n /**\n * The extensions the project takes, by id: each one's version and its\n * compiled declarations by node kind, every default resolved (what a\n * defaults helper reads, `extensionDecls`), and the project node's own values.\n * A node's values sit on the node, under `extensions`. Absent when the project\n * takes none, so a bundle without them is byte for byte what it was.\n */\n extensions?: Record<string, BundleExtension>;\n}\n\n/** One extension as the bundle carries it. */\nexport interface BundleExtension {\n version: string;\n /** Compiled declarations by node kind: no editor-only fields, no editor words. */\n fields: Record<string, ExtensionFieldDecl[]>;\n /** The project node's values, raw, when it has any. */\n project?: Record<string, unknown>;\n}\n\n/**\n * A compiled extension field declaration (`@wildwinter/toolkit/extensions`' FieldDecl,\n * less the editor's words). Its `default` is always present. Structured types\n * carry their parts: `fields` for a record, `key` and `of` for a map, `of` and\n * `id` for a list, `to` for a reference.\n */\nexport interface ExtensionFieldDecl {\n name: string;\n type: string;\n default: unknown;\n values?: string[];\n stages?: string[];\n to?: string;\n fields?: ExtensionFieldDecl[];\n key?: { type: string; to?: string };\n of?: Omit<ExtensionFieldDecl, \"name\" | \"default\"> & { default?: unknown };\n id?: string;\n min?: number;\n max?: number;\n required?: boolean;\n}\n\n/** A node's extension values in a bundle: extension id -> field -> value, raw. */\nexport type NodeExtensions = Record<string, Record<string, unknown>>;\n\n// --- reading the project map (design/project-map-contract.md 5, Q12) ---------\n//\n// The questions every reader of a bundle asks once a group may live outside the\n// box that uses it, answered once here beside `valueAddresses`, because the\n// engine, `describeBundle`, the Board and the server each looked a bound group up\n// in the hand's own box and silently lost the project one. Pure field access.\n\n/** The tag groups a box sees, by NAME as well as by id: its own groups, then the\n * project map's group when the box has opted in. The engine resolves a group\n * name in a box against exactly this list, own groups first (a bundle the\n * engine loads never has a box group sharing the project group's name, so the\n * order decides nothing; it is stated for determinism). */\nexport function groupsOfBox<E>(\n bundle: { map?: { group: TagGroup } },\n box: Pick<Box<E>, \"tagGroups\" | \"usesMap\">,\n): TagGroup[] {\n return box.usesMap === true && bundle.map !== undefined\n ? [...box.tagGroups, bundle.map.group]\n : box.tagGroups;\n}\n\n/** A tag group by internal id, wherever it lives: in a box (`box` set) or on the\n * project map (`box` absent). Ids are project-unique, so this is bundle-wide;\n * whether the box asking may USE the group is `groupsOfBox`'s question. */\nexport function groupById<E>(\n bundle: { boxes: readonly Box<E>[]; map?: { group: TagGroup } },\n id: string,\n): { group: TagGroup; box?: Box<E> } | undefined {\n for (const box of bundle.boxes) {\n const group = box.tagGroups.find((g) => g.id === id);\n if (group !== undefined) return { group, box };\n }\n return bundle.map?.group.id === id ? { group: bundle.map.group } : undefined;\n}\n\n/** Every tag group in the bundle, each once: the boxes' groups in bundle order,\n * then the project map's. What a walk over every value bag wants. */\nexport function allTagGroups<E>(bundle: { boxes: readonly Box<E>[]; map?: { group: TagGroup } }): TagGroup[] {\n const groups = bundle.boxes.flatMap((box) => box.tagGroups);\n return bundle.map !== undefined ? [...groups, bundle.map.group] : groups;\n}\n\n/** One declared owned property, as an address prints it. */\nexport interface PropertyAddress {\n /** The engine address, `<scope>.<owner segment>.<name>`, exactly as\n * `listProperties` prints it and `setProperty` accepts it. */\n path: string;\n scope: \"box\" | \"deck\" | \"hand\" | \"value\";\n /** The owner's internal id: what the stores and the save are keyed by. */\n owner: string;\n decl: PropertyDecl;\n}\n\n/**\n * Every declared box, deck, hand and tag property in the bundle, by the address\n * a runtime prints for it: boxes in bundle order (the box, its decks, its hands,\n * its tags), then the project map's zones, ONCE, whichever boxes use them. A hand\n * instance carries its template's declarations, as the engine's bags do.\n *\n * The owner segments are `valueAddresses`' for tags, so a zone prints\n * `value.<zone>.<name>` and a repeated box tag its qualified form.\n */\nexport function propertyAddresses(bundle: {\n boxes: readonly Box<unknown>[];\n map?: { group: TagGroup };\n}): PropertyAddress[] {\n const values = valueAddresses(bundle);\n const out: PropertyAddress[] = [];\n const push = (scope: PropertyAddress[\"scope\"], owner: string, segment: string, decls: readonly PropertyDecl[]): void => {\n for (const decl of decls) out.push({ path: `${scope}.${segment}.${decl.name}`, scope, owner, decl });\n };\n const tagsOf = (group: TagGroup): void => {\n for (const tag of group.tags) {\n push(\"value\", tag.id, values.print.get(tag.id) ?? effectiveGameId(tag), tag.properties ?? []);\n }\n };\n for (const box of bundle.boxes) {\n push(\"box\", box.id, effectiveGameId(box), box.properties);\n for (const deck of box.decks) push(\"deck\", deck.id, effectiveGameId(deck), deck.properties);\n for (const hand of box.hands) {\n const decls = hand.template !== undefined\n ? box.handTemplates.find((t) => t.id === hand.template)?.properties ?? []\n : hand.properties ?? [];\n push(\"hand\", hand.id, effectiveGameId(hand), decls);\n }\n for (const group of box.tagGroups) tagsOf(group);\n }\n if (bundle.map !== undefined) tagsOf(bundle.map.group);\n return out;\n}\n\n// --- the save envelope --------------------------------------------------------\n//\n// Version 2 (the one-registry model): property values are the game's\n// ScopeRegistry's, not the engine's. The envelope holds what is NOT a property\n// (boards, clocks, cooldowns, PRNGs, play logs, spent cards), plus, when the\n// engine made its own registry (a standalone game), that registry's values\n// under `registry`. A game that passed a registry saves it once itself.\n// Version 1 envelopes still load on every runtime: their property partitions\n// move into the registry under the keys below.\n//\n// Registry keys (identical on every runtime, since they are in the save):\n// `story` for the shared @story; `storylets/<kind>/<id>` for a shared box,\n// deck, hand, or value bag (kind is `box`, `deck`, `hand`, or `value`, id the\n// internal id); `storylets/flow/<flowId>/story` and\n// `storylets/flow/<flowId>/<kind>/<id>` for a flow's own. Ids escape `%` as\n// `%25` and `/` as `%2F`. A bag with no declared properties is not registered.\n// A self-backed @world (no resolver bound) is a stored property too, under\n// `world`.\n\n/** Version 3 (the hands-on review, 2026-10, foundation 7): a flow's turns,\n * cooldowns and board, and the shared spent cards, are keyed by GAME ID, as\n * the play log always was, so a card deleted and made again under the same\n * Game ID keeps its cooldown and its place as well as its play count. */\nexport const SAVE_SCHEMA = \"storylets/save@3\";\n/** Version 2, keyed by internal id: still read, every key resolved as a Game\n * ID first and an internal id second. */\nexport const SAVE_SCHEMA_V2 = \"storylets/save@2\";\n/** The version 1 envelope's schema tag, still read. */\nexport const SAVE_SCHEMA_V1 = \"storylets/save@1\";\n\nexport interface PlayRecord {\n /** Card and outcome by gameId (feeds the play-history functions). */\n card: string;\n /** \"\" for a card with no outcomes, played with none: the key is always\n * there, so a save's shape does not depend on the card. */\n outcome: string;\n turn: number;\n}\n\n/** A property bag: name -> value. */\nexport type PropertyBag = Record<string, ScalarValue>;\n\n/** The per-scope property partitions one side of the sharing flag holds:\n * a save carries one of these for the shared values and one per flow\n * (design/flows.md). NO world key, in either: @world is the game's own\n * state, resolved through the world resolver and saved by whoever owns\n * it - \"host saves its container once, each engine saves its own\n * envelope\" (engine-runtimes.md 3.1). */\nexport interface PropsPartition {\n story: PropertyBag;\n box: Record<string, PropertyBag>;\n deck: Record<string, PropertyBag>;\n hand: Record<string, PropertyBag>;\n /** Tag state, keyed by tag id. */\n value: Record<string, PropertyBag>;\n}\n\n/** One flow's snapshot inside the envelope (schema 4), and the blob\n * `saveFlow` parks. */\nexport interface FlowSave {\n /** The per-flow property partitions. Carried by `saveFlow`, which parks one\n * flow whole; absent from a version 2 envelope's flows, whose properties\n * are the registry's. Present in every flow of a version 1 envelope. */\n props?: PropsPartition;\n /** Per-box turn counters, keyed by box Game ID (schema 3.4; by box id\n * before version 3) - per flow: there is deliberately no global turn. */\n turns: Record<string, number>;\n /** mulberry32 state, uint32 (schema 3.3), per flow. */\n prng: number;\n /** Absolute next-eligible turn (of the card's box's clock) per card Game ID\n * (card id before version 3); MAX_SAFE_INTEGER = never (deliberately not\n * Infinity, which JSON-serialises to null). */\n cooldowns: Record<string, number>;\n /** Hand contents (card Game IDs, in dealt order), keyed by hand Game ID\n * (internal ids before version 3). The claims ledger is derived from this\n * (schema 3.5). */\n board: Record<string, string[]>;\n playLog: PlayRecord[];\n}\n\n/** The whole engine, one envelope: the shared partitions once, then every\n * live flow keyed by its id - Patter's shape (one shared blob + N flow\n * blobs; multi-flow and save/load are the same feature). */\n/** The engine's half of a save: what every flow shares. Properties, and the\n * cards a shared `redraw: \"never\"` has taken out of the world for good\n * (design/shared-scarcity.md). Claims are NOT here: they are derived from the\n * live boards, and each flow's board rides its own blob. */\nexport interface SharedSave {\n /** The shared property partitions: version 1 only. */\n props?: PropsPartition;\n /** Card Game IDs (ids before version 3), sorted, so a save is byte-stable\n * for a diff. */\n spent: string[];\n}\n\nexport interface SaveEnvelope {\n /** Written as version 3; a version 2 envelope has the same shape, keyed by\n * internal id. */\n schema: typeof SAVE_SCHEMA | typeof SAVE_SCHEMA_V2;\n content: BundleContent;\n /** The engine's own registry's values, keyed by registry key: present only\n * when the engine made the registry itself (a standalone game). A game that\n * passed a registry saves it once, beside this envelope. */\n registry?: Record<string, PropertyBag>;\n shared: SharedSave;\n flows: Record<string, FlowSave>;\n}\n\n/** A version 1 envelope, from before the registry held the properties. Every\n * runtime still reads it: its partitions move into the registry as it loads. */\nexport interface SaveEnvelopeV1 {\n schema: typeof SAVE_SCHEMA_V1;\n content: BundleContent;\n shared: SharedSave & { props: PropsPartition };\n flows: Record<string, FlowSave & { props: PropsPartition }>;\n}\n\n// --- the load report (design/engine-server.md 4.9) ----------------------------\n//\n// `loadGame` is forgiving by design: a card the bundle no longer has drops off\n// the board, a property the save does not carry keeps its default, and a\n// version two builds newer loads without a word. That forgiveness is what makes\n// a save survive an edit, and it is also what hides the cost of a content\n// update from whoever is about to apply one. The report is the same walk,\n// itemised: `previewLoad` computes it and changes nothing, `loadGame` computes\n// it and applies it, and `previewFlowRestore` answers the same questions for\n// one flow (4.1's `openFlow(id, { restore })`).\n//\n// Card, hand and flow identities are GAME IDS: a report is host-facing and\n// internal ids mean nothing outside the project. An entity the edit DELETED -\n// a vanished card, a vanished hand - is named by the key the save itself\n// carries, which is its Game ID from save version 3 (an internal id in an\n// older save). There is nothing else to name it by.\n//\n// A property is named differently, and deliberately: by its ENGINE ADDRESS,\n// the string listProperties() prints and getProperty()/setProperty() accept.\n// A report entry is then something a host can act on rather than merely\n// print, and the runtimes have one property grammar instead of two.\n\n/** One card that a restore refused to put back on the board, or a load\n * dropped from it because it no longer qualifies.\n *\n * `vanished` and `hand-vanished` are the edit's doing (the card, or the hand\n * it sat in, is no longer in the bundle). `claimed-elsewhere` is only ever a\n * single-flow restore into a LIVE engine: the card is shared, and the other\n * open flows already hold every copy the world has. */\nexport interface LoadEviction {\n flow: string;\n hand: string;\n card: string;\n /** The rest are a deal's own reasons, met as a load checks what it put\n * back (save version 3 era; the hands-on review, 2026-10, foundation 7):\n * the card, its deck or its hand no longer qualifies with the values the\n * save carries. Checked only when the envelope carries them. */\n reason: \"vanished\" | \"hand-vanished\" | \"claimed-elsewhere\"\n | \"condition\" | \"deck-gate\" | \"hand-condition\" | \"cooldown\" | \"tags\" | \"taken\";\n}\n\n/** One property the restore could not put back as it was. `flow` names the\n * flow whose half it belongs to; absent, it is the shared half.\n *\n * `path` is the engine's property address, spelled exactly as\n * `Flow.listProperties()` / `Engine.listProperties()` print it and exactly as\n * `getProperty` and `setProperty` accept it: `story.<name>` for the story\n * scope, `<scope>.<ownerGameId>.<name>` for the box, deck, hand and tag\n * scopes. No `@`, which belongs to the expression language and not to an\n * address.\n *\n * The owner segment is its GAMEID (design/engine-server.md 4.4), the name it\n * is called by everywhere else, so an operator reading a hot-swap report can\n * paste the address straight into `setProperty`. An owner the build no longer\n * has keeps the id the save carried: there is no gameId left to give it,\n * which is the rule the eviction list above has always used. */\nexport interface LoadProperty {\n flow?: string;\n path: string;\n}\n\n/** What a load or a flow restore would do that is not a plain restore\n * (design/engine-server.md 4.9). Arrays are sorted, so two runtimes given the\n * same save and bundle produce the same bytes; `flows` alone keeps the\n * envelope's own order, because a caller re-takes its handles in it. */\nexport interface LoadReport {\n /** No drift and nothing dropped, defaulted or retyped: the save goes back\n * exactly as it was. `flows` is not a divergence and does not count. */\n exact: boolean;\n project: string;\n /** Drift when the two differ; reported, never refused. */\n version: { saved: string; bundle: string };\n /** Drift when the two differ; reported, never refused. */\n hash: { saved: string; bundle: string };\n /** The flows this restores, in the order it restores them. */\n flows: string[];\n evicted: LoadEviction[];\n /** Cooldowns held for cards the bundle no longer has. */\n droppedCooldowns: { flow: string; card: string }[];\n /** Shared `redraw: \"never\"` entries for cards the bundle no longer has. */\n droppedSpent: string[];\n /** In the save, not declared any more. */\n droppedProperties: LoadProperty[];\n /** Declared, not in the save: it takes the declaration's default. */\n defaultedProperties: LoadProperty[];\n /** In the save, still declared, but the saved value no longer fits the\n * declaration (its type changed, or an enum value / quality stage was\n * edited away). It takes the declaration's default. */\n retypedProperties: LoadProperty[];\n}\n\n/** The .storyletsave FILE: the HOST's file, not the engine's - the engine's\n * envelope plus, when the host keeps one, its @world container. This is\n * \"host saves its container once, each engine saves its own envelope\"\n * folded into one file for the single-host case; the ENGINE never reads or\n * writes `world` (loadGame takes the envelope alone). */\nexport const SAVEFILE_SCHEMA = \"storylets/savefile@1\";\n\nexport interface SaveFile {\n schema: typeof SAVEFILE_SCHEMA;\n /** The engine's envelope: version 2 when written today, version 1 still read. */\n engine: SaveEnvelope | SaveEnvelopeV1;\n /** The host's @world values, saved and restored by the host. */\n world?: PropertyBag;\n}\n\n// --- source shards (design/storylets-source.md) --------------------------------\n\n/** The project folder: a macOS package, a plain folder elsewhere. */\nexport const PROJECT_FOLDER_EXTENSION = \".storylets\";\n/** The compiled bundle (strict JSON; generated, never hand-edited). */\nexport const BUNDLE_EXTENSION = \".storyletsc\";\n\n/**\n * Where a shipped background sits, relative to the bundle file.\n *\n * One function so the compiler (which writes the name into the bundle) and the\n * export op (which writes the bytes) cannot drift apart: a path agreed in two\n * places is a path that eventually disagrees. One folder for the project, as\n * the source has (`<project>/assets/`), since the project map is the only thing\n * with pictures and there is one of it (design/project-map-contract.md 1.4).\n */\nexport const bundleAssetPath = (file: string): string => `assets/${file}`;\n/** Per-type shard extensions, JSON5 inside (source doc section 2). */\nexport const SHARD_EXTENSIONS = {\n project: \".storyletproj\",\n box: \".storyletbox\",\n tags: \".storylettags\",\n hands: \".storylethands\",\n deck: \".storyletdeck\",\n /** The AUTHOR's arrangement layer: the canvases, where cards sit on a deck's\n * node canvas and the furniture drawn round them. Its own shard because\n * positions churn (an afternoon of tidying a canvas touches every card) and\n * content does not, so a designer arranging and a writer editing never\n * collide on one file (design/graphical-views.md section 1.2). */\n view: \".storyletview\",\n /** The DESIGNER's map, in two places told apart by schema tag. At the\n * project root (`map.storyletmap`, `PROJECTMAP_SCHEMA`) it is the PROJECT\n * MAP: the zone group and the map's furniture, once for the project\n * (design/project-map-contract.md 1.1). In a box folder (`MAP_SCHEMA`) it is\n * that box's SITES: where its hands stand on the project map.\n *\n * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1\n * point 5) because the two halves stopped having one owner. A hand's\n * position ships in the bundle's `map.geometry` (4.3) and is where a venue's\n * kiosk stands, so it is SHAPE, which a server's author key may not change;\n * the canvases are the author's own working drawing and never leave the\n * project folder. One file could not be both. */\n map: \".storyletmap\",\n /** Threaded comments: content-ADJACENT, so neither in a content shard (a\n * writer's deck edit must not conflict with a reviewer's comment) nor in the\n * arrangement sidecar (this is not where anything sits). One per box,\n * id-keyed (design/annotation.md). Documentation NOTES used to share this\n * file and were retired: `purpose` already says why a thing exists, and\n * Patterpad's typed routing has no destination here. */\n notes: \".storyletnotes\",\n /** An installation contract: what a VENUE depends on, one file per\n * installation in `contracts/` at the project root\n * (design/engine-server.md 4.11). Its own shard, and its own folder, for the\n * walkthrough's reason (Reboot 7.5, S4): a different owner, a different\n * change rate, and a merge that must never collide with the author's edits,\n * since the server always wins its own file. */\n contract: \".storyletcontract\",\n /** One extension's data (a Storylet extension's, design: patterkit\n * proposals/patter-storylet-extensions.md): one per extension per box, in the\n * box folder (`<extension id>.storyletextension`), and one at the project\n * root for project-level data. Keyed by node kind and id, merged by the\n * extension's manifest, never interpreted by the engine. Its own shard\n * because a tool's pass and a writer's edit must not collide on one file. */\n extension: \".storyletextension\",\n} as const;\n\n/**\n * An extension's manifest, as a project keeps it: `extensions/<id>.extension.json`\n * at the project root (`@wildwinter/toolkit/extensions`' `MANIFEST_FILE_SUFFIX`). Not a\n * shard: JSON the extension ships, never written by the tools nor put in the\n * canonical form, but SOURCE all the same, so a pack carries it and a project\n * compiles from it.\n */\nexport const MANIFEST_EXTENSION = \".extension.json\";\n/** Where a project keeps its extensions' manifests, relative to the root. */\nexport const EXTENSIONS_DIR = \"extensions\";\n\n/** Every file a project's source is made of: its shards and its manifests. */\nexport const SOURCE_EXTENSIONS: readonly string[] = [...Object.values(SHARD_EXTENSIONS), MANIFEST_EXTENSION];\n\n/** Where the installation contracts live, relative to the project root. The\n * directory is the registry, as it is for a box's decks: a contract exists\n * because its file exists. */\nexport const CONTRACTS_DIR = \"contracts\";\n\nexport const PROJECT_SCHEMA = \"storylets/project@0\";\nexport const BOX_SCHEMA = \"storylets/box@0\";\nexport const TAGS_SCHEMA = \"storylets/tags@0\";\nexport const HANDS_SCHEMA = \"storylets/hands@0\";\nexport const DECK_SCHEMA = \"storylets/deck@0\";\nexport const VIEW_SCHEMA = \"storylets/view@0\";\nexport const MAP_SCHEMA = \"storylets/map@0\";\n/** The project map shard's schema: the root `map.storyletmap`, beside the\n * project shard (design/project-map-contract.md 1.1). The same extension as a\n * box's map shard, told apart by this tag and by sitting at depth one. */\nexport const PROJECTMAP_SCHEMA = \"storylets/projectmap@0\";\n/** The comment sidecar's schema. Still called \"notes\" on disk: the file already\n * held both, and renaming it would break every project for no gain. */\nexport const NOTES_SCHEMA = \"storylets/notes@0\";\nexport const CONTRACT_SCHEMA = \"storylets/contract@0\";\n/** An extension data shard's schema. */\nexport const EXTENSION_SCHEMA = \"storylets/extension@0\";\n\n/**\n * One extension's data for one box, or for the project (the root shard).\n *\n * `entries` is the extension's, by node kind (`card`, `outcome`, `box`, `deck`,\n * `tagGroup`, `tag`, `handTemplate`, `hand`, `project`) and then node id, and\n * the canonical form never reorders anything inside it: its lists are the\n * extension's, in the extension's order. The values are raw, as a tool wrote\n * them; the extension's manifest says their types.\n */\nexport interface ExtensionShard {\n schema: typeof EXTENSION_SCHEMA;\n /** The extension's id: the same as the file's stem. */\n extension: string;\n entries: Record<string, Record<string, Record<string, unknown>>>;\n}\n\n/**\n * The extensions a project takes (the project opts in, ruled 2026-10-18), by id:\n * the version it is pinned to. The manifest itself sits in `extensions/`.\n */\nexport interface ProjectExtension {\n version: string;\n}\n\n/**\n * The reserved key a merged shard carries an unresolved merge under, in every\n * shard type (`@wildwinter/toolkit/extensions`' `UNRESOLVED_KEY`): the clashes, each\n * with its path, its kind and the three values, ours being what the shard now\n * holds. Validation refuses a shard carrying it; resolving removes it.\n */\nexport const UNRESOLVED_KEY = \"unresolved\";\n\n/**\n * What one installation depends on, written by the venue's server and read by\n * `validate` (design/engine-server.md 4.11).\n *\n * NOT THE AUTHOR'S FILE. A venue is provisioned against names - the hands its\n * stations deal, the boxes its scheduler ticks, the properties its clocks drive,\n * the fields its crew read - and the server writes them out so the tools that\n * already gate a build can refuse a rename before it reaches the venue. A\n * project playing at two venues has two of these. The author never edits one,\n * and today, with no server built, a project either receives one or has none.\n *\n * NEVER COMPILED. It is project-side config like `coverage` and `export`: the\n * server does not need its own contract handed back, it needs the bundle to\n * still honour it.\n *\n * BY GAMEID throughout, because a gameId is the name that crosses the project's\n * border and an internal id is authoring identity.\n */\nexport interface ContractShard {\n schema: typeof CONTRACT_SCHEMA;\n /** The installation this contract speaks for. One file per installation, and\n * two files naming the same one is an error. */\n installation: string;\n /** Who wrote it, for a human reading the file (\"Storylet Server 0.1.0\"). */\n by?: string;\n /** The server's revision when it wrote this. */\n revision?: number;\n /** Hands a station is bound to, by gameId: they may not be renamed or\n * removed. */\n hands?: string[];\n /** Timed boxes the venue's scheduler ticks, by box gameId, with the turn unit\n * in SECONDS it was provisioned against. A box whose unit changed means every\n * rest on its cards changed meaning. */\n boxes?: Record<string, { turn: number }>;\n /** Property paths the venue reads or drives, in the engine's own address\n * grammar with no `@` (\"world.time_wall\", \"story.visits\"), which is how\n * `listProperties()` prints them. */\n properties?: ContractProperty[];\n /** Card-template field names the crew and the bridges read. */\n fields?: string[];\n /** Outcome field names they read, the same way: the after-line a station\n * shows when a press lands. What `fields` is to the card template, this is\n * to the box's `outcomeFields`. */\n outcomeFields?: string[];\n}\n\n/**\n * One contracted property.\n *\n * A bare path is the common form and the one the spec's example writes. The\n * object form adds the TYPE the venue was provisioned against, which is the only\n * way `validate` can catch the break that costs a producer most: a property that\n * still exists under the same name and now holds something else. A server that\n * knows the type should write the object form; a hand-written contract may say\n * only the path and get the existence check alone.\n */\nexport type ContractProperty = string | { path: string; type?: PropertyType };\n\n/** The path a contracted property names, whichever form it was written in. */\nexport const contractPropertyPath = (p: ContractProperty): string =>\n typeof p === \"string\" ? p : p.path;\n\n/** The type a contracted property was provisioned against, when it says. */\nexport const contractPropertyType = (p: ContractProperty): PropertyType | undefined =>\n typeof p === \"string\" ? undefined : p.type;\n\n/** A point in a canvas's own coordinates. */\nexport interface ViewPoint {\n x: number;\n y: number;\n}\n\n/**\n * Canvas furniture: what an author draws AROUND the content to make sense of it\n * (design/graphical-views.md 3, \"Frames and sites\").\n *\n * Both canvases carry the same thing, which is why they share a type: a node\n * canvas and a map are different views of different material, but \"put a box\n * round this lot and call it act two\" is the same thought on either.\n *\n * It lives in the view sidecar because it is ARRANGEMENT. Nothing here is\n * content: no runtime reads it, no bundle carries it, and deleting the sidecar\n * loses only the drawing. Threaded comments are the\n * other thing entirely - they attach to entities and they travel - but a canvas\n * DRAWS their markers, while owning none of them.\n */\nexport interface CanvasFurniture {\n /** Titled areas behind the content, back to front (see `stacked`).\n *\n * There was a second kind, a `stickies` list, retired on 2026-08-10\n * (design/annotation.md): a dropped comment marker does the same job in a\n * fraction of the space, and an annotation that takes as much room as the\n * thing it is about is a bad trade on a canvas. */\n frames?: Frame[];\n}\n\n/**\n * A titled area behind a group of things: Unreal's comment box.\n *\n * Deliberately dumb about what is inside it. It has no membership list and\n * computes none: a frame is a thing an author DREW, and the cards under it are\n * whatever happens to be under it now. That is what keeps it honest when content\n * moves, and it is the same reasoning that keeps a zone's sites out of the map's\n * sidecar.\n */\nexport interface Frame extends ViewPoint {\n id: string;\n w: number;\n h: number;\n /** Shown in the frame's bar, and the handle it is dragged by. */\n title?: string;\n /** One of the furniture palette's names (see `FURNITURE_COLOURS`); the theme\n * decides what that looks like, so a frame does not carry a hex value that\n * would fight the palette on the day somebody switches theme. */\n colour?: string;\n /** Place in the frame band (sparse, `stacked`). Frames can nest. */\n z?: number;\n}\n\n/** The furniture palette: names, not colours. The theme maps them, so the same\n * shard reads correctly on linen and on baize. */\nexport const FURNITURE_COLOURS = [\"paper\", \"amber\", \"sage\", \"sky\", \"rose\", \"slate\"] as const;\nexport type FurnitureColour = typeof FURNITURE_COLOURS[number];\n\n/** One deck's node canvas: where its cards sit, and the furniture around them.\n * Sparse throughout. A card with no entry lays out by default, and an entry for\n * a card that no longer exists is inert: kept, and reported by `validate` as a\n * warning per entry (the family's orphan rule, 2026-10-18), never dropped. */\nexport interface DeckCanvas extends CanvasFurniture {\n /** Keyed by CARD id. */\n cards?: Record<string, ViewPoint>;\n /** The deck's colour: its stripe on a node canvas and its chip on a card\n * face. A slot of the 12-colour identity palette (0-11), as a box's\n * `ViewShard.colour` is. Absent, the colour is a hash of the deck's title,\n * which is every deck until it is first renamed: the rename stores the\n * colour the old title had, so the deck keeps it (Ian's ruling,\n * 2026-10-28). Editor-only, like everything in this shard: it never\n * reaches the bundle. */\n colour?: number;\n}\n\n/** The box's map: where its hands stand on the PROJECT map, and nothing else.\n * Carried by the MAP shard since 2026-09-06. The view shard's old `map` is\n * not read since 1.0.0.\n *\n * No furniture since the project map (design/project-map-contract.md 1.3): the\n * frames belong to the one map, in `ProjectMapShard`. A box map shard that\n * still carries `frames` is warned about and read for nothing until\n * `storyletengine format` moves them. */\nexport interface BoxMap {\n /** Keyed by HAND id. WHERE a site is, and nothing else.\n *\n * Which zone it is IN is not recorded here, and deliberately (2026-08-06,\n * with the rebinding drag): a hand that binds a zone already says so in its\n * own shard, as `chosen` or as a rule binding, and that is the truth the\n * runtime deals from. A copy here could only ever go on to disagree with it,\n * and a site whose recorded zone contradicts the hand it stands for would be\n * the most misleading thing on the map.\n *\n * Called `pins` until 2026-08-10 (design/annotation.md). No compatibility\n * branch: the only projects that exist are the examples in this repo, and they\n * were edited. */\n sites?: Record<string, ViewPoint>;\n}\n\n/** The AUTHOR's arrangement layer for one box: where cards sit on their decks'\n * canvases, and the furniture drawn round them.\n *\n * Its own shard on purpose (design/graphical-views.md section 1.2). Positions\n * churn, content does not: an afternoon of tidying a canvas touches every card,\n * and if that lived in the deck shard then a designer arranging and a writer\n * editing card text would collide on one file all day, while a content review\n * would be full of coordinates. Keyed by id throughout so the existing merge\n * engine handles two designers rearranging different things without a conflict.\n *\n * Source-only. It never reaches the compiled bundle, exactly as `order` does\n * not: the compiler reads the fields it names and this is not among them. */\nexport interface ViewShard {\n schema: typeof VIEW_SCHEMA;\n /** Keyed by DECK id: one node canvas each. */\n canvases?: Record<string, DeckCanvas>;\n /** The box's colour on the project map: its layer swatch, its pins and its\n * chips. A slot of the theme's 12-colour identity palette (0-11), not a hex\n * value, so it follows light, dark and the colour themes, as a Patter\n * writing status's `colour` does.\n *\n * STORED, not computed (the surfacing review's round-3 ruling): a box takes\n * the first slot no other box on the map uses when it joins, and keeps it\n * however the boxes are reordered, added or removed, until the author picks\n * another. Arrangement like everything else here, so it never reaches the\n * bundle. */\n colour?: number;\n}\n\n/** The DESIGNER's map for one box: where its hands stand on the project map.\n *\n * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1 point\n * 5). The two halves had stopped sharing an owner: a hand's position ships in\n * the bundle's `map.geometry.sites` (4.3), which makes it the thing a venue\n * provisions its kiosks against, while a deck's canvas is a working drawing\n * that never leaves the folder. A server's author key may change the canvases\n * and not this.\n *\n * The map is NESTED under `map` rather than flattened to the top level, and\n * deliberately: the block's bytes are then exactly what the view shard held, so\n * the migration was a move of a value rather than a reshaping of it, and the\n * merge strategy carried over word for word. The view shard's old `map` is no\n * longer read (1.0.0): a map left there is ignored, and `storyletengine\n * format` drops it.\n *\n * Source-only in the sense the view shard is not: `compileMaps` reads the\n * positions for the bundle's `map.geometry`, under `export.map`, for a box that\n * has opted in to the project map. */\nexport interface MapShard {\n schema: typeof MAP_SCHEMA;\n map: BoxMap;\n}\n\n/**\n * The PROJECT MAP's shard: the root `map.storyletmap` (design/project-map-contract.md\n * 1.1). At most one per project, above the boxes.\n *\n * `group` is the zone group, a `TagGroup` byte for byte the shape a box's tags\n * shard holds, so every reader of a group (the spatial marker, polygons,\n * backgrounds, group-level property declarations) carries over unchanged. It is\n * compiled exactly as a box's group is and ships as the bundle's `map.group`. A\n * box opts in with `usesMap` on its box shard; only an opted-in box's cards and\n * hands may reference it.\n *\n * `frames` is the map's furniture, moved here from every box's map shard:\n * arrangement, never compiled. Sites stay per box, in each box's own map shard,\n * because a hand belongs to one box.\n */\nexport interface ProjectMapShard extends CanvasFurniture {\n schema: typeof PROJECTMAP_SCHEMA;\n group: TagGroup;\n}\n\n/** A coverage input driver: during a coverage run the harness feeds a\n * host-scope property (`@world.x`, `@player.y`) values from `values`, so content gated on\n * external state gets exercised (Patter's coverageDrivers, carried whole). */\nexport interface CoverageDriver {\n /** \"initial\": set once as each playthrough starts. \"recurring\": re-rolled\n * per turn at the cadence, so one run passes through several states. */\n kind: \"initial\" | \"recurring\";\n /** For recurring drivers: how often to re-roll per turn (default \"sometimes\"). */\n cadence?: \"rarely\" | \"sometimes\" | \"often\";\n /** The pool the harness picks from (uniform). Empty = inert. */\n values: ScalarValue[];\n}\n\n/** Authoring-side coverage configuration (never compiled into the bundle). */\nexport interface CoverageConfig {\n /** Property drivers, keyed by ref (\"@world.danger\", \"@player.hp\"): any host scope's. */\n drivers?: Record<string, CoverageDriver>;\n\n}\n\n/** The version control systems a project can be set up for: `@wildwinter/toolkit/vcs`' list, written out here\n * because the model takes no tooling dependency (ops checks the two agree). Absent on a project means none. */\nexport type VcsKind = \"git\" | \"perforce\" | \"plastic\" | \"svn\";\n\nexport interface ProjectShard {\n schema: typeof PROJECT_SCHEMA;\n project: {\n id: string;\n name: string;\n version: string;\n };\n settings: ProjectSettings;\n /** Coverage drivers + argument domains (authoring/testing config; stays\n * out of the compiled bundle). */\n coverage?: CoverageConfig;\n /** Validation switches (authoring config; never compiled). Off is written\n * as ABSENT, like `export.map`: a shard says what an author chose. */\n validation?: {\n /** Also warn when state is WRITTEN but nothing reads it. Off by default:\n * cards are routinely written ahead of the content that will read them,\n * so mid-development this warning is mostly noise. The read side (a gate\n * on state nothing writes) always warns, because that kills cards now. */\n warnUnreadWrites?: boolean;\n };\n /**\n * Where the game's shared scopes folder is, relative to the folder holding this file\n * (`\"../../shared/game-scopes\"`). Authoring config, never compiled, and usually absent: the\n * tools find a `game-scopes/` folder by walking up from the project on their own, stopping at\n * the version-control root, so this is only for a folder that walk would not reach. A path\n * that doesn't exist is a project error. (patterkit design/shared-scopes.md.)\n */\n gameScopes?: string;\n /**\n * The version control system the project is kept in. Authoring config, never compiled. Set, Storyletter\n * talks to that system and no other, rather than guessing from the folders above the project (a project\n * inside an unrelated git checkout would otherwise be staged into it); `\"none\"` is plain writes, no version\n * control at all. ABSENT is \"not chosen\", and Storyletter finds the system from the folders as it always\n * did, which is every project made before the setting. Patter's `ProjectFile.vcs`, except that Patter\n * reads absent as none: its projects have had the setting from the start, and ours have not.\n */\n vcs?: VcsKind | \"none\";\n /**\n * The Patter project this one is paired with: a `.patter` folder, relative to the folder\n * holding this file (`\"../story/the-hamlet.patter\"`). Authoring config, never compiled: the\n * engines still know nothing of each other (Reboot 10). With it, `validate` checks each card\n * against the scene of the same name in the Patter project's published bundle, and\n * Storyletter can open Patterpad at a card's scene. A folder that isn't there is a warning,\n * since a writer may hold the cards without the dialogue.\n */\n patter?: string;\n /**\n * The boxes the game performs through the paired Patter project, by box id: every card in them\n * plays the scene named after it. Authoring config, never compiled, and only meaningful beside\n * `patter`. With it, `validate` checks only these boxes and also reports a card with no scene;\n * Storyletter's Board plays their cards' scenes, and the playable export carries them.\n * Absent, every card that has a scene is checked and none is required to have one.\n */\n patterBoxes?: string[];\n /**\n * The extensions this project takes, by id (ruled 2026-10-18: the project opts\n * in, a box does not). Authoring config: the bundle carries each one's\n * compiled declarations and values, never this block. An extension named here\n * whose manifest is missing is a warning, and its data is left untouched.\n */\n extensions?: Record<string, ProjectExtension>;\n /**\n * The game's own scopes this project declares (`@world`, `@player`, ...), in\n * Patter's shape: a token, a scope-level `writable` (true when absent), and\n * declarations (absent: an opaque scope, any name, unchecked). Where the game\n * has a scopes folder, `game.scopes.json` is the source and this is the\n * project's synced copy. Absent when the project declares none.\n */\n scopeRegistry?: HostScopeRegistry;\n /** The one game scope a project declared before host scopes: read as the\n * scope `world` until `storyletengine format` moves it into\n * `scopeRegistry`. Never written. */\n world?: LegacyWorldBlock;\n story: {\n properties: PropertyDecl[];\n };\n /** Templates of play: configuration bags keyed by template name. Core\n * validates only what it knows. */\n templates: Record<string, unknown>;\n export: {\n bundle: string;\n metadata: \"full\" | \"stripped\";\n /**\n * Does a `.storyletpack` carry the project's binary assets (the project\n * map's background images, in its root `assets/` folder)?\n *\n * Default false, and a project-level DEFAULT rather than a rule: a pack is a\n * delivery, so the caller can override it per pack (2026-08-07). Some\n * projects would benefit from sending their pictures in certain\n * circumstances and others never would, which is why neither \"always\" nor\n * \"never\" is the answer.\n *\n * Nothing to do with the compiled bundle, which has its own switch: `map`.\n */\n packAssets?: boolean;\n /**\n * Does the compiled bundle carry the project map's GEOMETRY (zone shapes,\n * background pictures and each opted-in box's sites, `map.geometry`)? The\n * zone group itself always ships when there is a project map: the engine\n * reads it.\n *\n * Default false, and the default matters: geometry is authoring data, the\n * runtime deals in tag names, and a shipping build should carry nothing it\n * does not use. But a host that wants an in-game map should not have to\n * invent its own export, and it is most useful early - a prototype with a\n * real map beats a prototype with a list of zone names.\n *\n * It sits beside `metadata` on purpose: that is already the switch for\n * \"authoring data that may or may not ship\", and this is its sibling rather\n * than a new concept. With it on, `export` also writes the background files\n * next to the bundle, and `describeBundle` says what is in there.\n */\n map?: boolean;\n };\n}\n\nexport interface BoxShard {\n schema: typeof BOX_SCHEMA;\n box: {\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order among boxes (sparse; absent falls back to the\n * folder-name position). Authoring-only, like a card's (never compiled\n * into the bundle); merges as a per-field value. */\n order?: number;\n ranking: { specificity: boolean };\n /** Who moves the box's clock (see `Box.turn`): present, the game does,\n * with `seconds` a timed box; compiled through unchanged. */\n turn?: { seconds?: number };\n /** The Card template's dealing rules: what a card that sets none takes\n * (the hands-on review, 2026-10). Live, as field defaults are: a card left\n * on the default follows it, a card's own value stays its own. Absent\n * keys take the engine's defaults (Redraw \"always\", weight 1, one slot of\n * one hand, one hand at a time). */\n cardDefaults?: CardDefaults;\n fields: FieldDecl[];\n /** The outcome fields (see `Box.outcomeFields`); a shard without the key\n * declares none. */\n outcomeFields?: FieldDecl[];\n properties: PropertyDecl[];\n /** The box has opted in to the PROJECT MAP (design/project-map-contract.md\n * 1.2): its hands may bind the map's zones and its cards may be tagged\n * with them. Absent is \"not on the map\"; never written false. Compiled\n * through to `Box.usesMap`. */\n usesMap?: true;\n };\n}\n\n/** The box's OWN tag groups: how its cards are filed. Never the project map's\n * zone group, which lives in the root map shard and which a box may not\n * declare a copy of (a spatial group here is a compile error naming\n * `storyletengine format`). */\nexport interface TagsShard {\n schema: typeof TAGS_SCHEMA;\n groups: TagGroup[];\n}\n\n/** The box's hand templates + hands (the writer/programmer contract). */\nexport interface HandsShard {\n schema: typeof HANDS_SCHEMA;\n templates: HandTemplate<string>[];\n hands: Hand<string>[];\n}\n\nexport interface DeckShard {\n schema: typeof DECK_SCHEMA;\n deck: {\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n condition?: string;\n /** Scarce across flows: see Deck.shared. */\n shared?: boolean;\n /** Its `redraw: \"never\"` cards are spent past the run: see Deck.durable. */\n durable?: boolean;\n /** Authored display order within the box (sparse; see BoxShard). */\n order?: number;\n properties: PropertyDecl[];\n };\n cards: Card<string>[];\n}\n\n// --- templates of play --------------------------------------------------------\n// The spatial template's types, field access and geometry. Re-exported here so the\n// package has one entry point, and kept in its own module because core schema and\n// a template of play are different things (Reboot 6).\nexport * from \"./spatial.js\";\n\n// How a hand reaches a tag group, and whether that binding is the hand's own to\n// change. Core schema rather than a template of play, but the map is what needed\n// it said out loud.\nexport * from \"./hands.js\";\n\n// Frames: what an author draws around the content. Arrangement,\n// so it lives in the sidecar and reads forgivingly (furniture.ts says why).\nexport * from \"./furniture.js\";\n\n// Threaded comments: the conversation about a thing, in its own sidecar.\nexport * from \"./comments.js\";\n\n// Guessing a property's type from what an outcome writes: the quick fix's input.\nexport * from \"./infer.js\";\n","// ---------------------------------------------------------------------------\n// The state logger: what changed in the state kernel, as it changes.\n//\n// Both product families shipped one of these, in four runtimes each, and they\n// were not the same shape. The Storylet Engine's was PUSH-based on the\n// PropertyBag audit hook - a write logs the moment it lands, with the previous\n// value straight off the event. Patterplay's diffed whole saveGame() snapshots,\n// so it could only ever say what changed BETWEEN captures, and only for state a\n// save persists. This is the first one, because it is the better one: a diff\n// cannot tell a write from a write-and-write-back, cannot name the reason a\n// host attached to a write, and cannot see a value that changed and changed\n// back.\n//\n// A diff is still needed, and is kept, for everything that is NOT in a bag: a\n// product's own non-property state (turns, cooldowns, visit counts) arrives\n// through the adapter's `extra()` and is diffed on capture. Bags replaced\n// wholesale by a load fire no audit events either, so capture() re-reads and\n// re-mounts.\n//\n// Line format: `${label}${path}: ${from} -> ${to}`, `<unset>` for a value that\n// was not there.\n// ---------------------------------------------------------------------------\n\nimport type { PropertyBag, ScalarValue } from \"./index.js\";\n\n/** A flattened snapshot: path -> value. */\nexport type StateSnapshot = Record<string, ScalarValue>;\n\nexport interface StateChange {\n path: string;\n from: ScalarValue | undefined;\n to: ScalarValue | undefined;\n}\n\n/**\n * One bag on the logger's path space.\n *\n * Named for the LOG, not the bag: a product may already have its own type for enumerating\n * bags (the Storylet Engine's LogMount, which labels a mount \"story\" for its own purposes),\n * and in the ported runtimes both land in one namespace. They are also not the same thing,\n * which the prefix rule below is about.\n *\n * `pathPrefix` is used VERBATIM, separator included, exactly as the bag's own\n * is - it is not a scope token with a dot implied. Omit it and the bag's own\n * `pathPrefix` is used, which is what a product wants whenever its log paths\n * and its property addresses agree.\n *\n * They do not always agree, which is why this can be overridden: Patterplay\n * addresses a scene property `@scene.mood` (relative to the flow's current\n * scene) but has to LOG it as `@scene:kitchen.mood`, because a log covering\n * several scenes needs to say which one.\n */\nexport interface LogMount {\n bag: PropertyBag;\n pathPrefix?: string;\n}\n\n/** What a product supplies: its kernel bags (re-read on every capture, so a\n * product that replaces its bags on load re-mounts), and its non-property\n * state as flattened paths. */\nexport interface StateLoggerAdapter {\n mounts(): LogMount[];\n extra?(): StateSnapshot;\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to console.log. */\n sink?: (line: string) => void;\n /** Prefixed to every line, verbatim (e.g. `\"[board] \"`). */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state. Logs nothing. */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the audited writes already logged as\n * they landed, plus anything that changed WITHOUT an audit event, diffed,\n * logged and re-baselined. */\n capture(): StateChange[];\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\n/** The sorted set of paths that differ between two snapshots. */\nexport function diffState(prev: StateSnapshot, next: StateSnapshot): StateChange[] {\n const changes: StateChange[] = [];\n const paths = new Set([...Object.keys(prev), ...Object.keys(next)]);\n for (const path of [...paths].sort()) {\n const from = prev[path], to = next[path];\n if (JSON.stringify(from) !== JSON.stringify(to)) changes.push({ path, from, to });\n }\n return changes;\n}\n\nconst show = (v: ScalarValue | undefined): string => (v === undefined ? \"<unset>\" : JSON.stringify(v));\n\nconst prefixOf = (m: LogMount): string => m.pathPrefix ?? m.bag.pathPrefix;\n\nexport function createStateLogger(adapter: StateLoggerAdapter, opts: StateLoggerOptions = {}): StateLogger {\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const label = opts.label ?? \"\";\n const emit = (c: StateChange): void => { sink(`${label}${c.path}: ${show(c.from)} -> ${show(c.to)}`); };\n\n const full = (): StateSnapshot => {\n const out: StateSnapshot = {};\n for (const m of adapter.mounts()) {\n const prefix = prefixOf(m);\n for (const [name, value] of Object.entries(m.bag.values)) out[prefix + name] = value;\n }\n Object.assign(out, adapter.extra?.() ?? {});\n return structuredClone(out);\n };\n\n let baseline = full();\n let pushed: StateChange[] = [];\n let mounted: { bag: PropertyBag; off: () => void }[] = [];\n\n const hook = (prefix: string, bag: PropertyBag): (() => void) =>\n bag.onAudit((change) => {\n // The write logs as it lands, `from` straight off the event; the baseline\n // moves with it so capture() never re-reports what was already said.\n const c: StateChange = structuredClone({ path: prefix + change.name, from: change.prev, to: change.next });\n emit(c);\n pushed.push(c);\n baseline[c.path] = structuredClone(change.next);\n });\n\n const mount = (): void => {\n const mounts = adapter.mounts();\n const same = mounted.length === mounts.length && mounts.every((m, i) => mounted[i]!.bag === m.bag);\n if (same) return;\n for (const m of mounted) m.off();\n mounted = mounts.map((m) => ({ bag: m.bag, off: hook(prefixOf(m), m.bag) }));\n };\n mount();\n\n return {\n snapshot: full,\n capture(): StateChange[] {\n // Whatever arrived WITHOUT an audit event: the adapter's non-property\n // paths, and bag values replaced wholesale by a load (which fires none).\n const next = full();\n const diffed = diffState(baseline, next);\n for (const c of diffed) emit(c);\n const changes = [...pushed, ...diffed];\n pushed = [];\n baseline = next;\n mount(); // a load replaces a product's bags; re-hook them\n return changes;\n },\n dispose(): void {\n for (const m of mounted) m.off();\n mounted = [];\n pushed = [];\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport { valueFits } from \"@wildwinter/expr\";\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only TO THE STORY; the HOST still writes it, by passing\n * `{ host: true }` (see `set`). Default is read/write. (`type`/`values` feed\n * validation.)\n *\n * The distinction is the whole point of the flag on a foreign scope, where the\n * value is the game's own: a flag carried in the story's bundle must not lock a\n * game out of its own state. Ruled 2026-09-05, after both products met it - the\n * Storylet Engine's venue clock and Patter's coverage driver were each blocked\n * from the one property they existed to move.\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n /** The author's note on what the property is for, which another tool's picker shows as a tip\n * (shared game scopes, scopes.ts). The registry itself ignores it. */\n purpose?: string;\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n /** The address this property answers to - what getProperty/setProperty take.\n * A bag composes it from its own `pathPrefix` and the name, so a row is\n * self-describing: an examiner can render and write a row without being told\n * separately where it came from.\n *\n * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,\n * because a prefix is not always a bare scope token: the Storylet Engine\n * addresses a deck's properties as `deck.<id>.name`, so the prefix is already\n * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain\n * case. (`@gold` also resolves - splitRef defaults an unqualified name to the\n * patter scope - but it is the shorthand, not the address a row reports.)\n *\n * With no prefix this is just the name. Both families forked this interface\n * to add exactly this field - once per runtime - which is the same reason\n * `stages` is here. */\n path: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\n/** A saved value a refitting load put back to its declaration's default:\n * the property's name, and what the save held. */\nexport interface BagMisfit {\n name: string;\n value: ScalarValue;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies.\n *\n * It has no prototype, so a scope answers only its own properties: on a plain\n * object, `@world.constructor` read the built-in `Object` function rather than\n * \"missing\", and a write to `__proto__` vanished (October 2026 review). */\n readonly values: Record<string, ScalarValue> = Object.create(null) as Record<string, ScalarValue>;\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n /** The address prefix this bag's rows carry, separator included (`@`,\n * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */\n readonly pathPrefix: string;\n\n constructor(\n declarations: ScopeDeclaration[] = [],\n opts?: { normalise?: (name: string) => string; pathPrefix?: string },\n ) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.pathPrefix = opts?.pathPrefix ?? \"\";\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** A name as this bag keys it: its normalisation policy applied. The registry\n * uses it to key quality ladders and the validation schema the bag's own way,\n * so a case-significant (identity) bag is not quietly folded to lower case\n * one layer up. */\n normalise(name: string): string {\n return this.norm(name);\n }\n\n /** Whether a STORY write to this property is allowed: false only for a\n * declaration marked `writable: false`. A host write is always allowed. The\n * registry asks before writing, so it never has to read a refusal out of an\n * error that a listener might have raised instead. */\n writable(name: string): boolean {\n return this.decls.get(this.norm(name))?.writable !== false;\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property unless the caller says it is the\n * HOST (`host: true`), for whom `writable: false` was never a rule - it is\n * the story's promise, not the game's. `silent` and `host` are separate on\n * purpose: one is about who hears the write, the other about who may make\n * it. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string; host?: boolean }): BagChange {\n const n = this.norm(name);\n if (!opts?.host && !this.writable(n)) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n // The listeners as they stood when the write began, each told once, whatever any\n // of them does to the lists meanwhile (unsubscribing itself or another): the\n // contract every port keeps, pinned by the registry corpus.\n for (const audit of [...this.auditors]) audit(change);\n if (!change.silent) for (const fn of [...this.subscribers]) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name, this.pathPrefix));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events.\n *\n * With `refit`, a saved value that no longer fits its declaration (a stage,\n * an enum value or a flag the build has since deleted, or a value of another\n * type) goes back to the declaration's default instead, and is returned with\n * what the save held, in declaration order. Without it, nothing is returned\n * and every value lands as saved. */\n load(values: Record<string, ScalarValue>, opts?: { refit?: boolean }): BagMisfit[] {\n const saved = new Map<string, ScalarValue>();\n for (const [k, v] of Object.entries(values)) {\n const n = this.norm(k);\n // A copy, as every port's load takes one: the caller's blob (a flags list) is not this bag's.\n this.values[n] = structuredClone(v);\n saved.set(n, v);\n }\n const misfits: BagMisfit[] = [];\n if (!opts?.refit) return misfits;\n for (const [name, d] of this.decls) {\n if (!saved.has(name)) continue;\n const value = saved.get(name)!;\n // An enum whose own default is \"\" (none chosen) takes \"\" back, as a host write does.\n if (valueFits(d, value, { noneChosen: d.type === \"enum\" && d.default === \"\" })) continue;\n this.values[name] = structuredClone(defaultFor(d));\n misfits.push({ name, value });\n }\n return misfits;\n }\n}\n\nfunction rowFor(\n d: ScopeDeclaration,\n value: ScalarValue | undefined,\n writable?: boolean,\n name?: string,\n pathPrefix = \"\",\n): PropertyRow {\n const rowName = name ?? d.name.toLowerCase();\n return {\n name: rowName,\n path: pathPrefix + rowName,\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n // `stages` was added to the row so an examiner could offer a quality's ladder\n // instead of a free-text box, and then never populated here: every quality row\n // this function built came out without one. Fixed 2026-09-02.\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n owner?: string;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n norm: (name: string) => string;\n owner?: string;\n}\ntype Entry = OwnedScope | ForeignScope;\n\nconst lowerCase = (name: string): string => name.toLowerCase();\n\n/** Options for an owned scope the registry builds (`defineOwned`). */\nexport interface OwnedScopeOptions {\n /** The address prefix its examiner rows carry, separator included. Defaults\n * to `<token>.`; the address grammar is the product's, not the registry's. */\n pathPrefix?: string;\n /** Name normalisation: lower case by default; a case-significant product\n * passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it (an engine's name). Named in a clash error and carried\n * on examiner rows, so one examiner can group a combined game by engine. */\n owner?: string;\n}\n\n/** Options for a foreign scope (`defineForeign`). */\nexport interface ForeignScopeOptions {\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Name normalisation for the names passed to the resolver: lower case by\n * default; a case-significant product passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it. See `OwnedScopeOptions.owner`. */\n owner?: string;\n}\n\n/** Options for a context or schema built from the registry. */\nexport interface AliasOptions {\n /**\n * Expression token -> registered key. `{ scene: \"patter/flow-2/scene/tavern\" }`\n * makes `@scene` read that instance bag, for this context only.\n *\n * The registry learns nothing about what the token MEANS: which flow, which\n * scene, which deck is an engine's own idea, and the engine names the key per\n * evaluation. It has to be the registry's mechanism rather than the engine\n * patching the context afterwards, because quality ladders and validation are\n * looked up by token too, and an alias applies to all three alike.\n *\n * An alias to a key that is not registered throws: a condition evaluated\n * against a scope that is not there is an engine bug, not a graceful false.\n */\n aliases?: Record<string, string>;\n}\n\n/** Options for `remove`. */\nexport interface RemoveOptions {\n /** Park an owned scope's values, to be handed back when the same key is next\n * registered (a live reload rebuilding an engine). No effect on a foreign\n * scope, whose values were never the registry's. */\n keep?: boolean;\n}\n\n/** Options for `load`. */\nexport interface LoadOptions {\n /** Keep what earlier loads parked, adding this blob's unclaimed sections to\n * it (a section for the same key replaces the parked one). Without it, each\n * load replaces whatever an earlier load parked. For an engine moving an\n * older save's values into a registry the game has already loaded. */\n keepParked?: boolean;\n /** Put each saved value that no longer fits its declaration back to the\n * default, and report it (`takeMisfits`): a save from before a stage, an\n * enum value or a flag was deleted. A section parked for a key nobody has\n * registered keeps the option, and is refitted when that key is claimed. */\n refit?: boolean;\n}\n\n/** A saved value a refitting load put back to its default: where it was, and\n * what the save held. */\nexport interface LoadMisfit {\n scope: string;\n name: string;\n value: ScalarValue;\n}\n\n/**\n * The versioned owned-state fragment.\n *\n * @deprecated Versioning belongs to the save that embeds the values, not to the\n * registry; no engine ever called this. Embed `save()` in your own versioned\n * save instead. Removed at the next breaking release.\n */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\n/** @deprecated See `OwnedStateFragment`. Removed at the next breaking release. */\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n /** Values loaded for keys nobody has registered yet, waiting to be claimed. */\n private readonly parked = new Map<string, Record<string, ScalarValue>>();\n /** The parked keys whose load asked for a refit, applied when they are claimed. */\n private readonly parkedRefit = new Set<string>();\n /** What refitting loads have put back since the last `takeMisfits`. */\n private misfits: LoadMisfit[] = [];\n private rev = 0;\n\n /**\n * A counter that moves whenever a scope is registered or removed, and at no\n * other time: it starts at 0 and each registration or removal adds 1. Values\n * changing does not move it. A caller that caches a context built by\n * `toEvalContext()` rebuilds it when this moves, because the context's set of\n * scopes is fixed when it is built while the values it reads stay live.\n */\n get revision(): number {\n return this.rev;\n }\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n *\n * The third argument may be the path prefix alone (the pre-0.7 form) or an\n * options object.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[], opts?: string | OwnedScopeOptions): this {\n const o: OwnedScopeOptions = typeof opts === \"string\" ? { pathPrefix: opts } : opts ?? {};\n // The scope knows its own token, so its rows can address themselves: `world.hp`.\n // The ADDRESS GRAMMAR is the product's, though, not the registry's - Patterplay\n // writes `@patter.gold` where the Storylet Engine writes `world.gold` - so a\n // caller may say how its addresses look. A bag MOUNTED here keeps whatever prefix\n // its holder gave it: the holder owns the addressing.\n const bag = new PropertyBag(declarations, {\n pathPrefix: o.pathPrefix ?? `${token}.`,\n ...(o.normalise ? { normalise: o.normalise } : {}),\n });\n return this.mountOwned(token, bag, o.owner !== undefined ? { owner: o.owner } : undefined);\n }\n\n /**\n * Attach an EXISTING bag as an owned scope: an engine (or a host) holds the\n * bag and this registry reads, writes, lists and saves it like its own.\n *\n * If values were loaded for this key before anyone registered it, the bag\n * claims them now: laid over its seeded defaults by the bag's own `load` rule.\n */\n mountOwned(token: string, bag: PropertyBag, opts?: { owner?: string }): this {\n this.assertFree(token, opts?.owner);\n this.scopes.set(token, { kind: \"owned\", bag, ...(opts?.owner !== undefined ? { owner: opts.owner } : {}) });\n this.rev++;\n const waiting = this.parked.get(token);\n if (waiting) {\n this.note(token, bag.load(waiting, { refit: this.parkedRefit.has(token) }));\n this.parked.delete(token);\n this.parkedRefit.delete(token);\n }\n return this;\n }\n\n /**\n * Unregister a scope. With `{ keep: true }` an owned scope's values are parked\n * and handed back when the same key is next registered, which is how a live\n * reload hands an engine's state to its replacement. Throws on an unknown key.\n */\n remove(token: string, opts?: RemoveOptions): this {\n const e = this.scopes.get(token);\n if (!e) throw new Error(`unknown scope '@${token}'`);\n if (opts?.keep && e.kind === \"owned\") {\n this.parked.set(token, e.bag.save());\n this.parkedRefit.delete(token);\n }\n this.scopes.delete(token);\n this.rev++;\n return this;\n }\n\n /**\n * Drop parked values nobody claimed. Parked values are kept in the next save by\n * default, so nothing loaded is lost to a flow or deck that simply has not\n * reopened yet; a game that knows they are dead drops them here.\n *\n * With a `prefix`, only keys starting with it are dropped: an engine resetting\n * itself drops its own instance keys (`my-engine/`) and leaves every other\n * engine's alone.\n */\n discardParked(prefix?: string): this {\n if (prefix === undefined) { this.parked.clear(); this.parkedRefit.clear(); }\n else {\n for (const key of [...this.parked.keys()]) {\n if (key.startsWith(prefix)) { this.parked.delete(key); this.parkedRefit.delete(key); }\n }\n }\n return this;\n }\n\n /**\n * The saved values refitting loads (`load(blob, { refit: true })`) have put\n * back to their defaults since the last call, each with its scope, its name\n * and what the save held, in the order they were met; the list is emptied.\n * An engine reports these as a load's drifted values. With `only`, it takes\n * the misfits of the scopes `only` accepts (its own keys), in order, and\n * leaves the rest for their owners: one registry serves several engines.\n */\n takeMisfits(only?: (scope: string) => boolean): LoadMisfit[] {\n if (!only) {\n const taken = this.misfits;\n this.misfits = [];\n return taken;\n }\n const taken = this.misfits.filter((m) => only(m.scope));\n this.misfits = this.misfits.filter((m) => !only(m.scope));\n return taken;\n }\n\n private note(scope: string, found: BagMisfit[]): void {\n for (const m of found) this.misfits.push({ scope, name: m.name, value: m.value });\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n opts: boolean | ForeignScopeOptions = true,\n ): this {\n // A boolean is the pre-0.7 form: the scope-level writable default alone.\n const o: ForeignScopeOptions = typeof opts === \"boolean\" ? { writable: opts } : opts;\n this.assertFree(token, o.owner);\n const norm = o.normalise ?? lowerCase;\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(norm(d.name), d);\n this.scopes.set(token, {\n kind: \"foreign\", resolver, decls, scopeWritable: o.writable ?? true, norm,\n ...(o.owner !== undefined ? { owner: o.owner } : {}),\n });\n this.rev++;\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(e.norm(name));\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown scope.\n *\n * `writable: false` is the STORY's promise, so a story write is refused and\n * a HOST write is not: pass `{ host: true }` from a host's own surface (its\n * `setProperty`, its tooling, a coverage driver) and never from the path an\n * outcome or effect takes. A foreign scope whose resolver has no `set` is\n * refused for everyone, host included - that is not a rule to bypass, it is\n * a game that gave no way to write. */\n set(scope: string, name: string, value: ScalarValue, opts?: { host?: boolean }): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n // Asked first, never caught afterwards: catching whatever the bag's write raised\n // reported a listener's own error (a game's hook) as a read-only refusal.\n if (!opts?.host && !e.bag.writable(name)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.bag.set(name, value, opts?.host ? { host: true } : undefined);\n return;\n }\n const n = e.norm(name);\n if (!e.resolver.set) throw new Error(`'@${scope}.${name}' is read-only`);\n if (!opts?.host && !this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string; owner?: string } & PropertyRow)[] {\n const out: ({ scope: string; owner?: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n const owner = e.owner !== undefined ? { owner: e.owner } : {};\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...owner, ...row });\n } else {\n for (const [n, d] of e.decls) {\n out.push({\n scope: token, ...owner,\n ...rowFor(d, e.resolver.get(n), this.foreignWritable(e, n), n, `${token}.`),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>, opts?: AliasOptions): EvalContext {\n const view = this.view(opts?.aliases);\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of view) scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders(view);\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => {\n const e = view.get(scope);\n return e ? qualities.get(scope)?.get(normOf(e)(name)) : undefined;\n },\n };\n }\n\n /**\n * The scopes an expression sees: every registered key under its own token,\n * then each alias token pointing at its key's entry (an alias shadows a key of\n * the same name). Keys an engine uses for instance bags (`engine/flow-2/...`)\n * are not valid expression tokens, so they are present but unreachable.\n */\n private view(aliases?: Record<string, string>): Map<string, Entry> {\n const out = new Map(this.scopes);\n for (const [token, key] of Object.entries(aliases ?? {})) {\n const e = this.scopes.get(key);\n if (!e) throw new Error(`alias '@${token}' names '${key}', which is not registered`);\n out.set(token, e);\n }\n return out;\n }\n\n /** Every quality declaration's ladder, keyed scope token then name (the\n * scope's own normalisation). */\n private qualityLadders(view: Map<string, Entry>): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of view) {\n for (const [n, d] of declsOf(e)) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(n, d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation. Aliases\n * apply as they do to `toEvalContext`, so a condition written against `@scene`\n * validates against the instance bag the engine names.\n */\n toSchema(opts?: AliasOptions): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.view(opts?.aliases)) {\n const decls = declsOf(e);\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const [n, d] of decls) m.set(n, {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes (foreign scopes are the game's, and the game\n * saves them), as bare bags keyed by token, plus any values still parked, so\n * a save taken before every engine has re-registered loses nothing. The\n * registry knows nothing about game saves: a game embeds this in its own. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n for (const [token, vals] of this.parked) out[token] = structuredClone(vals);\n return out;\n }\n\n /**\n * Restore from a `save` blob. An owned scope lays its section over its current\n * values (the bag's `load` rule). A section for a key nobody has registered\n * yet is PARKED and handed over when that key registers, so a game can load\n * its registry before its engines have reopened their flows or decks. A\n * section for a foreign scope is ignored: those values are the game's.\n *\n * A load replaces whatever was parked before it: it is a whole restore, and\n * residue from an earlier load must not leak into this one.\n *\n * Changed in 0.7.0: sections for unregistered keys used to be dropped.\n */\n load(blob: Record<string, Record<string, ScalarValue>>, opts?: LoadOptions): void {\n if (!opts?.keepParked) { this.parked.clear(); this.parkedRefit.clear(); }\n const refit = opts?.refit === true;\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") this.note(token, e.bag.load(vals, { refit }));\n else if (!e) {\n this.parked.set(token, structuredClone(vals));\n if (refit) this.parkedRefit.add(token); else this.parkedRefit.delete(token);\n }\n }\n }\n\n /**\n * `save()` wrapped with a version stamp.\n *\n * @deprecated Versioning belongs to the save that embeds the values; no\n * engine ever called this. Embed `save()` in your own versioned save.\n * Removed at the next breaking release.\n */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /**\n * Restore from a versioned fragment; an unsupported version throws.\n *\n * @deprecated See `saveFragment`. Removed at the next breaking release.\n */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n /**\n * A token is taken once. There is no reserved-token list: a clash surfaces\n * here, the moment a game combines its engines, which is the only moment\n * anyone knows which engines are present. With owners recorded the error says\n * whose token it already is.\n */\n private assertFree(token: string, owner?: string): void {\n const e = this.scopes.get(token);\n if (!e) return;\n const by = e.owner !== undefined ? ` by ${e.owner}` : \"\";\n const wants = owner !== undefined ? ` (wanted by ${owner})` : \"\";\n throw new Error(`scope '@${token}' is already registered${by}${wants}`);\n }\n}\n\n/** A scope entry's declarations, keyed by its own normalisation. */\nfunction declsOf(e: Entry): [string, ScopeDeclaration][] {\n if (e.kind === \"foreign\") return [...e.decls.entries()];\n return e.bag.declarations().map((d) => [e.bag.normalise(d.name), d]);\n}\n\n/** A scope entry's name normalisation. */\nfunction normOf(e: Entry): (name: string) => string {\n return e.kind === \"foreign\" ? e.norm : (n) => e.bag.normalise(n);\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: {\n readonly type: string; readonly default?: ScalarValue; readonly values?: readonly string[]; readonly stages?: readonly string[];\n}): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n // A property's `string`, and a field's `text` and `multiline` (the family's field types).\n case \"string\":\n case \"text\":\n case \"multiline\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle\n // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the\n // union does not have. Falling off the switch would seed `undefined`, which is not a\n // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the\n // guard and this one did not, which is the drift you only find by removing a duplicate.\n default: return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The state logger, which both product families had written twice each.\n// ---------------------------------------------------------------------------\nexport type {\n StateSnapshot, StateChange, LogMount, StateLoggerAdapter, StateLoggerOptions, StateLogger,\n} from \"./state-logger.js\";\nexport { createStateLogger, diffState } from \"./state-logger.js\";\n","// ---------------------------------------------------------------------------\n// The reference runtime: an Engine is the world + flow manager, and ALL play\n// happens on a Flow handle (design/flows.md; the shape is Patter's, adopted\n// deliberately so a host driving both engines holds two objects with the\n// same grammar). The dealing semantics of design/storylets-schema.md\n// section 3 run per flow, implemented exactly and held to the conformance\n// corpus.\n//\n// The flow model, in one place:\n// - an Engine owns the bundle, every lookup built from it, the SHARED\n// property partitions and the host scopes (@world, ...); a Flow owns its own\n// PRNG, per-box clocks, cooldowns, board, claims, play history and the\n// per-flow property partitions. Flows meet only through shared state.\n// - sharing is a per-property `shared` flag on the declaration (never a\n// scope token): @story defaults shared; box, deck, hand and tag\n// properties default per-flow. Every name is shared XOR per-flow, so a\n// read is a union of two bags and a write routes by name.\n// - every property bag lives in ONE ScopeRegistry per game (the\n// one-registry model): the game hands the engine its registry\n// (EngineOptions.registry) or the engine makes its own and acts as its\n// own game. The shared @story registers under `story`, every other bag\n// under a key starting `storylets/`, which no expression can name.\n// saveGame() carries what is NOT a property, plus the registry's values\n// only when the engine made the registry itself.\n// - the HOST SCOPES (@world, @player, ...: every scope the bundle's\n// scopeRegistry declares) are the game's: each one a resolver it binds\n// (EngineOptions.hostScopes, never saved), a scope it registers in its\n// registry, or, for a standalone engine, a self-backed bag the registry\n// stores and saves.\n// - there is no default flow and no ambient current flow: openFlow(id) is\n// the only way in, an existing id is REPLACED (the old flow closes),\n// and a closed flow's handle is INERT - every verb throws (Patter's\n// stale-handle rule).\n// - engine.getProperty serves host scopes (world.*) and shared refs only; a ref that\n// resolves per-flow throws, naming the fix (Patter's teaching rule).\n// - the PROJECT MAP (design/project-map-contract.md 3) is one zone group\n// above the boxes, in `bundle.map.group` and in no box's `tagGroups`. A\n// box that opts in (`usesMap`) sees its name beside its own groups', so\n// its hands bind zones and its cards are tagged with them by id exactly\n// as with a box group. Each zone is ONE value bag, keyed by the tag's id\n// like any tag's, so a zone property is one value whichever box's hand\n// is dealt to it; the `shared` flag still decides per-flow against one\n// for the engine. What the boxes on a map do NOT share: a hand deals only\n// from its own box's decks, and play history stays the asking box's\n// (D7). A bundle that breaks the map's rules is refused at construction\n// (3.8) rather than half-played.\n//\n// Key dealing contracts, unchanged from round 2 (all per flow now):\n// - two verbs: deal(hand) claims, peek(box, criteria) just looks; you can\n// never play a card you only peeked (3.1, look/use rule)\n// - availability order: hand condition -> deck gate -> taken -> cooldown ->\n// tags -> card condition -> claims -> priority (3.1), the order an ask\n// reaches the verdicts below in\n// - claims are physical WITHIN a flow: a card sits in at most `copies`\n// hands of that flow's board at once, at most once in any one hand; the\n// ledger is derived from the board contents (3.5)\n// - a SHARED card (its deck's flag, or its own overriding it) is scarce\n// across flows too: at most `sharedCopies` hands anywhere, counted over\n// every live flow's board, and a shared `redraw: \"never\"` is spent for\n// everyone the first time anyone plays it. A finite redraw deliberately\n// stays per flow - a cooldown is an absolute turn of a per-flow clock, so\n// there is nothing shared to compare it against (design/shared-scarcity.md)\n// - the reserved home group inverts the wildcard: a homed card is\n// available only to an ask binding its home (2.4)\n// - ranking: priority desc -> specificity desc (box toggle) -> seeded\n// shuffle of each maximal tie run (3.2)\n// - one PRNG per flow: expression random(), tie shuffles and the batch\n// deal's hand-order shuffle all advance it; state lives in the save (3.3)\n// - each box has its own turn counter PER FLOW; cooldowns are absolute\n// next-eligible turns of the card's box's clock, set at play time from\n// the post-advance turn; \"never\" is MAX_SAFE_INTEGER, not Infinity (3.4)\n// - @hand composes bound-tag props -> hand props -> chosen tags/criteria\n// (by group name), later shadowing earlier; writes route back to their\n// source; criteria names cannot be written (3.6)\n// - outcome availability is never snapshotted: outcomes() and play()\n// evaluate gates against current state (3.1, 3.7)\n// - a trace event fires after the state it reports has landed, so a\n// handler reading the flow inside it sees the effect (the Live Link's\n// board snapshot depends on this; the shared fixture pins it)\n// ---------------------------------------------------------------------------\n\nimport { deserialiseAst, evaluate, valueFits } from \"@wildwinter/expr\";\nimport type { EvalContext, ExprNode, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\nimport { matchedSpecificity } from \"@wildwinter/expr-specificity\";\nimport { storyletsDialect, NEVER_PLAYED } from \"@storylet-studio/dialect\";\n\n/** The play-history indexes' key for one (box, group, tag) triple.\n *\n * The BOX is in it because play history is box-specific (design/project-map-\n * contract.md 3.7, D7, ruled 2026-10-01): `count_played_in` asks about the\n * asking box's own plays. A box group was already box-unique, so its key never\n * needed the box; a project-map zone is one tag every opted-in box tags its\n * cards with, and without the box a play of a newspaper at the quay in one box\n * would count as an encounter at the quay in another.\n *\n * A UNIT SEPARATOR (U+001F) joins them: ids are letters, digits and\n * underscores, so a control character cannot occur in one and two different\n * triples can never collide into one key the way a \".\" or \":\" join could. NUL\n * would say the same thing and was the first choice, but GDScript will not\n * carry one in a string - it substitutes U+FFFD and warns on every parse - and\n * the four runtimes keep the same spelling. */\nconst tagKey = (boxId: string, groupId: string, tagId: string): string =>\n `${boxId}\\u001f${groupId}\\u001f${tagId}`;\n\nimport type { StoryletsHost } from \"@storylet-studio/dialect\";\nimport {\n BUNDLE_SCHEMA, BUNDLE_SCHEMAS, PLACE_GROUP, bundleHostScopes, allTagGroups, ambiguousValueAddressMessage, effectiveGameId, groupsOfBox,\n isHoleRef, parseHoleRef, valueAddresses, zoneQualifiedValueAddressMessage,\n} from \"@storylet-studio/model\";\nimport type {\n Box, Bundle, BundleContent, Card, Deck, Expression, FlowSave, Hand, HandTemplate, HostScopeDecl, HostScopeSpec,\n LoadEviction, LoadProperty, LoadReport, NodeExtensions, PlayRecord, PropertyBag, PropertyDecl, PropsPartition,\n SaveEnvelope, SaveEnvelopeV1, Tag, TagGroup,\n} from \"@storylet-studio/model\";\nimport { SAVE_SCHEMA, SAVE_SCHEMA_V1, SAVE_SCHEMA_V2 } from \"@storylet-studio/model\";\nimport { PropertyBag as StateBag, ScopeRegistry, defaultFor } from \"@wildwinter/scoperegistry\";\nimport type { LoadMisfit, PropertyRow, ScopeDeclaration } from \"@wildwinter/scoperegistry\";\nimport { makePrng, shuffleInPlace } from \"./prng.js\";\nimport type { Prng } from \"./prng.js\";\n\n/**\n * The error the engine throws when it refuses something itself: an unknown\n * box, hand, card or flow, a closed flow, a gated-shut outcome, a write it will\n * not make, a bundle or save it cannot read. A host can tell these apart from a\n * fault in its own code with `instanceof StoryletError`; the message is the\n * same text it always was. Errors the shared property kernel raises reach the\n * host as they are.\n */\nexport class StoryletError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"StoryletError\";\n }\n}\n\nexport interface EngineOptions {\n /** Default seed for each flow's PRNG; override per flow in openFlow\n * (cross-runtime determinism, schema 3.3). Default 0. */\n seed?: number;\n /** Retain each flow's event log for introspection - the game-engine seam\n * (schema 5): every trace event, sequence-stamped and turn-stamped where\n * the event has a box context. `true` keeps the default 1000 entries\n * (oldest dropped first). Off by default; subscribeTrace stays the\n * zero-retention stream. */\n log?: boolean | { cap?: number };\n /**\n * The host scopes the game binds, by token: for each, a resolver over\n * values the game owns and the story reads (and, where `set` is offered,\n * writes), such as `{ world: { get, set } }` for `@world.*`. A declared\n * scope left unbound is self-backed from its defaults, as a property the\n * registry stores and saves. Engine-level, shared by all flows, never\n * saved: the game keeps these values. A game running several engines\n * registers its scopes in its registry itself instead, and binds none\n * here. Tokens are case-sensitive: a token the bundle doesn't declare is\n * reported (`setup`), naming the declared spelling when only the case\n * differs. Patterplay's option, name for name, on every runtime.\n */\n hostScopes?: Record<string, HostScope>;\n /**\n * The game's registry: ONE per game, holding every engine's properties\n * except those the game keeps itself, saved once. Given one, the engine\n * registers its own scopes in it (@story under `story`, every other bag\n * under a key starting `storylets/`, and each scope `hostScopes` binds),\n * reads every other scope from it, and `saveGame()` leaves the property\n * values to the game. The host scopes the bundle declares are then the\n * game's to register: owned if the registry should store them, foreign if\n * the game keeps them. Omit it and the engine makes its own registry and\n * acts as its own game: it self-backs every host scope the bundle\n * declares, and `saveGame()` carries the registry's values too.\n */\n registry?: ScopeRegistry;\n /**\n * Diagnostics hook (opt-in, dev tooling only): fired when `openFlow` REPLACES\n * a flow that still had cards dealt, with the flow id and how many. The\n * behaviour is unchanged - replacing is deliberate and the same in Patter -\n * this only makes it observable, because the case it catches is a host\n * calling `openFlow` straight after `loadGame` to \"re-take\" its handle and\n * silently discarding the hand the save just restored (`getFlow` is the\n * call). Zero cost when unset; leave it unset in shipped games.\n */\n onReplacedFlow?: (id: string, dealt: number) => void;\n /** Told of every condition the engine could not evaluate and counted as\n * false (see {@link PlayError}), whether or not anything is tracing, and of\n * each set-up problem (reported as the constructor ends). Default: a console\n * warning. Patterplay's `onError`, the same shape. */\n onError?: (error: PlayError) => void;\n /** Told what the registry's refitting loads put back for this engine: a saved\n * value that no longer fits its declaration (a stage, an enum value or a flag\n * deleted since the save, or a value of another type) goes back to its\n * default, and each one comes here with what the save held. A game that\n * loads its own registry passes `{ refit: true }` and hears its Storylets\n * share here as the engine's bags claim it (at construction, as a flow opens,\n * and as a save loads); the engine's own `loadGame` reports drift in its\n * LoadReport as before. Only this engine's keys (`story`, `storylets/...`,\n * the host scopes it self-backs): the other engines on the registry take\n * theirs. Default: a console warning per value, so a drifted save is never\n * silent. Patterplay's `onRefit`, the same shape (ruling 3, 2026-10-28). */\n onRefit?: (misfits: LoadMisfit[]) => void;\n}\n\n/**\n * A host scope's resolver: a `{ get, set? }` the game provides so the story can\n * read (and, if you allow it, write) values the game owns, `@world.*` among\n * them, at run time. Property metadata (types, read-only) comes from the\n * compiled bundle's declarations; the values themselves live in the game and\n * are never stored or saved by this engine. Patterplay's `HostScope`.\n */\nexport type HostScope = ScopeResolver;\n\n/** Every key `EngineOptions` takes: another is reported (`setup`), since a\n * plain-JS caller has no type check. */\nconst ENGINE_OPTIONS = new Set([\"seed\", \"log\", \"hostScopes\", \"registry\", \"onReplacedFlow\", \"onError\", \"onRefit\"]);\n\n/**\n * A content error the engine played through. Content can fail at run time in\n * ways the compiler cannot see: a host value of the wrong type, a resolver that\n * throws, a division by zero. A deal never stops for one: the condition counts\n * as false, so the card, deck or hand is not available, and the error is\n * reported here and as a `diagnostic` trace event. The same rule and the same\n * shape as Patterplay's, on all four runtimes (the hands-on review, 2026-10,\n * foundation 5: an error used to reach only a trace, so with tracing off a\n * resolver typo looked like a card that never came up). The engine also\n * reports how it was set up (`setup`: a host scope the bundle doesn't\n * declare, an option it doesn't take), as the constructor ends.\n */\nexport interface PlayError {\n /** The flow it happened in; \"\" for a `setup` report, which belongs to no flow. */\n flow: string;\n /** What failed: a condition (a Storylets deal evaluates only conditions), or\n * how the engine was set up (`setup`). */\n kind: \"condition\" | \"setup\";\n /** Whose condition: `card <gameId>`, `deck <gameId>`, `hand <gameId>` or\n * `outcome <card gameId>/<outcome gameId>`. For `setup`, the host-scope\n * token or option key at fault. */\n node: string;\n /** The expression's source text, when the bundle carries it. */\n source?: string;\n message: string;\n}\n\nexport interface OpenFlowOptions {\n /** Seed for this flow's PRNG (defaults to the engine's `seed`). */\n seed?: number;\n /**\n * Open this flow AS IT WAS: a blob from `saveFlow`, applied to the freshly\n * opened (or replaced) flow before the handle comes back\n * (design/engine-server.md 4.1).\n *\n * An option on `openFlow` rather than a `Flow.restore` verb on purpose:\n * restoring INTO a running flow is the trap hosts keep falling into\n * (openFlow REPLACES), and \"open this flow as it was\" is one act. Drift is\n * tolerated exactly as `loadGame` tolerates it, with one addition, because\n * this restore lands in a LIVE engine: a shared card whose world copies are\n * all held by the OTHER open flows is not put back, and is reported as\n * `claimed-elsewhere`. Ask `previewFlowRestore` first to see that coming.\n */\n restore?: FlowSave;\n /**\n * Open this flow with its POCKET in: a durable half from `flow.saveDurable()`,\n * written into the fresh flow before the handle comes back (ruling H). The\n * flow starts as any new flow does, on its own seed and clocks, with the\n * pocket's values laid over its defaults and its durable spends spent.\n * What no longer fits this build is reported through `onRestoreReport`, as\n * `loadDurable` reports the engine's half.\n *\n * Refused together with `restore`: a restore already carries the flow's\n * durable values, so a pocket beside it would be a second answer for the\n * same property, and which one won would be a rule nobody could see.\n */\n durable?: DurableSave;\n /** Handed the report of a `restore` or a `durable` as it happens. For a\n * restore it is the same report `previewFlowRestore` returns for the same\n * blob. Ignored without either; the report has nowhere else to go, since\n * `openFlow` returns the handle. */\n onRestoreReport?: (report: LoadReport) => void;\n}\n\n/** The schema tag every durable half carries, so the shape can change later\n * and a runtime can tell which one it was given. */\nexport const DURABLE_SCHEMA = \"storylets/durable@1\";\n\n/**\n * One DURABLE HALF (ruling H, 2026-10-06): what outlives a run. The engine's\n * half (`engine.saveDurable()`) is the installation's memory, a flow's half\n * (`flow.saveDurable()`) is the player's pocket, and the two have one shape.\n *\n * Keyed the way everything host-facing is, because a half is meant to cross\n * builds: a value by its property ADDRESS, exactly as `listProperties()`\n * prints it and `setProperty` takes it (`story.souls`, `hand.the-elder.met`,\n * `value.harbour/docks.lamps`), and a spend by the card's GAMEID. A save keys\n * its property values by internal id so they survive a rename (its cards,\n * hands and boxes by Game ID, from save version 3); a durable half keys by name\n * so a person can read it, and a rename shows up in the report instead.\n */\nexport interface DurableSave {\n schema: typeof DURABLE_SCHEMA;\n /** The build it was taken from: a load reports drift against it, and\n * refuses another project's. */\n content: BundleContent;\n /** Every durable property on this half, by address, keys sorted. */\n values: Record<string, ScalarValue>;\n /** The durable `redraw: \"never\"` spends on this half, by card gameId,\n * sorted. */\n spent: string[];\n}\n\n/** A card view in a dealt hand or a peeked list. Carries NO outcome\n * availability - ask `outcomes()` for current truth (schema 5). */\nexport interface DealtCard {\n id: string;\n gameId: string;\n title?: string;\n purpose?: string;\n fields?: Record<string, ScalarValue>;\n /** The card's Storylet extension values, by extension id, exactly as the\n * bundle carries them: raw and sparse, any JSON, never read by the engine.\n * Absent when it has none. `effectiveFields(extensionDecls(...), ...)`\n * fills in defaults. READ-ONLY: these are the bundle's own values, handed\n * out uncopied (unlike `fields`), so changing them changes them for every\n * later deal. */\n extensions?: NodeExtensions;\n}\n\nexport interface OutcomeView {\n id: string;\n gameId: string;\n title?: string;\n purpose?: string;\n /** The outcome's fields, exactly as the bundle carries them: game data\n * declared by the box's `outcomeFields`, never read by the engine. */\n fields?: Record<string, ScalarValue>;\n /** The outcome's Storylet extension values, by extension id, raw and\n * read-only, as a card's are. Absent when it has none. */\n extensions?: NodeExtensions;\n /** Evaluated against CURRENT state at the moment of the ask. */\n available: boolean;\n}\n\n/** What a peek returns: the top of the stock, looked at and put back.\n * The engine has no pick policy (Reboot 2.1). */\nexport interface RankedList {\n box: string;\n cards: DealtCard[];\n}\n\nexport interface PlayOptions {\n /** Turn advance override; default settings.playAdvancesTurns, or 0 when the\n * card's box is timed (design/engine-server.md 4.8). */\n advanceTurns?: number;\n}\n\n// --- the trace (schema 5): the deal/play log for tooling ----------------------\n\n/** Why a card did or did not make an ask, in availability order (schema 3.1). */\n/** Why a held card left its hand: an `evict` trace event's reason, and a\n * load report's when the load checks what it put back. */\nexport type EvictReason = CheckReason | \"outranked\";\n/** The reasons a held card fails the check a deal and a load both make. */\ntype CheckReason = \"hand-condition\" | \"vanished\" | \"deck-gate\" | \"taken\" | \"cooldown\" | \"tags\" | \"condition\";\n\nexport type TraceVerdict =\n | \"dealt\" // in the hand / the returned list\n | \"capped\" // eligible, ranked below the size cap\n | \"cooldown\" // schema 3.1 step 1\n | \"deck-gate\" // step 2\n | \"tags\" // step 3 (incl. the home group's inverted default)\n | \"condition\" // steps 4-5 (a failing or erroring condition)\n | \"priority\" // a priority expression errored or was not a number\n | \"claimed\" // step 6: no free copy on YOUR board\n | \"claimed-elsewhere\" // step 6: another flow holds the world's copies\n | \"taken\"; // a shared redraw:never was spent, by anyone, for everyone\n\n/** One event on the deal/play log - \"why did Ambush at the ford get dealt\n * here?\" is answered by the ask event's per-card verdicts and keys. The\n * verb is the event type, so a peek is distinguishable from a deal when\n * reading a run back.\n *\n * IDENTITY IS BY GAMEID throughout (design/engine-server.md 4.4). It was\n * mixed until then: `deal.hand` and `peek.box` were gameIds while\n * `evict.hand`, `play.card` and every `cards[].id` were internal ids, so\n * every consumer outside the engine - the Board, the four examiners, the\n * Live Link, a wire a kiosk reads - mapped one to the other itself. */\nexport type TraceEvent =\n | {\n type: \"deal\";\n /** Hand gameId. */\n hand: string;\n /** `id` is the card's GAMEID (design/engine-server.md 4.4). */\n cards: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[];\n }\n | {\n type: \"peek\";\n /** Box gameId. */\n box: string;\n criteria: Record<string, string>;\n /** `id` is the card's GAMEID (design/engine-server.md 4.4). */\n cards: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[];\n }\n /** Hand and card gameIds. A card the build no longer has (`vanished`) has\n * no gameId left and is named by the id the board carried. */\n | { type: \"evict\"; hand: string; card: string; reason: TraceVerdict | \"hand-condition\" | \"vanished\" | \"outranked\" }\n /** A card the game took out of a hand without playing it (`discard`):\n * card and hand gameIds. */\n | { type: \"discard\"; hand: string; card: string; turn: number }\n /** Card and outcome gameIds; `outcome` is \"\" for a card with no outcomes,\n * played with none. */\n | { type: \"play\"; card: string; outcome: string; turn: number }\n /** One landed outcome change; `path` is the resolved store location, in the\n * address grammar `getProperty` takes - the owner segment is its gameId (a\n * routed @hand write shows where it actually went, schema 3.6). `prev`\n * is the value it replaced, so a log can read \"0 -> 1\". */\n | { type: \"write\"; target: string; path: string; value: ScalarValue; prev?: ScalarValue }\n /** An explicit clock advance via advanceTurns (schema 3.4); `turn` is the\n * box's new value. Plays stamp their own turn on the play event. */\n | { type: \"turns\"; box: string; turn: number }\n /** An expression eval error: never a silent pass (schema 3.1), always a\n * visible diagnostic. */\n | { type: \"diagnostic\"; where: string; message: string };\n\nexport type TraceHandler = (event: TraceEvent) => void;\n/** The engine-level tap: every flow's events, tagged with the flow id -\n * the tools' one stream. */\nexport type EngineTraceHandler = (flow: string, event: TraceEvent) => void;\n\n/** A retained log entry: the trace event plus its place in flow time.\n * `seq` orders the whole flow (monotonic; survives clearLog). `turn` is\n * the clock of the box the event happened in when it fired (peek: the box;\n * deal/evict: the hand's box; play and its writes: the played card's box,\n * stamped together with the play's own turn). Diagnostics carry no turn. */\nexport type LogEntry = TraceEvent & { seq: number; turn?: number };\n\n/** One entry on the ENGINE's log: the same event, plus the flow it happened\n * in. A run is several flows over shared state, so \"what happened in this\n * run\" is only answerable in one ordered stream, and only if each line says\n * who. The flow's own log stays flow-local and unchanged. */\nexport type EngineLogEntry = LogEntry & { flow: string };\n\n// --- internals ---------------------------------------------------------------\n\ninterface CardEntry {\n card: Card<Expression>;\n deck: Deck<Expression>;\n box: Box<Expression>;\n}\n\n/** Is this card scarce across flows (design/shared-scarcity.md)? The deck says\n * what the pile is for and the card may override it, the same inheritance a\n * property has with its scope default. Hoist the deck's flag out of a card\n * loop and pass the answer down: the ask runs this per card per deal. */\nconst cardIsShared = (card: Card<Expression>, deckShared: boolean): boolean =>\n card.shared ?? deckShared;\n\n/** A card whose spend outlives the run: `redraw: \"never\"`, with `durable` on\n * the card or, failing that, its deck, the same fall-through `shared` has.\n * Only `never` can cross a run boundary: a finite cooldown is an absolute turn\n * of a clock that restarts with the run. */\nconst cardIsDurable = (card: Card<Expression>, deck: Deck<Expression>): boolean =>\n card.redraw === \"never\" && (card.durable ?? deck.durable ?? false) === true;\n\n/** How many hands ACROSS EVERY FLOW may hold this at once. Defaults to\n * `copies`, so the common case writes one number and only \"five in the world,\n * one to a customer\" needs both. Meaningless on an unshared card, and the\n * compiler warns when one sets it. */\nconst sharedCap = (card: Card<Expression>): number => card.sharedCopies ?? acrossLimit(card);\n\n/** How many hands of one flow may hold a card at once: one, or any number\n * (`acrossHands`), or a bundle from before it, its `copies`. */\nconst acrossLimit = (card: Card<Expression>): number =>\n card.acrossHands === \"any\" ? Infinity : card.acrossHands === \"one\" ? 1 : card.copies ?? 1;\n\n/**\n * One run of equally ranked cards, in the order a deal takes them (the\n * hands-on review, 2026-10, rulings 1 and 4). Cards the hand already holds\n * come first, in the order they were scored, so a re-ranking deal never swaps\n * one for an equal. The rest are shuffled, as runs always were, when their\n * weights are all equal; otherwise drawn by weight, one draw per place but the\n * last: each place takes the card whose share of the remaining weight the draw\n * falls in, walking the remaining cards in scored order. Both forms draw\n * (rest - 1) numbers, held cards none, so unweighted content deals exactly\n * as it did.\n */\nfunction orderRun<T extends { entry: CardEntry }>(run: T[], held: ReadonlyMap<string, number> | undefined, prng: Prng): T[] {\n const first = held === undefined ? [] : run.filter((s) => held.has(s.entry.card.id));\n const rest = held === undefined ? run : run.filter((s) => !held.has(s.entry.card.id));\n const weight = (s: T): number => s.entry.card.weight ?? 1;\n if (rest.length > 1 && rest.some((s) => weight(s) !== weight(rest[0]!))) {\n const drawn: T[] = [];\n const left = [...rest];\n while (left.length > 1) {\n let total = 0;\n for (const s of left) total += weight(s);\n let r = prng.next() * total;\n let k = 0;\n while (k < left.length - 1 && r >= weight(left[k]!)) { r -= weight(left[k]!); k++; }\n drawn.push(left.splice(k, 1)[0]!);\n }\n drawn.push(left[0]!);\n return [...first, ...drawn];\n }\n if (rest.length > 1) shuffleInPlace(rest, prng);\n return [...first, ...rest];\n}\n\n/** How many slots of one hand a card may fill (`inOneHand`, default 1). */\nconst inOneHandOf = (card: Card<Expression>): number => card.inOneHand ?? 1;\n\ntype HandSource = { kind: \"value\"; id: string } | { kind: \"hand\"; id: string } | { kind: \"criteria\" };\n\n/** One outcome change, resolved and checked but not yet landed. `path` is\n * the store location the trace reports, in the address grammar. */\ntype WritePlan =\n | { kind: \"host\"; host: HostScopeState; name: string; path: string }\n | { kind: \"bag\"; bag: StateBag; name: string; path: string }\n | { kind: \"scope\"; scope: string; name: string; path: string };\n\n/** The composed @hand for one ask: the read bag, plus where each name\n * routes on write (schema 3.6). */\ninterface HandEnv {\n bag: PropertyBag;\n sources: Map<string, HandSource>;\n /** tag group id -> bound tag id (home included, its \"tag\" a hand id). */\n boundTags: Map<string, string>;\n}\n\n/** One ask, resolved: a deal (hand present, condition from its template or\n * rule) or a peek (criteria only, no condition - schema 3.1). */\ninterface AskDescriptor {\n box: Box<Expression>;\n hand?: Hand<Expression>;\n condition?: Expression;\n /** tag group id -> tag id, everything the ask binds (fixed + chosen +\n * criteria; for deals also home -> the hand's own id). */\n boundTags: Map<string, string>;\n /** Chosen tags / criteria surfaced into @hand by group gameId, the tag's\n * gameId as the value (schema 3.6). */\n askNames: Record<string, string>;\n}\n\n/** The owner label on everything this engine registers: named in a clash\n * error and carried on the registry's examiner rows. */\nconst OWNER = \"Storylet Engine\";\n\n/** Identity: storylets property names are case-significant as authored. */\nconst identity = (n: string): string => n;\n\n/** A property path as the game may write it (ruling 3, 2026-10-28: Patter's\n * form as well as this engine's). `story.gold` and `@story.gold` are the same\n * path, as are `world.time` and `@world.time`; a bare `@gold` is the story's,\n * as it is in an expression. Anything else is passed through as it was. */\nfunction propertyPath(path: string): string {\n if (!path.startsWith(\"@\")) return path;\n const rest = path.slice(1);\n return rest.includes(\".\") ? rest : `story.${rest}`;\n}\n\n/** The registry keys this engine's bags live under. An id is escaped (`%` and\n * `/`) so no two keys can meet. Every runtime writes the same keys: they are\n * in the save. Owners are keyed by INTERNAL id, as the save always was, so a\n * save survives a rename. */\nconst esc = (id: string): string => id.replace(/%/g, \"%25\").replace(/\\//g, \"%2F\");\nconst unesc = (id: string): string => id.replace(/%2F/g, \"/\").replace(/%25/g, \"%\");\nconst sharedKey = (kind: OwnedScope, id: string): string => `storylets/${kind}/${esc(id)}`;\nconst flowPrefix = (flowId: string): string => `storylets/flow/${esc(flowId)}/`;\nconst flowKey = (flowId: string, kind: FlaggedScope, id?: string): string =>\n kind === \"story\" ? `${flowPrefix(flowId)}story` : `${flowPrefix(flowId)}${kind}/${esc(id!)}`;\n\n/** A registry section's values: registry key -> name -> value. */\ntype Sections = Record<string, PropertyBag>;\n\nconst emptyPartitionValues = (): PropsPartition => ({ story: {}, box: {}, deck: {}, hand: {}, value: {} });\n\n/** Sort a registry save's sections back into partitions, for the load walk:\n * the shared ones, each flow's (only the flows the save restores; the rest\n * are dropped), and everything that is not this engine's, passed through. */\nfunction partitionsFromSections(sections: Sections, flowIds: Set<string>): {\n shared: PropsPartition; flows: Map<string, PropsPartition>; rest: Sections;\n} {\n const shared = emptyPartitionValues();\n const flows = new Map<string, PropsPartition>();\n const rest: Sections = {};\n const flowOf = (escaped: string): PropsPartition | undefined => {\n const id = unesc(escaped);\n if (!flowIds.has(id)) return undefined;\n let p = flows.get(id);\n if (!p) { p = emptyPartitionValues(); flows.set(id, p); }\n return p;\n };\n for (const [key, values] of Object.entries(sections)) {\n let m: RegExpExecArray | null;\n if (key === \"story\") shared.story = values;\n else if ((m = /^storylets\\/(box|deck|hand|value)\\/([^/]+)$/.exec(key))) shared[m[1] as OwnedScope][unesc(m[2]!)] = values;\n else if ((m = /^storylets\\/flow\\/([^/]+)\\/story$/.exec(key))) { const p = flowOf(m[1]!); if (p) p.story = values; }\n else if ((m = /^storylets\\/flow\\/([^/]+)\\/(box|deck|hand|value)\\/([^/]+)$/.exec(key))) {\n const p = flowOf(m[1]!); if (p) p[m[2] as OwnedScope][unesc(m[3]!)] = values;\n } else if (!key.startsWith(\"storylets/\")) rest[key] = values;\n }\n return { shared, flows, rest };\n}\n\n/** A cleaned partition as registry sections, keyed the way its bags register.\n * Empty sections are left out: they would load nothing, and a section for a\n * bag that never registers would wait in the registry for ever. */\nfunction sectionsOf(p: PropsPartition, keyOf: (kind: FlaggedScope, id?: string) => string, out: Sections): void {\n if (Object.keys(p.story).length > 0) out[keyOf(\"story\")] = p.story;\n for (const kind of OWNED_SCOPES) {\n for (const [id, values] of Object.entries(p[kind])) if (Object.keys(values).length > 0) out[keyOf(kind, id)] = values;\n }\n}\n\n// Stores are shared-kernel bags (@wildwinter/scoperegistry, the properties\n// implementer Patter shares): identity normalisation because storylets\n// property names are case-significant as authored.\n// `pathPrefix` carries its own separator, so a bag composes its rows' addresses itself\n// (`story.gold`, `deck.tavern.drawn`) instead of every caller pasting a prefix onto a row.\nconst bagFromDecls = (decls: PropertyDecl[], pathPrefix: string): StateBag =>\n new StateBag(decls, { normalise: identity, pathPrefix });\n\n/** One name read from a bag as an OWN value. The bag's `values` is an ordinary\n * object, so a plain index would answer `constructor`, `toString` and the rest\n * of Object.prototype with a function: an expression reading `@deck.constructor`\n * must find nothing there, as it would in every other runtime. Identity\n * normalisation (above) means the authored name IS the key. */\nconst ownValue = (bag: StateBag | undefined, name: string): ScalarValue | undefined =>\n bag !== undefined && Object.prototype.hasOwnProperty.call(bag.values, name) ? bag.values[name] : undefined;\n\n/** The scope an expression sees where there is nothing to read (@deck in a\n * hand condition, a box or deck with no reader): empty, and with no prototype,\n * so no built-in property answers either. */\nconst EMPTY_SCOPE: PropertyBag = Object.freeze(Object.create(null) as PropertyBag);\n\n// Truthiness for a bare condition. Booleans and numbers as you would expect;\n// a string passes when non-empty and a flag list when non-empty, matching\n// JavaScript's own coercion for those two.\n//\n// Until 2026-09-01 this admitted ONLY booleans and numbers, and Patterplay's\n// `truthy` admitted strings and lists as well. That was drift from writing the\n// two engines at different times, not a considered difference: the two share a\n// property registry, so the same value read from the same registry answered a\n// condition differently depending on which engine asked. expr-specificity calls\n// truthiness host-bound, and it is, but \"host-bound\" is licence for a host to\n// choose, not licence for two hosts in one family to disagree by accident.\nfunction conditionPasses(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[] (flags)\n}\n\n/** One kernel bag with its store path prefix (story / box.<gameId> / deck.<gameId>\n * / hand.<gameId> / value.<gameId>): the state logger's mount surface\n * (design/engine-runtimes.md 3.4 - the logger builds on the PropertyBag\n * audit hook, so it needs the bags themselves, not just their rows).\n * The Engine lists the shared bags, a Flow its own; the @world container\n * is the host's bag and the host mounts it itself. loadGame() rebuilds\n * every flow, and so every flow's bags, so re-enumerate after a load. */\nexport interface BagMount {\n prefix: string;\n bag: StateBag;\n}\n\n/** One box on the enumeration surface (examiners, hosts): identity plus\n * its clock (per flow). */\nexport interface BoxView {\n id: string;\n gameId: string;\n title?: string;\n turn: number;\n /** The box's Storylet extension values, by extension id, raw and read-only, as a card's are. Absent when it has none. */\n extensions?: NodeExtensions;\n}\n\n/** The sharing default per scope (design/flows.md, the old system's rule):\n * @story is the playthrough family's globals, shared; the narrower scopes\n * are geographic, per-flow, so \"this flow's danger in the docks\" stays\n * expressible. A declaration's `shared` flag overrides. */\nconst SCOPE_DEFAULT_SHARED = { story: true, box: false, deck: false, hand: false, value: false } as const;\ntype FlaggedScope = keyof typeof SCOPE_DEFAULT_SHARED;\n\nconst isShared = (scope: FlaggedScope, d: PropertyDecl): boolean => d.shared ?? SCOPE_DEFAULT_SHARED[scope];\nconst sharedHalf = (scope: FlaggedScope, decls: PropertyDecl[]): PropertyDecl[] =>\n decls.filter((d) => isShared(scope, d));\nconst flowHalf = (scope: FlaggedScope, decls: PropertyDecl[]): PropertyDecl[] =>\n decls.filter((d) => !isShared(scope, d));\n\n/** One side's five declaration lists, keyed by owner id where the scope has\n * owners. The bags are built from these; so is the load report's answer to\n * \"what does this build declare that the save does not carry\". */\ninterface DeclSet {\n story: PropertyDecl[];\n box: Map<string, PropertyDecl[]>;\n deck: Map<string, PropertyDecl[]>;\n hand: Map<string, PropertyDecl[]>;\n value: Map<string, PropertyDecl[]>;\n}\n\n/** The four owned property scopes: the ones whose address carries an owner\n * segment. `story` has no owner and `world` is the host's. */\ntype OwnedScope = \"box\" | \"deck\" | \"hand\" | \"value\";\nconst OWNED_SCOPES = [\"box\", \"deck\", \"hand\", \"value\"] as const;\nconst isOwnedScope = (kind: string | undefined): kind is OwnedScope =>\n (OWNED_SCOPES as readonly string[]).includes(kind ?? \"\");\n\n/** The owner segment of a property address, both ways round\n * (design/engine-server.md 4.4).\n *\n * `gameId` is the segment the ADDRESS uses; `id` is the internal id everything\n * inside the engine is keyed by - the bags, the save envelope, the ladders.\n * Both maps are built in bundle order and a repeated gameId does NOT\n * overwrite the first.\n *\n * Box, deck, hand and card gameIds are unique bundle-wide, so for three of the\n * four scopes the segment is simply the gameId. A TAG's is unique only within\n * its group, and a group's only within its box, so two boxes may each name a\n * tag \"docks\": the value scope's segment is box-qualified,\n * `value.<boxGameId>/<tagGameId>.<name>`, wherever a gameId repeats, and the\n * short form is REFUSED there rather than resolved to the first in bundle\n * order. `valueAddresses` in the model is the one definition of that rule -\n * the Board draws these addresses from the bundle while the engine builds them\n * from this index, and the two have to agree - and `repeated` is what it\n * found, so a refusal can name the candidates.\n *\n * Two GROUPS in one box naming the same tag is the case the box qualifier\n * cannot separate, and it is closing at the source rather than here (question\n * 16, ruled 2026-09-06): the compiler warns that a tag gameId must be unique\n * within its box, and refuses it from the next release. Until then the first\n * in bundle order answers, as it always did.\n */\ninterface OwnerIndex {\n gameId: Map<string, string>; // internal id -> owner segment\n id: Map<string, string>; // owner segment -> internal id (first in bundle order wins)\n repeated: Map<string, string[]>; // an ambiguous short form -> the qualified candidates\n zoneQualified: Map<string, string>; // \"<box>/<zone>\" -> \"<zone>\", refused (value scope only)\n}\n\ntype OwnerIndexes = Record<OwnedScope, OwnerIndex>;\n\nconst emptyOwnerIndex = (): OwnerIndex =>\n ({ gameId: new Map(), id: new Map(), repeated: new Map(), zoneQualified: new Map() });\nconst emptyOwnerIndexes = (): OwnerIndexes => ({\n box: emptyOwnerIndex(), deck: emptyOwnerIndex(), hand: emptyOwnerIndex(), value: emptyOwnerIndex(),\n});\n\nconst indexOwner = (index: OwnerIndex, entity: { id: string; gameId?: string; title?: string }): void => {\n const gameId = effectiveGameId(entity);\n index.gameId.set(entity.id, gameId);\n if (!index.id.has(gameId)) index.id.set(gameId, entity.id);\n};\n\n/** The value scope's index, whole: the segments to print, the segments to\n * accept, the gameIds that need qualifying, and the box-qualified forms of\n * the project map's zones, which are refused. Built from the bundle rather\n * than tag by tag, because whether a tag's own gameId is enough is a question\n * about the OTHER boxes. */\nconst indexValueOwners = (index: OwnerIndex, bundle: Bundle): void => {\n const addresses = valueAddresses(bundle);\n for (const [id, segment] of addresses.print) index.gameId.set(id, segment);\n for (const [segment, id] of addresses.accept) index.id.set(segment, id);\n for (const [gameId, candidates] of addresses.repeated) index.repeated.set(gameId, candidates);\n for (const [segment, zone] of addresses.zoneQualified) index.zoneQualified.set(segment, zone);\n};\n\n/** One side's five stores (shared on the engine, per-flow on each flow). */\ninterface Partition {\n story: StateBag;\n box: Map<string, StateBag>;\n deck: Map<string, StateBag>;\n hand: Map<string, StateBag>;\n value: Map<string, StateBag>;\n}\n\ntype PartitionKind = keyof Partition;\n\n/** One durable declaration on one side of the sharing flag, with the address a\n * durable half keys it by and where its bag is. */\ninterface DurableProp {\n address: string;\n kind: FlaggedScope;\n /** The owner's internal id; absent for `story`. */\n owner?: string;\n decl: PropertyDecl;\n}\n\n/** Every durable declaration in one side's DeclSet, in bundle order. Where two\n * owners print the same address (two groups in one box naming a tag alike,\n * the residual the compiler is closing) the first answers, which is the rule\n * `resolveOwner` keeps for the same address. */\nconst durableProps = (internals: Internals, decls: DeclSet): DurableProp[] => {\n const out: DurableProp[] = [];\n const seen = new Set<string>();\n const push = (p: DurableProp): void => {\n if (seen.has(p.address)) return;\n seen.add(p.address);\n out.push(p);\n };\n for (const decl of decls.story) if (decl.durable === true) push({ address: `story.${decl.name}`, kind: \"story\", decl });\n for (const kind of OWNED_SCOPES) {\n for (const [owner, list] of decls[kind]) {\n for (const decl of list) {\n if (decl.durable === true) push({ address: `${addressOf(internals, kind, owner)}.${decl.name}`, kind, owner, decl });\n }\n }\n }\n return out;\n};\n\nconst durableBag = (p: Partition, prop: DurableProp): StateBag | undefined =>\n prop.kind === \"story\" ? p.story : p[prop.kind].get(prop.owner!);\n\n/** One half's values, keys in byte order so the same state writes the same\n * text. An address never looks like an integer (it has a dot in it), so the\n * object keeps that order (ruling E). */\nconst durableValues = (p: Partition, props: DurableProp[]): Record<string, ScalarValue> => {\n const entries: [string, ScalarValue][] = [];\n for (const prop of props) {\n const value = ownValue(durableBag(p, prop), prop.decl.name);\n if (value !== undefined) entries.push([prop.address, value]);\n }\n return Object.fromEntries(byKey(entries, ([address]) => address));\n};\n\n/** What a durable half will write, decided before anything is written. */\ninterface DurablePlan {\n values: Map<DurableProp, ScalarValue>;\n /** Internal card ids to spend. */\n cards: string[];\n}\n\n/**\n * Walk one durable half against this build's durable declarations and\n * durable cards on one side (`flow` names the flow for a pocket; absent, it is\n * the engine's memory), filing what does not fit in the load report's own\n * fields. Pure: the caller writes the plan.\n *\n * A value lands only at an address this build declares DURABLE ON THIS SIDE:\n * a renamed property, one no longer durable, and one that moved to the other\n * side of `shared` are all dropped, since this half would never have carried\n * them. A value its declaration no longer takes is retyped and takes the\n * default; a durable declaration the half does not carry is defaulted. A spend\n * lands only on a card that is still a durable one-shot on this side, and is\n * otherwise a dropped cooldown (a pocket's spends are cooldowns) or a dropped\n * spent card (a memory's are the engine's spent set), named by the gameId the\n * half carried.\n */\nfunction planDurable(\n internals: Internals, props: DurableProp[], cards: CardEntry[],\n save: DurableSave, flow: string | undefined, draft: ReportDraft,\n): DurablePlan {\n const at = (path: string): LoadProperty => ({ ...(flow !== undefined ? { flow } : {}), path });\n const byAddress = new Map(props.map((p) => [p.address, p]));\n const values = new Map<DurableProp, ScalarValue>();\n const carried = save.values ?? {};\n for (const [address, value] of Object.entries(carried)) {\n const prop = byAddress.get(address);\n if (prop === undefined) { draft.droppedProperties.push(at(address)); continue; }\n if (!valueFits(prop.decl, value)) { draft.retypedProperties.push(at(address)); continue; }\n values.set(prop, value);\n }\n for (const prop of props) {\n if (!Object.prototype.hasOwnProperty.call(carried, prop.address)) draft.defaultedProperties.push(at(prop.address));\n }\n const eligible = new Set(cards.map((e) => e.card.id));\n const spend: string[] = [];\n for (const gameId of save.spent ?? []) {\n const entry = internals.cardsByGameId.get(gameId);\n if (entry !== undefined && eligible.has(entry.card.id)) spend.push(entry.card.id);\n else if (flow !== undefined) draft.droppedCooldowns.push({ flow, card: gameId });\n else draft.droppedSpent.push(gameId);\n }\n return { values, cards: spend };\n}\n\n/** The two refusals every durable load shares, made before anything moves: a\n * shape this runtime does not know, and another project's state. */\nconst assertDurable = (internals: Internals, save: DurableSave): void => {\n const schema = (save as { schema?: unknown } | null | undefined)?.schema;\n if (schema !== DURABLE_SCHEMA) throw new StoryletError(`unsupported durable schema: ${String(schema)}`);\n const project = internals.bundle.content.project;\n if (save.content?.project !== project) {\n throw new StoryletError(`durable state is for project \"${String(save.content?.project)}\", bundle is \"${project}\"`);\n }\n};\n\n/** A host write of one durable value: silent, as every host write is, and\n * past a `writable: false`, which is the story's promise and not the game's. */\nconst putDurable = (bag: StateBag | undefined, name: string, value: ScalarValue): void => {\n bag?.set(name, structuredClone(value), { silent: true, reason: \"host durable\", host: true });\n};\n\n/** Everything a Flow shares with its Engine: the bundle-derived lookups\n * (immutable), the shared stores (reseeded in place by loadGame/reset),\n * and the seams. One object, held by both classes - the two are one\n * machine in two lifetimes. */\ninterface Internals {\n bundle: Bundle;\n logCap?: number;\n cardsById: Map<string, CardEntry>;\n cardsByGameId: Map<string, CardEntry>;\n boxesByGameId: Map<string, Box<Expression>>;\n boxesById: Map<string, Box<Expression>>;\n handsById: Map<string, { hand: Hand<Expression>; box: Box<Expression> }>;\n handsByGameId: Map<string, { hand: Hand<Expression>; box: Box<Expression> }>;\n /** The owner segment of a property address, both ways round (4.4). */\n owners: OwnerIndexes;\n templatesById: Map<string, HandTemplate<Expression>>;\n /** Every group by internal id. `box` is absent for the project map's group,\n * which belongs to no box. */\n groupsById: Map<string, { group: TagGroup; box?: Box<Expression> }>;\n requiredGroups: Set<string>;\n nodeCache: WeakMap<Expression, ExprNode>;\n ladders: {\n /** Each host scope's ladders, by token, then name. */\n host: Map<string, Map<string, readonly string[]>>;\n story: Map<string, readonly string[]>;\n box: Map<string, Map<string, readonly string[]>>;\n deck: Map<string, Map<string, readonly string[]>>;\n value: Map<string, Map<string, readonly string[]>>;\n hand: Map<string, Map<string, readonly string[]>>;\n };\n hasQualities: boolean;\n /** Does ANY deck or card in the bundle opt into shared scarcity? False for\n * the overwhelming majority of projects, and when it is false the two\n * claim-ledger walks in dealing are skipped entirely. Same idea as\n * `hasQualities` above: a bundle that does not use a feature must not pay\n * for it. */\n hasShared: boolean;\n /** The per-flow halves of every declaration list, precomputed once: each\n * new flow builds its bags from these. */\n flowDecls: DeclSet;\n /** The shared halves, the same way. Not used to build anything - the shared\n * bags are built straight from the bundle - but a load report has to say\n * what the shared side WOULD hold without building a bag, which is what\n * makes previewLoad pure. */\n sharedDecls: DeclSet;\n /** The durable declarations and durable cards on each side (ruling H),\n * precomputed once from the two DeclSets and the decks. */\n durable: { shared: DurableProp[]; flow: DurableProp[]; sharedCards: CardEntry[]; flowCards: CardEntry[] };\n /** The shared stores, registered in the registry for the engine's life.\n * Reseeded in place by reset and by a load that carries values. */\n shared: Partition;\n /** The game's one registry (or the engine's own, when it is standalone). */\n registry: ScopeRegistry;\n /** True when the engine made the registry: `saveGame()` then carries its values. */\n ownsRegistry: boolean;\n /** Every OTHER scope in the registry, as an eval context sees it (instance\n * keys left out), rebuilt only when the registry's set of scopes moves. */\n registryView: () => { scopes: EvalContext[\"scopes\"]; qualities: EvalContext[\"qualities\"] };\n /** Every host scope the bundle declares (`@world`, `@player`, ...), by\n * token, in bundle order. */\n hostScopes: Map<string, HostScopeState>;\n /** The host scopes as an eval context's scopes, by token. */\n hostResolvers: Record<string, ScopeResolver>;\n /** The game's `onError` (EngineOptions), or the console warning. */\n onError: (error: PlayError) => void;\n /** Take this engine's share of the registry's refits and tell the game\n * (EngineOptions.onRefit, or the console warning). */\n reportRefits: () => void;\n /** `turn` is the box clock the event happened on, where the caller knows it\n * - the same stamp the flow's own log carries. Unity and Unreal passed it\n * from the start; JS and Godot dropped it, so their examiners printed \"[-]\"\n * on every deal, peek, evict and write line while the other two printed the\n * real turn. Four runtimes, two different run logs (2026-08-29). */\n emitEngine: (flow: string, event: TraceEvent, turn?: number) => void;\n engineTracing: () => boolean;\n}\n\n/** One host scope the bundle declares, as the engine serves it. */\ninterface HostScopeState {\n spec: HostScopeSpec;\n /** Read through the registry by name (the scope's own normalisation), so a\n * scope the game registered folded to lower case still answers the names\n * as authored. */\n resolver: ScopeResolver;\n /** The WRITE seam. `host` says the caller is the GAME's own surface -\n * setProperty, the coverage harness, the CLI's --set - which the shared\n * kernel lets past a `writable: false` (scoperegistry 0.6.0): that flag is\n * the story's promise, not the game's. The story's refusal is `readOnly`\n * below, consulted before this seam is reached. A BOUND resolver is opaque -\n * it takes a name and a value and keeps whatever rule the game has - so the\n * flag only ever reaches a self-backed bag. Undefined when the scope cannot\n * be written at all (a resolver bound with no `set`). */\n set?: (name: string, value: ScalarValue, host?: boolean) => void;\n /** True when this engine self-backed it (standalone, no resolver bound). */\n selfBacked: boolean;\n}\n\n/** Is a host scope's property read-only TO THE STORY? Its own `writable:\n * false`, or its scope's: the story's promise, kept at run time as the\n * compiler keeps it at publish (Reboot.md 10). */\nconst hostReadOnly = (spec: HostScopeSpec, name: string): boolean =>\n (spec.declarations?.find((d) => d.name === name)?.writable ?? spec.writable) === false;\n\n/** A host scope declaration as the registry takes it, for a foreign scope. */\nconst toForeignDecl = (d: HostScopeDecl): ScopeDeclaration => ({\n name: d.name, type: d.type,\n ...(d.values !== undefined ? { values: d.values } : {}),\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n ...(d.default !== undefined ? { default: d.default } : {}),\n ...(d.writable !== undefined ? { writable: d.writable } : {}),\n});\n\n/** A self-backed host scope's declarations: the scope's own `writable`\n * folded into each, since an owned bag reads writability per declaration. */\nconst selfBackedDecls = (spec: HostScopeSpec): ScopeDeclaration[] =>\n (spec.declarations ?? []).map((d) => {\n const decl = toForeignDecl(d);\n const writable = d.writable ?? spec.writable;\n return writable === undefined ? decl : { ...decl, writable };\n });\n\n/** A template instance inherits the template's property declarations; a\n * standalone hand declares its own (schema 2.6). `templatesById` is complete\n * before anything asks: the constructor indexes every box first. */\nconst handDeclsOf = (internals: Internals, hand: Hand<Expression>): PropertyDecl[] =>\n hand.template !== undefined\n ? internals.templatesById.get(hand.template)?.properties ?? []\n : hand.properties ?? [];\n\n/**\n * One owned property's ADDRESS, owner segment and all: `box.village.mood`.\n *\n * The stores, the save envelope and the ladders stay keyed by internal id -\n * a save must survive a rename, which is the whole reason ids exist - so this\n * is the one place the two vocabularies meet, and it is a formatter, never a\n * lookup key. An owner the build no longer has (a save that outlived an edit)\n * keeps the id it arrived with: there is no gameId left to give it, which is\n * the rule a load report's evictions have always used.\n */\nconst addressOf = (internals: Internals, kind: OwnedScope, id: string): string =>\n `${kind}.${internals.owners[kind].gameId.get(id) ?? id}`;\n\n/**\n * Resolve a property address's owner segment to the internal id the stores are\n * keyed by. `legacy` says the caller used the pre-4.4 form - an internal id\n * where a gameId belongs - which resolves for THIS release and earns a\n * diagnostic; the next lockstep release refuses it, in every scope including\n * `value`, which had the one reprieve until it gained an address of its own.\n * `ambiguous` is a short-form value address two boxes answer to: the caller\n * REFUSES it, listing those candidates. Undefined when the segment names no\n * owner at all, which is the caller's \"no <kind> store\" error.\n */\ntype OwnerLookup = { id: string; legacy: boolean } | { ambiguous: string[] } | { zone: string };\n\nconst resolveOwner = (internals: Internals, kind: OwnedScope, segment: string): OwnerLookup | undefined => {\n // Checked before the lookup, because the short form is deliberately NOT in\n // the accept map when it is ambiguous: silently picking the first tag in\n // bundle order is the bug this removes.\n const candidates = internals.owners[kind].repeated.get(segment);\n if (candidates !== undefined) return { ambiguous: candidates };\n // A box-qualified form of a project-map zone: never accepted, because the\n // zone belongs to no box (design/project-map-contract.md 3.4).\n const zone = internals.owners[kind].zoneQualified.get(segment);\n if (zone !== undefined) return { zone };\n const byGameId = internals.owners[kind].id.get(segment);\n if (byGameId !== undefined) return { id: byGameId, legacy: false };\n // A gameId that equals its id took the branch above, so anything reaching\n // here and known as an id is genuinely the old spelling.\n if (internals.owners[kind].gameId.has(segment)) return { id: segment, legacy: true };\n return undefined;\n};\n\n/** Resolve or throw: the two refusals every property address shares, in one\n * place, so the engine's surface and a flow's answer alike. */\nconst ownerOrThrow = (internals: Internals, kind: OwnedScope, segment: string, name: string): { id: string; legacy: boolean } => {\n const owner = resolveOwner(internals, kind, segment);\n if (owner === undefined) throw new StoryletError(`no ${kind} store \"${segment}\"`);\n if (\"ambiguous\" in owner) throw new StoryletError(ambiguousValueAddressMessage(segment, name, owner.ambiguous));\n if (\"zone\" in owner) throw new StoryletError(zoneQualifiedValueAddressMessage(segment, owner.zone, name));\n return owner;\n};\n\n/** What a legacy address is told. It NAMES the address to move to, because\n * \"that form is deprecated\" without the replacement leaves a host grepping a\n * bundle for ids it never chose. */\nconst legacyAddressMessage = (internals: Internals, kind: OwnedScope, segment: string, name: string): string =>\n `\"${kind}.${segment}.${name}\" names the ${kind} by its internal id; write \"${addressOf(internals, kind, segment)}.${name}\". `\n + `The internal-id form is refused after the next release.`;\n\n/** Build one side of the partition from that side's declarations (the\n * engine precomputes both halves once, in bundle order, as a DeclSet). The\n * bags are KEYED by internal id and ADDRESSED by gameId; see addressOf. */\nconst buildPartition = (internals: Internals, decls: DeclSet): Partition => {\n const bags = (kind: OwnedScope): Map<string, StateBag> => new Map([...decls[kind]].map(\n ([id, list]): [string, StateBag] => [id, bagFromDecls(list, `${addressOf(internals, kind, id)}.`)]));\n return {\n story: bagFromDecls(decls.story, \"story.\"),\n box: bags(\"box\"), deck: bags(\"deck\"), hand: bags(\"hand\"), value: bags(\"value\"),\n };\n};\n\nconst partitionValues = (p: Partition): PropsPartition => ({\n story: p.story.values,\n box: Object.fromEntries([...p.box].map(([id, bag]) => [id, bag.values])),\n deck: Object.fromEntries([...p.deck].map(([id, bag]) => [id, bag.values])),\n hand: Object.fromEntries([...p.hand].map(([id, bag]) => [id, bag.values])),\n value: Object.fromEntries([...p.value].map(([id, bag]) => [id, bag.values])),\n});\n\nconst loadPartition = (p: Partition, values: PropsPartition | undefined): void => {\n // Fresh defaults are already in the bags; the saved values land over\n // them: orphaned keys (deleted entities, re-flagged properties) drop;\n // newly declared properties keep defaults.\n p.story.load(values?.story ?? {});\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of Object.entries(values?.[kind] ?? {})) {\n p[kind].get(id)?.load(bag);\n }\n }\n};\n\n// --- the load report (design/engine-server.md 4.9) ---------------------------\n//\n// One walk, two entry points. `previewLoad` runs it and returns the report;\n// `loadGame` runs it, returns the same report and then applies the CLEANED\n// blob the walk produced. Two implementations of \"what does this save cost\"\n// would drift the first time one of them was fixed, so there is one, and the\n// apply half consumes its output rather than repeating its decisions.\n\n/** The report under construction: unsorted, until finishReport orders it. */\ninterface ReportDraft {\n evicted: LoadEviction[];\n droppedCooldowns: { flow: string; card: string }[];\n droppedSpent: string[];\n droppedProperties: LoadProperty[];\n defaultedProperties: LoadProperty[];\n retypedProperties: LoadProperty[];\n}\n\nconst emptyDraft = (): ReportDraft => ({\n evicted: [], droppedCooldowns: [], droppedSpent: [],\n droppedProperties: [], defaultedProperties: [], retypedProperties: [],\n});\n\n/** The sort key separator: a UNIT SEPARATOR, as the play-history indexes use,\n * because it cannot occur in an id, a gameId or a property name. */\nconst SORT_SEP = \"\\u001f\";\n\nconst byKey = <T>(items: T[], key: (item: T) => string): T[] =>\n [...items].map((item) => ({ item, k: key(item) }))\n .sort((a, b) => (a.k < b.k ? -1 : a.k > b.k ? 1 : 0))\n .map((e) => e.item);\n\nconst propKey = (p: LoadProperty): string => `${p.flow ?? \"\"}${SORT_SEP}${p.path}`;\n\n// Whether a saved value still fits its declaration is the kernel's `valueFits`\n// (@wildwinter/expr), shared with Patter's loads: the type first, then the\n// declaration's own vocabulary, so an enum value or a stage the edit struck out\n// no longer fits though it is still a string.\n\n/** Walk one bag's worth of saved values against one bag's worth of\n * declarations: report the orphans, the newcomers and the misfits, and\n * return the values that survive. */\nfunction walkScope(\n decls: PropertyDecl[] | undefined,\n saved: PropertyBag | undefined,\n path: (name: string) => string,\n flow: string | undefined,\n draft: ReportDraft,\n): PropertyBag {\n const at = (name: string): LoadProperty =>\n ({ ...(flow !== undefined ? { flow } : {}), path: path(name) });\n const byName = new Map((decls ?? []).map((d) => [d.name, d]));\n const values = saved ?? {};\n const clean: PropertyBag = {};\n for (const [name, value] of Object.entries(values)) {\n const decl = byName.get(name);\n if (decl === undefined) { draft.droppedProperties.push(at(name)); continue; }\n if (!valueFits(decl, value)) { draft.retypedProperties.push(at(name)); continue; }\n clean[name] = value;\n }\n for (const decl of decls ?? []) {\n if (!(decl.name in values)) draft.defaultedProperties.push(at(decl.name));\n }\n return clean;\n}\n\n/** The same walk over all five scopes of one partition. An owner the save\n * carries and the build no longer has drops whole (its bag is gone, so its\n * values have nowhere to land); an owner the build has and the save lacks\n * keeps every default. */\nfunction walkPartition(\n internals: Internals,\n decls: DeclSet,\n values: PropsPartition | undefined,\n flow: string | undefined,\n draft: ReportDraft,\n): PropsPartition {\n const out: PropsPartition = {\n story: walkScope(decls.story, values?.story, (n) => `story.${n}`, flow, draft),\n box: {}, deck: {}, hand: {}, value: {},\n };\n for (const kind of OWNED_SCOPES) {\n const savedKind = values?.[kind] ?? {};\n const ids = [...new Set([...decls[kind].keys(), ...Object.keys(savedKind)])].sort();\n for (const id of ids) {\n // The report's `path` is exactly what listProperties() prints and what\n // setProperty takes: one grammar, so an operator reading a hot-swap\n // report can paste the address straight back in (4.4).\n const owner = addressOf(internals, kind, id);\n out[kind][id] = walkScope(decls[kind].get(id), savedKind[id],\n (n) => `${owner}.${n}`, flow, draft);\n }\n }\n return out;\n}\n\n/** Order the draft and answer the identity questions. `saved` is the content\n * block the save carries; for a single-flow restore there is none, so the\n * caller passes the bundle's own and no drift is reported. */\nfunction finishReport(bundle: BundleContent, saved: BundleContent, flows: string[], draft: ReportDraft): LoadReport {\n const drift = saved.version !== bundle.version || saved.hash !== bundle.hash;\n const evicted = byKey(draft.evicted, (e) => [e.flow, e.hand, e.card, e.reason].join(SORT_SEP));\n const droppedCooldowns = byKey(draft.droppedCooldowns, (c) => `${c.flow}${SORT_SEP}${c.card}`);\n const droppedSpent = [...draft.droppedSpent].sort();\n const droppedProperties = byKey(draft.droppedProperties, propKey);\n const defaultedProperties = byKey(draft.defaultedProperties, propKey);\n const retypedProperties = byKey(draft.retypedProperties, propKey);\n return {\n // `flows` is what the load restores, not something it had to change, so\n // it never makes a report inexact.\n exact: !drift && evicted.length === 0 && droppedCooldowns.length === 0 && droppedSpent.length === 0\n && droppedProperties.length === 0 && defaultedProperties.length === 0 && retypedProperties.length === 0,\n project: bundle.project,\n version: { saved: saved.version, bundle: bundle.version },\n hash: { saved: saved.hash, bundle: bundle.hash },\n flows,\n evicted, droppedCooldowns, droppedSpent,\n droppedProperties, defaultedProperties, retypedProperties,\n };\n}\n\n// --- inputs the game hands over -------------------------------------------------\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n/** What a refused input was, for the message: `null`, `an array`, `a string`. */\nconst describeInput = (value: unknown): string =>\n value === null ? \"null\" : value === undefined ? \"nothing\" : Array.isArray(value) ? \"an array\" : `a ${typeof value}`;\n\n/** The parts of a bundle the engine reads before anything else, each named\n * with what it should be; empty when they are all there. */\nconst bundleShapeProblems = (bundle: Record<string, unknown>): string[] => {\n const problems: string[] = [];\n if (!isRecord(bundle[\"content\"])) problems.push(\"no content\");\n if (!isRecord(bundle[\"settings\"])) problems.push(\"no settings\");\n // A bundle @1 or @0 has a `world` block; from @2 the game's scopes are\n // `scopeRegistry`, absent when the project declares none.\n const scopes = bundle[\"schema\"] === BUNDLE_SCHEMA ? [\"story\"] as const : [\"story\", \"world\"] as const;\n for (const scope of scopes) {\n const part = bundle[scope];\n if (!isRecord(part) || !Array.isArray(part[\"properties\"])) problems.push(`no ${scope} properties`);\n }\n const registry = bundle[\"scopeRegistry\"];\n if (registry !== undefined && (!isRecord(registry) || !Array.isArray(registry[\"scopes\"]))) problems.push(\"no host scopes in its scopeRegistry\");\n if (!Array.isArray(bundle[\"boxes\"])) problems.push(\"no boxes\");\n return problems;\n};\n\n/** Refuse a save envelope this runtime cannot read, by what is wrong with it,\n * before anything reads it (the hands-on review, 2026-10, foundation 5). */\nconst refuseUnreadableSave = (envelope: unknown): void => {\n if (!isRecord(envelope)) throw new StoryletError(`not a Storylets save: expected an object, got ${describeInput(envelope)}`);\n const schema = envelope[\"schema\"];\n if (schema !== SAVE_SCHEMA && schema !== SAVE_SCHEMA_V2 && schema !== SAVE_SCHEMA_V1) {\n throw new StoryletError(`unsupported save schema: ${String(schema)} (this runtime reads ${SAVE_SCHEMA_V1}, ${SAVE_SCHEMA_V2} and ${SAVE_SCHEMA})`);\n }\n if (!isRecord(envelope[\"content\"])) throw new StoryletError(\"not a complete Storylets save: no content\");\n};\n\n/** What a declared property takes, in words, for a refused host write. */\nconst takes = (decl: PropertyDecl | HostScopeDecl): string => {\n const quoted = (xs: readonly string[] | undefined): string => (xs ?? []).map((x) => JSON.stringify(x)).join(\", \");\n switch (decl.type) {\n case \"number\": return \"a number\";\n case \"boolean\": return \"true or false\";\n case \"enum\": return `one of ${quoted(decl.values)}`;\n case \"flags\": return decl.values !== undefined && decl.values.length > 0 ? `a list of flags from ${quoted(decl.values)}` : \"a list of flags\";\n case \"quality\": return `one of its stages ${quoted(decl.stages)}`;\n default: return \"text\";\n }\n};\n\n// --- save keys ------------------------------------------------------------------\n//\n// A save names cards, hands and boxes by GAME ID from version 3 (the hands-on\n// review, 2026-10, foundation 7), as its play log always did: a card deleted\n// and made again under the same Game ID is the same card to a game, so it\n// keeps its cooldown and its place as well as its play count. Earlier saves\n// name them by internal id, so a saved key is read as a Game ID first and an\n// internal id second; the two never collide, ids being generated.\n\nconst savedCard = (internals: Internals, key: string): CardEntry | undefined =>\n internals.cardsByGameId.get(key) ?? internals.cardsById.get(key);\nconst savedHand = (internals: Internals, key: string): { hand: Hand<Expression>; box: Box<Expression> } | undefined =>\n internals.handsByGameId.get(key) ?? internals.handsById.get(key);\nconst savedBox = (internals: Internals, key: string): Box<Expression> | undefined =>\n internals.boxesByGameId.get(key) ?? internals.boxesById.get(key);\n/** The Game ID a save names an entity by; the id itself for one the build no\n * longer has, which is all there is to name it by. */\nconst cardKey = (internals: Internals, id: string): string => {\n const entry = internals.cardsById.get(id);\n return entry !== undefined ? effectiveGameId(entry.card) : id;\n};\n\n/**\n * Refuse a host write that does not fit what the content declared (the\n * hands-on review, 2026-10, foundation 5): a string for a number, a value an\n * enum or a quality does not list, a flag a flags property does not, or a name\n * the content never declared in a host scope it declares. The rule is the\n * kernel's `valueFits`, the one a load walks saved values with, so a value the\n * game may set is exactly one a save may carry. An opaque host scope declares\n * no names, so any name and value in it is the game's. Another engine's scope\n * (`patter.x`) is that engine's to check.\n */\nconst checkHostWrite = (internals: Internals, path: string, value: ScalarValue): void => {\n const parts = path.split(\".\");\n let decl: PropertyDecl | HostScopeDecl | undefined;\n const host = parts.length === 2 ? internals.hostScopes.get(parts[0]!) : undefined;\n if (host !== undefined) {\n const decls = host.spec.declarations;\n if (decls === undefined) return;\n const name = parts[1]!;\n decl = decls.find((d) => d.name === name) ?? decls.find((d) => d.name.toLowerCase() === name.toLowerCase());\n if (decl === undefined) throw new StoryletError(`no property at \"${path}\": the content declares no @${host.spec.token}.${name}`);\n } else if (parts.length === 2 && parts[0] === \"story\") {\n const name = parts[1]!;\n decl = internals.sharedDecls.story.find((d) => d.name === name) ?? internals.flowDecls.story.find((d) => d.name === name);\n } else if (parts.length === 3 && isOwnedScope(parts[0])) {\n const kind = parts[0];\n const [, segment, name] = parts as unknown as [string, string, string];\n const id = ownerOrThrow(internals, kind, segment, name).id;\n decl = internals.sharedDecls[kind].get(id)?.find((d) => d.name === name)\n ?? internals.flowDecls[kind].get(id)?.find((d) => d.name === name);\n }\n // An enum whose own default is \"\" (none chosen, what an editor writes for\n // \"(none)\") takes \"\" back, so a game can put its default back.\n if (decl !== undefined && !valueFits(decl, value, { noneChosen: decl.type === \"enum\" && decl.default === \"\" })) {\n throw new StoryletError(`\"${path}\" takes ${takes(decl)}; ${JSON.stringify(value)} doesn't fit`);\n }\n};\n\n/** Every declared host scope's properties as examiner rows, read through its\n * resolver, in bundle order (an opaque scope declares none, so lists none). */\nconst hostRows = (internals: Internals): PropertyRow[] => {\n const out: PropertyRow[] = [];\n for (const host of internals.hostScopes.values()) {\n for (const d of host.spec.declarations ?? []) {\n const value = host.resolver.get(d.name);\n out.push({\n path: `${host.spec.token}.${d.name}`, name: d.name, type: d.type,\n value: value ?? defaultFor(d), default: defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n // A host scope is FOREIGN - a host resolver backs it - so writability is whether\n // that resolver can be written at all AND what the declaration says, which is\n // the shared registry's own rule for a foreign scope (its foreignWritable).\n //\n // A row is where `writable: false` is meant to SHOW (Reboot.md 10): it tells a\n // state panel this is the game's value, not the story's. It does not stop the\n // panel editing it - the host's setProperty passes `{ host: true }`.\n writable: host.set !== undefined && !hostReadOnly(host.spec, d.name),\n });\n }\n }\n return out;\n};\n\n// --- the Engine ---------------------------------------------------------------\n\n/**\n * Refuse, at construction, a bundle this engine cannot read faithfully\n * (design/project-map-contract.md 3.8). Throws one error naming every problem\n * found, each naming the box and the group or tag at fault.\n *\n * The compiler refuses all of these first. The engine checks again because it\n * cannot tell a hand-built or stale bundle from a compiled one, and the\n * alternative is the silent half-working the server audit found twice: a hand\n * whose bound group is looked up in the wrong place comes back empty, and an\n * old runtime given a map bundle deals plausibly while every zone value is\n * missing. Only what would make the engine's OWN resolution ambiguous or wrong\n * is refused here; the compiler's bundle-wide group-name rule is stricter.\n */\nconst refuseUnreadableBundle = (bundle: Bundle): void => {\n // Not an object at all (`new Engine(null)`, a bundle file read as text):\n // said plainly, rather than as whichever property the engine reached for\n // first (the hands-on review, 2026-10, foundation 5).\n if (!isRecord(bundle)) throw new StoryletError(`not a Storylets bundle: expected an object, got ${describeInput(bundle)}`);\n // The schema tag (D4). Checked alone and first: a bundle of a schema this\n // runtime does not know may not have any of the shape the rest reads.\n const schema = (bundle as { schema?: unknown }).schema;\n if (typeof schema !== \"string\" || !BUNDLE_SCHEMAS.includes(schema)) {\n throw new StoryletError(`unsupported bundle schema: ${String(schema)} (this runtime reads ${BUNDLE_SCHEMAS.slice(0, -1).join(\", \")} and ${BUNDLE_SCHEMAS[BUNDLE_SCHEMAS.length - 1]})`);\n }\n // The parts every bundle of a known schema has, by name: a bundle missing\n // one is cut short or hand-built, and would fail later as a raw TypeError.\n const missing = bundleShapeProblems(bundle);\n if (missing.length > 0) throw new StoryletError(`not a complete Storylets bundle: ${missing.join(\"; \")}`);\n const problems: string[] = [];\n const map = bundle.map?.group;\n const mapName = map !== undefined ? effectiveGameId(map) : undefined;\n if (map !== undefined && mapName === PLACE_GROUP) {\n problems.push(`the project map's tag group is called \"${PLACE_GROUP}\", which is reserved for a box's own hands`);\n }\n // Every box tag gameId, for the zone-name rule: a zone's address has no\n // qualified form to fall back on (3.4), so any box tag sharing it anywhere\n // makes `value.<zone>.<name>` ambiguous.\n const boxTags = new Map<string, { box: string; group: string }>();\n for (const box of bundle.boxes) {\n for (const group of box.tagGroups) {\n for (const tag of group.tags) {\n const gameId = effectiveGameId(tag);\n if (!boxTags.has(gameId)) boxTags.set(gameId, { box: effectiveGameId(box), group: effectiveGameId(group) });\n }\n }\n }\n for (const tag of map?.tags ?? []) {\n const zone = effectiveGameId(tag);\n const clash = boxTags.get(zone);\n if (clash !== undefined) {\n problems.push(`the project map's zone \"${zone}\" has the name of tag \"${zone}\" in box \"${clash.box}\", group \"${clash.group}\", `\n + `so \"value.${zone}.<name>\" would name two things`);\n }\n }\n for (const box of bundle.boxes) {\n const boxName = effectiveGameId(box);\n if (box.usesMap === true) {\n if (map === undefined) {\n problems.push(`box \"${boxName}\" uses the project map, but the bundle has no map`);\n continue;\n }\n // One namespace in an opted-in box (3.1): a box group with the map's\n // name would make every name-based lookup there a coin toss.\n const twin = box.tagGroups.find((g) => effectiveGameId(g) === mapName);\n if (twin !== undefined) {\n problems.push(`box \"${boxName}\" uses the project map and declares its own tag group \"${mapName}\", the map's name`);\n }\n continue;\n }\n if (map === undefined) continue;\n // A box NOT on the map may not reference its group at all: every route a\n // reference takes, card tags, template bindings and holes, a hand's\n // chosen tags and a rule's bindings, by the group's id as written.\n const names = (where: string): void => {\n problems.push(`box \"${boxName}\" is not on the project map, but ${where} names the map's tag group \"${mapName}\"`);\n };\n for (const deck of box.decks) {\n for (const card of deck.cards) {\n if (card.tags?.[map.id] !== undefined) names(`card \"${effectiveGameId(card)}\"`);\n }\n }\n for (const template of box.handTemplates) {\n if (template.bindings?.[map.id] !== undefined || template.chooses?.includes(map.id) === true) {\n names(`hand template \"${effectiveGameId(template)}\"`);\n }\n }\n for (const hand of box.hands) {\n if (hand.chosen?.[map.id] !== undefined || hand.rule?.bindings?.[map.id] !== undefined) {\n names(`hand \"${effectiveGameId(hand)}\"`);\n }\n }\n }\n if (problems.length > 0) throw new StoryletError(`bundle refused: ${problems.join(\"; \")}`);\n};\n\nexport class Engine {\n private readonly internals: Internals;\n private readonly seed: number;\n private readonly onReplacedFlow: EngineOptions[\"onReplacedFlow\"];\n private readonly flowsById = new Map<string, Flow>();\n private readonly engineTraceHandlers = new Set<EngineTraceHandler>();\n /** The options this engine was built with: hotSwap builds its replacement from them. */\n private readonly creationOptions: EngineOptions;\n /** How to register each shared scope again, in registration order: a failed\n * hotSwap puts this engine back exactly as it was. */\n private readonly sharedMounts: Array<{ key: string; mount: () => void }> = [];\n\n constructor(bundle: Bundle, opts: EngineOptions = {}) {\n // First, before anything is indexed or registered: a bundle this engine\n // cannot read faithfully is refused whole (design/project-map-contract.md\n // 3.8), and a refusal must leave the game's registry untouched.\n refuseUnreadableBundle(bundle);\n this.creationOptions = opts;\n this.seed = opts.seed ?? 0;\n this.onReplacedFlow = opts.onReplacedFlow;\n // Set-up problems found while building, reported as the constructor ends,\n // when the engine can report. A plain-JS caller gets no type check on its\n // options, so a mistyped or retired key would be ignored in silence.\n const setup: Array<{ node: string; message: string }> = [];\n for (const key of Object.keys(opts)) {\n if (ENGINE_OPTIONS.has(key)) continue;\n setup.push({ node: key, message: key === \"world\"\n ? `the engine option \"world\" has gone: bind the scope as hostScopes: { world }`\n : `\"${key}\" isn't an engine option, so it's ignored` });\n }\n const internals: Internals = {\n bundle,\n ...(opts.log ? { logCap: typeof opts.log === \"object\" ? (opts.log.cap ?? 1000) : 1000 } : {}),\n cardsById: new Map(), cardsByGameId: new Map(),\n boxesByGameId: new Map(), boxesById: new Map(),\n handsById: new Map(), handsByGameId: new Map(),\n owners: emptyOwnerIndexes(),\n templatesById: new Map(), groupsById: new Map(),\n requiredGroups: new Set(),\n nodeCache: new WeakMap(),\n ladders: { host: new Map(), story: new Map(), box: new Map(), deck: new Map(), value: new Map(), hand: new Map() },\n hasQualities: false,\n hasShared: false,\n flowDecls: { story: [], box: new Map(), deck: new Map(), hand: new Map(), value: new Map() },\n sharedDecls: { story: [], box: new Map(), deck: new Map(), hand: new Map(), value: new Map() },\n durable: { shared: [], flow: [], sharedCards: [], flowCards: [] },\n shared: undefined as unknown as Partition,\n registry: opts.registry ?? new ScopeRegistry(),\n onError: opts.onError ?? ((e) => console.warn(e.kind === \"setup\" ? `Storylet Engine: ${e.message}`\n : `Storylet Engine: the condition on ${e.node} in flow '${e.flow}' failed, counted as false: ${e.message}`)),\n ownsRegistry: opts.registry === undefined,\n reportRefits: () => {\n const misfits = this.internals.registry.takeMisfits((scope) => scope === \"story\" || scope.startsWith(\"storylets/\")\n || this.internals.hostScopes.get(scope)?.selfBacked === true);\n if (misfits.length === 0) return;\n if (opts.onRefit !== undefined) opts.onRefit(misfits);\n else for (const m of misfits) console.warn(`Storylet Engine: the save's ${m.scope}.${m.name} was ${JSON.stringify(m.value)}, which no longer fits its declaration; it is back at its default`);\n },\n registryView: () => view(),\n hostScopes: new Map(),\n hostResolvers: {},\n emitEngine: (flow, event, turn) => {\n if (this.internals.logCap !== undefined) {\n this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...(turn !== undefined ? { turn } : {}) });\n if (this.engineLog.length > this.internals.logCap) {\n this.engineLog.splice(0, this.engineLog.length - this.internals.logCap);\n }\n }\n // From a copy (ruling F): a handler that subscribes or unsubscribes\n // during an event changes who hears the NEXT one, never this one.\n for (const h of [...this.engineTraceHandlers]) h(flow, event);\n },\n engineTracing: () => this.engineTraceHandlers.size > 0,\n };\n this.internals = internals;\n let viewRevision = -1;\n let viewCache: ReturnType<Internals[\"registryView\"]> = { scopes: {}, qualities: undefined };\n const view = (): ReturnType<Internals[\"registryView\"]> => {\n const reg = internals.registry;\n if (reg.revision !== viewRevision) {\n const ctx = reg.toEvalContext();\n const scopes: EvalContext[\"scopes\"] = {};\n // An instance key (`storylets/deck/x`, another engine's) is no expression token.\n for (const [k, v] of Object.entries(ctx.scopes)) if (!k.includes(\"/\")) scopes[k] = v;\n viewCache = { scopes, qualities: ctx.qualities };\n viewRevision = reg.revision;\n }\n return viewCache;\n };\n\n // The value scope's segments come off the whole bundle at once (a tag\n // gameId is only unique within its group), so they are built before the\n // walk rather than tag by tag inside it.\n indexValueOwners(internals.owners.value, bundle);\n\n for (const box of bundle.boxes) {\n internals.boxesById.set(box.id, box);\n internals.boxesByGameId.set(effectiveGameId(box), box);\n indexOwner(internals.owners.box, box);\n for (const group of box.tagGroups) {\n internals.groupsById.set(group.id, { group, box });\n if (group.required === true) internals.requiredGroups.add(group.id);\n }\n for (const deck of box.decks) {\n indexOwner(internals.owners.deck, deck);\n if (deck.shared === true) internals.hasShared = true;\n for (const card of deck.cards) {\n const entry = { card, deck, box };\n internals.cardsById.set(card.id, entry);\n internals.cardsByGameId.set(effectiveGameId(card), entry);\n if (card.shared === true) internals.hasShared = true;\n if (cardIsDurable(card, deck)) {\n (cardIsShared(card, deck.shared ?? false) ? internals.durable.sharedCards : internals.durable.flowCards).push(entry);\n }\n }\n }\n for (const template of box.handTemplates) {\n internals.templatesById.set(template.id, template);\n }\n for (const hand of box.hands) {\n internals.handsById.set(hand.id, { hand, box });\n internals.handsByGameId.set(effectiveGameId(hand), { hand, box });\n indexOwner(internals.owners.hand, hand);\n }\n }\n // The project map's group: by id like any group, so a hand's binding, a\n // filled hole and tag matching need no logic of their own for it. Which\n // boxes may NAME it is `groupInBox`'s business (3.1); which may reference\n // it at all was settled by the refusal above.\n if (bundle.map !== undefined) {\n internals.groupsById.set(bundle.map.group.id, { group: bundle.map.group });\n if (bundle.map.group.required === true) internals.requiredGroups.add(bundle.map.group.id);\n }\n this.initLadders();\n\n // Both halves, precomputed once (a bundle's declarations never change):\n // each openFlow builds its bags from the per-flow half, and a load report\n // asks either half what it declares without building anything at all.\n const declSet = (half: (scope: FlaggedScope, decls: PropertyDecl[]) => PropertyDecl[]): DeclSet => ({\n story: half(\"story\", bundle.story.properties),\n box: new Map(bundle.boxes.map((box) => [box.id, half(\"box\", box.properties)])),\n deck: new Map(bundle.boxes.flatMap((box) => box.decks.map(\n (deck): [string, PropertyDecl[]] => [deck.id, half(\"deck\", deck.properties)]))),\n hand: new Map(bundle.boxes.flatMap((box) => box.hands.map(\n (hand): [string, PropertyDecl[]] => [hand.id, half(\"hand\", handDeclsOf(internals, hand))]))),\n value: new Map(allTagGroups(bundle).flatMap((group) => group.tags.map(\n (tag): [string, PropertyDecl[]] => [tag.id, half(\"value\", tag.properties ?? [])]))),\n });\n internals.flowDecls = declSet(flowHalf);\n internals.sharedDecls = declSet(sharedHalf);\n internals.durable.shared = durableProps(internals, internals.sharedDecls);\n internals.durable.flow = durableProps(internals, internals.flowDecls);\n\n this.initShared(opts.hostScopes ?? {}, setup);\n // The shared bags claimed values the game may have loaded first.\n internals.reportRefits();\n // Last, so the game's handler and the decision log hear about an engine\n // that is ready to be asked.\n for (const s of setup) this.reportSetup(s.node, s.message);\n }\n\n /** Report a set-up problem (a `setup` PlayError, which belongs to no flow):\n * to the game's onError, and to the decision log. */\n private reportSetup(node: string, message: string): void {\n this.internals.onError({ flow: \"\", kind: \"setup\", node, message });\n if (this.internals.logCap !== undefined || this.internals.engineTracing()) {\n this.internals.emitEngine(\"\", { type: \"diagnostic\", where: \"setup\", message });\n }\n }\n\n /** Build the shared stores, register them and the host scopes, and set up\n * each host scope's seam. Once, for the engine's life: reset and loads\n * reseed the bags in place, so the registry never sees them come and go. */\n private initShared(bound: Record<string, HostScope>, setup: Array<{ node: string; message: string }>): void {\n const internals = this.internals;\n const reg = internals.registry;\n const declared = bundleHostScopes(internals.bundle);\n internals.shared = buildPartition(internals, internals.sharedDecls);\n const registered: string[] = [];\n // Register now, and remember how, so a failed hotSwap can register again.\n const mount = (key: string, register: () => void): void => {\n register();\n registered.push(key);\n this.sharedMounts.push({ key, mount: register });\n };\n try {\n // `story` is this engine's token whether or not the bundle declares a shared\n // @story property: registering it is what makes a clash show at once.\n const story = internals.shared.story;\n mount(\"story\", () => { reg.mountOwned(\"story\", story, { owner: OWNER }); }); // claims values the game loaded first\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of internals.shared[kind]) {\n if (bag.declarations().length === 0) continue; // holds nothing: not registered\n mount(sharedKey(kind, id), () => { reg.mountOwned(sharedKey(kind, id), bag, { owner: OWNER }); });\n }\n }\n // Each scope the game binds is EXTERNAL: the game keeps the values, the\n // registry never saves them. Its declarations (types, read-only) come\n // from the bundle.\n for (const [token, resolver] of Object.entries(bound)) {\n if (resolver === undefined) continue;\n const spec = declared.find((s) => s.token === token);\n // Tokens are case-sensitive everywhere, so a near miss is reported,\n // never folded: folding would make a second spelling of the scope.\n // Another engine's scope the content names is that engine's to declare.\n if (spec === undefined && !(internals.bundle.externalScopes ?? []).includes(token)) {\n const near = declared.find((s) => s.token.toLowerCase() === token.toLowerCase());\n setup.push({ node: token, message: near\n ? `hostScopes binds \"${token}\", which this bundle doesn't declare: it declares \"${near.token}\", and a scope's token is case-sensitive`\n : `hostScopes binds \"${token}\", which this bundle doesn't declare` });\n }\n const decls = spec?.declarations?.map(toForeignDecl);\n mount(token, () => { reg.defineForeign(token, resolver, decls, { normalise: identity, writable: spec?.writable ?? true, owner: OWNER }); });\n }\n // A declared host scope nobody bound. A standalone engine is its own\n // game, so it self-backs the scope from its declared defaults,\n // DECLARATIONS AND ALL, as a property the registry stores and SAVES (only\n // a resolver the game binds is external). The bag keeps `writable: false`\n // so an examiner still reads it there, and the kernel lets a\n // `{ host: true }` write past it, which is what the game's own surface\n // passes. Given the GAME's registry, the engine registers nothing here:\n // those scopes are the game's to register.\n for (const spec of internals.ownsRegistry ? declared : []) {\n const token = spec.token;\n if (bound[token] !== undefined || reg.has(token)) continue;\n reg.defineOwned(token, selfBackedDecls(spec), { normalise: identity, pathPrefix: `${token}.`, owner: OWNER });\n const bag = reg.ownedBag(token);\n registered.push(token);\n this.sharedMounts.push({ key: token, mount: () => { reg.mountOwned(token, bag, { owner: OWNER }); } });\n }\n } catch (e) {\n for (const k of registered) reg.remove(k, { keep: true }); // a clash leaves the game's registry as it was\n throw e;\n }\n internals.hostScopes = new Map();\n internals.hostResolvers = {};\n for (const spec of declared) {\n const token = spec.token;\n const resolver = bound[token];\n const isBound = resolver !== undefined;\n const state: HostScopeState = {\n spec,\n resolver: {\n get: (n) => reg.get(token, n),\n set: (n, v) => { reg.set(token, n, v); },\n },\n // A bound resolver is opaque: it keeps whatever rule the game has, so\n // the story's `writable: false` is kept by `hostReadOnly` before this seam.\n ...(isBound\n ? resolver!.set !== undefined ? { set: (n: string, v: ScalarValue): void => { reg.set(token, n, v, { host: true }); } } : {}\n : { set: (n: string, v: ScalarValue, host?: boolean): void => { reg.set(token, n, v, host === true ? { host: true } : undefined); } }),\n selfBacked: !isBound && this.sharedMounts.some((m) => m.key === token),\n };\n internals.hostScopes.set(token, state);\n internals.hostResolvers[token] = state.resolver;\n }\n }\n\n /** Every shared bag back to its declared defaults, in place (the registry\n * keeps them registered), the self-backed host scopes included. */\n private reseedShared(): void {\n const { shared, sharedDecls, registry } = this.internals;\n shared.story.reseed(sharedDecls.story as unknown as ScopeDeclaration[]);\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of shared[kind]) bag.reseed((sharedDecls[kind].get(id) ?? []) as unknown as ScopeDeclaration[]);\n }\n for (const host of this.internals.hostScopes.values()) {\n if (host.selfBacked) registry.reseedOwned(host.spec.token, selfBackedDecls(host.spec));\n }\n }\n\n /** Quality ladders by scope for the eval channel (design/quality.md):\n * host scopes by token then name; story by name; box/deck/value keyed by\n * owner id then name.\n * Built once - a bundle's declarations never change. Ladders are\n * declaration-level, so the sharing flag does not touch them. */\n private initLadders(): void {\n const internals = this.internals;\n const grab = (decls: readonly (PropertyDecl | HostScopeDecl)[] | undefined): Map<string, readonly string[]> => {\n const m = new Map<string, readonly string[]>();\n for (const d of decls ?? []) if (d.type === \"quality\" && d.stages !== undefined) m.set(d.name, d.stages);\n return m;\n };\n const b = internals.bundle;\n for (const spec of bundleHostScopes(b)) internals.ladders.host.set(spec.token, grab(spec.declarations));\n internals.ladders.story = grab(b.story.properties);\n for (const box of b.boxes) {\n internals.ladders.box.set(box.id, grab(box.properties));\n for (const deck of box.decks) internals.ladders.deck.set(deck.id, grab(deck.properties));\n for (const group of box.tagGroups) {\n for (const tag of group.tags) internals.ladders.value.set(tag.id, grab(tag.properties));\n }\n for (const hand of box.hands) internals.ladders.hand.set(hand.id, grab(handDeclsOf(internals, hand)));\n }\n // The zones, once, keyed by tag id like every other tag (3.3).\n for (const tag of b.map?.group.tags ?? []) internals.ladders.value.set(tag.id, grab(tag.properties));\n const any = (m: Map<string, Map<string, readonly string[]>>): boolean =>\n [...m.values()].some((x) => x.size > 0);\n internals.hasQualities = [...internals.ladders.host.values()].some((x) => x.size > 0) || internals.ladders.story.size > 0\n || any(internals.ladders.box) || any(internals.ladders.deck)\n || any(internals.ladders.value) || any(internals.ladders.hand);\n }\n\n /** The registry this engine's bags live in: the game's, the very object the\n * `registry` option passed in, or the one the engine made because it was\n * given none. The same object for the engine's life. Read-only: a game\n * hands an engine its registry at construction and never swaps it. */\n get registry(): ScopeRegistry {\n return this.internals.registry;\n }\n\n // --- flow management (Patter's surface, name for name) ----------------------\n\n /** Open (or REPLACE) the named flow. An existing id's flow is closed\n * first - re-opening a name is a reset of that name's whole per-flow\n * state; shared state is untouched. There is no default flow: \"main\" is\n * a caller convention, not an engine rule. */\n openFlow(id: string, opts: OpenFlowOptions = {}): Flow {\n return this.open(id, opts, false);\n }\n\n /** openFlow, and loadGame's rebuild. `claim` says the new flow's bags take\n * the values the registry holds for them (a load); a fresh open is a reset\n * of that name, so anything waiting for it is discarded first. */\n private open(id: string, opts: OpenFlowOptions, claim: boolean): Flow {\n if (opts.durable !== undefined && opts.restore !== undefined) {\n throw new StoryletError(`openFlow \"${id}\": restore and durable cannot be given together; a restore already carries the flow's durable state`);\n }\n // A pocket is checked and planned before the name is touched: a refusal,\n // and anything the plan finds, must leave the flow already open as it was.\n let durable: { plan: DurablePlan; draft: ReportDraft; content: BundleContent } | undefined;\n if (opts.durable !== undefined) {\n assertDurable(this.internals, opts.durable);\n const draft = emptyDraft();\n const { flow: props, flowCards } = this.internals.durable;\n durable = { plan: planDurable(this.internals, props, flowCards, opts.durable, id, draft), draft, content: opts.durable.content };\n }\n this.assertExternalScopes();\n // The world's claims as they stand WITHOUT this name, taken before the\n // replace: a resume competes with the other flows, never with the flow it\n // is replacing (which is about to release everything it holds).\n const otherClaims = opts.restore !== undefined ? this.sharedClaims(id) : undefined;\n // Replacing an existing id KEEPS its place in the order. `close()` would\n // drop the key, and a JS Map re-inserts a deleted key at the END, so\n // openFlow(\"a\"); openFlow(\"b\"); openFlow(\"a\") listed [b, a] here and\n // [a, b] on all three ports - a different `flows()` order and, since\n // `saveGame` keys its flows in that order, a different `.storyletsave`\n // byte stream for the same run (2026-08-29). markClosed without dropFlow\n // is the difference: the old handle goes inert, the slot stays put. The\n // registry cannot keep a key's place the same way (the old flow's keys are\n // removed and the new flow's appended), so `saveGame` writes the registry\n // section in flow order itself: see `registrySection` (2026-10-01).\n const existing = this.flowsById.get(id);\n if (existing) {\n // Say so BEFORE the old flow goes inert, while its board is still readable.\n const dealt = existing.heldCardIds().length;\n if (dealt > 0) this.onReplacedFlow?.(id, dealt);\n existing.markClosed();\n }\n if (!claim) this.internals.registry.discardParked(flowPrefix(id));\n const flow = new Flow(this, this.internals, id, opts.seed ?? this.seed);\n this.flowsById.set(id, flow);\n if (opts.restore !== undefined) {\n const draft = emptyDraft();\n // Cloned: a caller holding one blob may resume two flows from it, and\n // an aliased playLog would then grow in both.\n const clean = this.planFlowRestore(id, structuredClone(opts.restore), otherClaims, draft);\n flow.restore(clean);\n const content = this.internals.bundle.content;\n opts.onRestoreReport?.(finishReport(content, content, [id], draft));\n }\n if (durable !== undefined) {\n flow.writeDurable(durable.plan);\n opts.onRestoreReport?.(finishReport(this.internals.bundle.content, durable.content, [id], durable.draft));\n }\n return flow;\n }\n\n getFlow(id: string): Flow | undefined {\n return this.flowsById.get(id);\n }\n\n /** Every live flow, open order. */\n flows(): Flow[] {\n return [...this.flowsById.values()];\n }\n\n /** Close the named flow: its handle goes INERT (every verb throws). A\n * dropped-but-held flow must not keep writing shared state (Patter's\n * stale-handle lesson). Unknown ids are a quiet no-op, like closing a\n * closed door. */\n closeFlow(id: string): void {\n const flow = this.flowsById.get(id);\n if (!flow) return;\n this.flowsById.delete(id);\n flow.markClosed();\n }\n\n /** @internal - Flow.close() routes here so both doors agree. */\n dropFlow(id: string, flow: Flow): void {\n if (this.flowsById.get(id) === flow) this.flowsById.delete(id);\n }\n\n /** Close every flow and reseed the shared state to its defaults (the\n * self-backed host scopes included; a host-bound one is the host's and is\n * not touched). */\n reset(): void {\n this.dropRun(false);\n this.reseedShared();\n // Values loaded for bags nobody has claimed yet are the old run's too: a\n // flow opened after the reset must not pick them up. Other engines' stay.\n this.internals.registry.discardParked(\"storylets/\");\n }\n\n /** End the run: clear the log, close every flow, forget spent cards. Each\n * flow's bags leave the registry; `keepFlows` names the flows whose values\n * are kept there for the flow that replaces them (a load into the game's\n * registry). */\n private dropRun(keepFlows: Set<string> | false): void {\n // The log is a run-lifetime utility and is not saved; a reset is a new run.\n // All three ports cleared it here and the reference did not, so `reset()`\n // (and `loadGame`) left the previous run's entries in place with `seq`\n // continuing across the boundary - while the same call on Godot, Unity or\n // Unreal returned an empty log (2026-08-29).\n this.engineLog = [];\n for (const [id, flow] of this.flowsById) {\n flow.releaseBags(keepFlows !== false && keepFlows.has(id));\n flow.markClosed();\n }\n this.flowsById.clear();\n this.spent.clear();\n }\n\n // --- shared scarcity (design/shared-scarcity.md) -----------------------------\n\n /** Cards a shared `redraw: \"never\"` has taken out of the world, by card id.\n * The claim ledger is DERIVED from live boards and so needs no storage;\n * this one is durable, so it rides the save's shared half. */\n private spent = new Set<string>();\n\n /** @internal */\n isTaken(cardId: string): boolean {\n return this.spent.has(cardId);\n }\n\n /** @internal - take a shared one-shot out of the world, as its play does.\n * Keyed by internal id. A host carries spends across a run with\n * `saveDurable` and `loadDurable`, which speak gameIds and check that the\n * card is still one that may be carried (ruling H). */\n markTaken(cardId: string): void {\n this.spent.add(cardId);\n }\n\n // --- the run's log (design/shared-scarcity.md 8.2) ---------------------------\n\n /** Every flow's events in one ordered stream, each tagged with its flow.\n * Opt in with the same `log` option the flow logs use; capped the same way.\n *\n * This exists because a flow's own log cannot answer the question a run\n * raises: when a story action in ANOTHER flow moves shared state, your\n * flow's log says nothing and your value simply changes. Reading a run\n * needs one stream that says who did what, and merging the per-flow logs by\n * hand is a thing every host would otherwise have to write. */\n private engineLog: EngineLogEntry[] = [];\n private engineSeq = 0;\n\n log(): readonly EngineLogEntry[] {\n return this.engineLog;\n }\n\n clearLog(): void {\n this.engineLog = [];\n }\n\n /** @internal - shared claims across every LIVE flow, card id -> holders.\n * Derived, which is what makes closeFlow and the openFlow replace release\n * what a flow was holding: its board leaves the map with it. `except`\n * leaves one flow out: what the REST of the world holds, which is the\n * question a resume under that name has to ask. */\n sharedClaims(except?: string): Map<string, number> {\n const counts = new Map<string, number>();\n for (const [flowId, flow] of this.flowsById) {\n if (flowId === except) continue;\n for (const cardId of flow.heldCardIds()) counts.set(cardId, (counts.get(cardId) ?? 0) + 1);\n }\n return counts;\n }\n\n // --- engine-level state access ----------------------------------------------\n\n /**\n * Read shared state by path: \"world.x\", \"story.gold\" (when shared),\n * \"box.village.heat\" (when shared) - the owner segment is its GAMEID\n * (design/engine-server.md 4.4). A ref that resolves PER-FLOW throws,\n * naming the fix - silently answering with some flow's copy (or a junk\n * default) was the bug Patter's engine.getProperty guard exists to stop.\n */\n getProperty(path: string): ScalarValue {\n const found = this.resolveShared(propertyPath(path));\n const value = found.kind === \"host\" ? found.host.resolver.get(found.name)\n : found.kind === \"scope\" ? this.internals.registry.get(found.token, found.name)\n : found.bag.get(found.name);\n if (value === undefined) throw new StoryletError(`no property at \"${path}\"`);\n return value;\n }\n\n setProperty(path: string, value: ScalarValue): void {\n const found = this.resolveShared(propertyPath(path));\n checkHostWrite(this.internals, path, value);\n if (found.kind === \"host\") {\n if (!found.host.set) {\n throw new StoryletError(`@${found.host.spec.token} is read-only here: the host bound no write`);\n }\n found.host.set(found.name, value, true);\n return;\n }\n if (found.kind === \"scope\") {\n this.internals.registry.set(found.token, found.name, value, { host: true });\n return;\n }\n // A host write: silent under the firing rule (no subscriber feedback\n // loop), but visible to the bag's audit hook - and a HOST write, so a\n // `writable: false` does not refuse it. That flag is the story's promise\n // about its own outcomes; this is the game speaking, in any scope.\n found.bag.set(found.name, value, { silent: true, reason: \"host setProperty\", host: true });\n }\n\n private resolveShared(path: string):\n | { kind: \"host\"; host: HostScopeState; name: string }\n | { kind: \"scope\"; token: string; name: string }\n | { kind: \"bag\"; bag: StateBag; name: string } {\n const parts = path.split(\".\");\n const perFlow = (): never => {\n throw new StoryletError(`\"${path}\" is per-flow state - read it on a Flow, not the Engine`);\n };\n const host = parts.length === 2 ? this.internals.hostScopes.get(parts[0]!) : undefined;\n if (host !== undefined) return { kind: \"host\", host, name: parts[1]! };\n // Another engine's game-wide scope (`patter.gold`): every engine reads every scope.\n if (parts.length === 2 && parts[0] !== \"story\" && this.internals.registry.has(parts[0]!)) {\n return { kind: \"scope\", token: parts[0]!, name: parts[1]! };\n }\n if (parts.length === 2 && parts[0] === \"story\") {\n const name = parts[1]!;\n if (ownValue(this.internals.shared.story, name) !== undefined) return { kind: \"bag\", bag: this.internals.shared.story, name };\n if (this.internals.flowDecls.story.some((d) => d.name === name)) perFlow();\n throw new StoryletError(`no property at \"${path}\"`);\n }\n if (parts.length === 3 && isOwnedScope(parts[0])) {\n const kind = parts[0];\n const [, segment, name] = parts as unknown as [string, string, string];\n const owner = ownerOrThrow(this.internals, kind, segment, name);\n if (owner.legacy) this.diagnose(legacyAddressMessage(this.internals, kind, segment, name));\n const id = owner.id;\n const bag = this.internals.shared[kind].get(id);\n if (bag !== undefined && ownValue(bag, name) !== undefined) return { kind: \"bag\", bag, name };\n if (this.internals.flowDecls[kind].get(id)?.some((d) => d.name === name)) perFlow();\n if (bag === undefined && !this.internals.flowDecls[kind].has(id)) throw new StoryletError(`no ${kind} store \"${segment}\"`);\n throw new StoryletError(`no property at \"${path}\"`);\n }\n throw new StoryletError(`bad property path \"${path}\"`);\n }\n\n /** The engine's own surface has no flow, so an engine-level diagnostic\n * carries the empty flow id - the same way a LoadReport's shared half\n * carries no flow. It reaches the run log and the engine tap; there is\n * nowhere else for it to go, and it fires only on a legacy address. */\n private diagnose(message: string): void {\n this.internals.emitEngine(\"\", { type: \"diagnostic\", where: \"property address\", message });\n }\n\n /** Content that names another engine's scope (`@patter.visits`) runs only\n * where that engine is on this registry: without it every read would answer\n * false and every write fail, so the flow is refused as it opens, before\n * anything changes. By then a game has built all of its engines, whatever\n * order it built them in. The same message on every runtime. */\n private assertExternalScopes(): void {\n for (const token of this.internals.bundle.externalScopes ?? []) {\n if (!this.internals.registry.has(token)) {\n throw new StoryletError(`this content names @${token}, which no engine on this registry registered: `\n + `give every engine the game's one registry`);\n }\n }\n }\n\n /** The shared surface as examiner rows: the host scopes (read through\n * their resolvers) then the shared partitions. Per-flow rows live on each Flow. */\n listProperties(): PropertyRow[] {\n const out: PropertyRow[] = hostRows(this.internals);\n // The bag composes its rows' addresses from its own pathPrefix, so the prefix here is\n // only the caller's label for the mount; the row arrives already addressed.\n const add = (_prefix: string, bag: StateBag): void => {\n for (const row of bag.rows()) out.push(row);\n };\n add(\"story\", this.internals.shared.story);\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of this.internals.shared[kind]) add(addressOf(this.internals, kind, id), bag);\n }\n return out;\n }\n\n /** The SHARED kernel bags with their store path prefixes (the state\n * logger's mount surface). The @world container is the host's own bag -\n * the host mounts it itself. */\n listBags(): BagMount[] {\n const mounts: BagMount[] = [{ prefix: \"story\", bag: this.internals.shared.story }];\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of this.internals.shared[kind]) mounts.push({ prefix: addressOf(this.internals, kind, id), bag });\n }\n return mounts;\n }\n\n /** Every flow's trace, one stream, each event tagged with its flow id. */\n subscribeTrace(handler: EngineTraceHandler): () => void {\n // A Set: the same handler subscribed twice is registered once (ruling F).\n this.engineTraceHandlers.add(handler);\n return () => { this.engineTraceHandlers.delete(handler); };\n }\n\n // --- persistence (schema 4) -------------------------------------------------\n\n /**\n * Live bundle refresh: rebuild on an edited bundle with the whole run carried\n * over, and return the replacement with the report its load produced.\n *\n * Standalone, that is a save and a load into a new engine, and this one is left\n * untouched (discard it). With the game's registry the two cannot both hold the\n * same keys, so this engine is spent afterwards (its flows closed, the\n * replacement holding everything on the same registry): it carries its\n * own values into the snapshot, steps out of the registry, and the replacement\n * loads them the way a standalone save loads: so the report covers the\n * properties the edit dropped, defaulted, or retyped, and a dropped property\n * is dropped rather than kept. Values the game loaded that were still waiting\n * for a flow of this engine carry across as they were, and nothing belonging\n * to any other engine is touched. A save for another project is refused before\n * anything moves; if the rebuild fails for any other reason, this engine takes\n * its registrations back and is left exactly as it was.\n */\n hotSwap(bundle: Bundle, opts: EngineOptions = {}): { engine: Engine; report: LoadReport } {\n if (bundle.content.project !== this.internals.bundle.content.project) {\n // Names the BUNDLE: there is no save in a hot swap, so the load's own\n // \"save is for project\" wording pointed the reader at the wrong thing.\n throw new StoryletError(`hotSwap: the bundle is for project \"${bundle.content.project}\", this engine runs \"${this.internals.bundle.content.project}\"`);\n }\n const options: EngineOptions = { ...this.creationOptions, ...opts };\n const snapshot = this.saveGame();\n const reg = this.internals.registry;\n if (this.internals.ownsRegistry) {\n // Its own registry: a save and a load into a new engine, as it always was,\n // and this engine is left untouched.\n const next = new Engine(bundle, { ...options, registry: undefined });\n return { engine: next, report: next.loadGame(snapshot) };\n }\n // This engine's own values, registered or still waiting for a flow.\n const mine: Record<string, Record<string, ScalarValue>> = {};\n for (const [key, values] of Object.entries(reg.save())) {\n if (key === \"story\" || key.startsWith(\"storylets/\")) mine[key] = values;\n }\n const registeredKeys = new Set([\n ...this.sharedMounts.map((m) => m.key),\n ...[...this.flowsById.values()].flatMap((f) => f.registeredKeys()),\n ]);\n const waiting = Object.fromEntries(Object.entries(mine).filter(([key]) => !registeredKeys.has(key)));\n // Step out of the registry. The bags keep their values, so stepping back in is exact.\n for (const { key } of this.sharedMounts) if (reg.has(key)) reg.remove(key);\n for (const flow of this.flowsById.values()) flow.releaseBags(false);\n let next: Engine | undefined;\n try {\n next = new Engine(bundle, { ...options, registry: reg });\n const report = next.loadGame({ ...snapshot, registry: mine });\n // Values that were waiting for a flow wait on, for the replacement's flow of that name.\n const stillWaiting = Object.fromEntries(Object.entries(waiting).filter(([key]) => !reg.has(key)));\n if (Object.keys(stillWaiting).length > 0) reg.load(stillWaiting, { keepParked: true });\n this.dropRun(false);\n return { engine: next, report };\n } catch (e) {\n // Back as it was. The replacement's registrations go; a constructor that\n // failed part way parked what it had claimed, so this engine's keys are\n // cleared of anything waiting, registered again, and its own values laid\n // back over them (the waiting ones parked again).\n next?.dropRun(false);\n for (const { key } of next?.sharedMounts ?? []) if (reg.has(key)) reg.remove(key);\n reg.discardParked(\"storylets/\");\n for (const { mount } of this.sharedMounts) mount();\n for (const flow of this.flowsById.values()) flow.mountBags();\n // `story` may have claimed what the failed replacement parked there: back to\n // its declarations, then this engine's own values over it.\n this.reseedShared();\n reg.load(mine, { keepParked: true });\n throw e;\n }\n }\n\n /** The whole engine's NON-property state, one envelope: the spent cards\n * once, then every live flow (board, clocks, cooldowns, PRNG, play log)\n * keyed by its id. The property values are the registry's: a standalone\n * engine (one that made its own registry) carries them here under\n * `registry`, self-backed host scopes included; a game that passed a registry\n * saves it once itself, beside each engine's envelope. */\n saveGame(): SaveEnvelope {\n return structuredClone({\n schema: SAVE_SCHEMA,\n content: this.internals.bundle.content,\n ...(this.internals.ownsRegistry ? { registry: this.registrySection() } : {}),\n shared: { spent: [...this.spent].map((id) => cardKey(this.internals, id)).sort() },\n flows: Object.fromEntries([...this.flowsById].map(([id, flow]) => [id, flow.snapshot(false)])),\n });\n }\n\n /** The registry's values in CANONICAL order, the order a load rebuilds them\n * in: the engine-wide keys as the constructor registered them, then each\n * flow's keys in the order the save's `flows` lists the flows (each flow's\n * own registration order), then\n * anything else the registry holds (values still waiting for a key), as the\n * registry lists it. The registry itself lists keys in registration order,\n * and a flow replaced in place (`open()` above keeps its slot in\n * `flowsById`) re-registers its keys at the END, so `openFlow(\"a\");\n * openFlow(\"b\"); openFlow(\"a\")` saved b's keys before a's while a load\n * rebuilt a's first: the same run, different `.storyletsave` bytes, and a\n * save loaded and saved again no longer equal to itself. It is the\n * 2026-08-29 rule carried into the section save@2 moved the per-flow values\n * to (2026-10-01). Order does not matter on READ (`partitionsFromSections`\n * sorts by key shape), so a save written in the old order loads as before.\n *\n * \"The order the save lists them\" is JS OBJECT order (ruling E,\n * 2026-10-06), which is not always `flows()` order: a flow id that looks like\n * an integer (\"7\") comes first, ascending, in any JS object, and a load\n * reopens the flows in the order the parsed save hands them over. Without\n * this, `openFlow(\"main\"); openFlow(\"7\")` saved main's keys first and the\n * loaded engine saved 7's first. */\n private registrySection(): Sections {\n const all = this.internals.registry.save() as Sections;\n const out: Sections = {};\n const take = (key: string): void => {\n if (Object.prototype.hasOwnProperty.call(all, key) && !Object.prototype.hasOwnProperty.call(out, key)) out[key] = all[key]!;\n };\n for (const { key } of this.sharedMounts) take(key);\n for (const flow of Object.values(Object.fromEntries(this.flowsById))) for (const key of flow.registeredKeys()) take(key);\n for (const key of Object.keys(all)) take(key);\n return out;\n }\n\n /** ONE flow's blob, to park a visit that is walking away: the same shape\n * the envelope carries per flow, and the same shape `openFlow`'s `restore`\n * option takes back (design/engine-server.md 4.1). Saving the whole\n * envelope to park one of four hundred players is wrong in cost and in\n * meaning. Throws for a name that is not open - a closed flow has nothing\n * left to save. */\n saveFlow(id: string): FlowSave {\n const flow = this.flowsById.get(id);\n if (!flow) throw new StoryletError(`unknown flow \"${id}\"`);\n // Parked whole, properties included: a parked flow's bags leave the\n // registry when it closes, so its values have to travel with it.\n return structuredClone(flow.snapshot(true));\n }\n\n /**\n * The engine's DURABLE HALF, the installation's memory (ruling H): every\n * shared `durable` property's value by its address, and every shared\n * durable one-shot that has been spent, by card gameId. What a new run\n * starts from, through `loadDurable`; a flow's own half is\n * `flow.saveDurable()`.\n */\n saveDurable(): DurableSave {\n const { bundle, shared, durable } = this.internals;\n return structuredClone({\n schema: DURABLE_SCHEMA,\n content: bundle.content,\n values: durableValues(shared, durable.shared),\n spent: durable.sharedCards.filter((e) => this.spent.has(e.card.id)).map((e) => effectiveGameId(e.card)).sort(),\n });\n }\n\n /**\n * Write the engine's durable half into this engine, a fresh one at the top\n * of a new run (ruling H). Every shared durable property takes the memory's\n * value, or its default where the memory carries none that fits, and every\n * spend the memory lists is taken out of the world. Nothing else is touched:\n * not a run-scoped value, not a flow, not a spend the memory does not name.\n *\n * Returns the report `loadGame` would give, for this half: an address this\n * build does not declare durable and shared is dropped, a value its\n * declaration no longer takes is retyped, a durable declaration the memory\n * lacks is defaulted, and a spend for a card that is no longer a shared\n * durable one-shot is a dropped spent card. Another project's memory, or an\n * unknown schema, is refused before anything moves.\n */\n loadDurable(memory: DurableSave): LoadReport {\n const internals = this.internals;\n assertDurable(internals, memory);\n const draft = emptyDraft();\n const plan = planDurable(internals, internals.durable.shared, internals.durable.sharedCards, memory, undefined, draft);\n for (const prop of internals.durable.shared) {\n const value = plan.values.has(prop)\n ? plan.values.get(prop)!\n : defaultFor(prop.decl as unknown as ScopeDeclaration);\n putDurable(durableBag(internals.shared, prop), prop.decl.name, value);\n }\n for (const cardId of plan.cards) this.spent.add(cardId);\n return finishReport(internals.bundle.content, memory.content, [], draft);\n }\n\n /** What `loadGame(envelope)` would do that is not a plain restore, without\n * doing any of it (design/engine-server.md 4.9). Pure: nothing on this\n * engine moves. A project mismatch is refused here exactly as `loadGame`\n * refuses it - it is the one thing neither call will tolerate. */\n previewLoad(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport {\n refuseUnreadableSave(envelope);\n this.assertSameProject(envelope);\n const plan = this.planLoad(envelope);\n if (plan.sections === undefined) return plan.report;\n // The held cards are checked against the values the envelope carries,\n // which only a load puts in place: so the load runs on a scratch engine of\n // its own, with its own registry, and this one is left as it was.\n const { seed, hostScopes } = this.creationOptions;\n const scratch = new Engine(this.internals.bundle, {\n ...(seed !== undefined ? { seed } : {}), ...(hostScopes !== undefined ? { hostScopes } : {}), onError: () => {},\n });\n try {\n return scratch.loadGame(envelope);\n } catch {\n return plan.report;\n }\n }\n\n /** What `openFlow(id, { restore: saved })` would do to a flow of that name,\n * without doing it: the same report shape, since a visit parked under one\n * build and resumed under the next raises the same questions. Pure. */\n previewFlowRestore(id: string, saved: FlowSave): LoadReport {\n const draft = emptyDraft();\n this.planFlowRestore(id, saved, this.sharedClaims(id), draft);\n const content = this.internals.bundle.content;\n return finishReport(content, content, [id], draft);\n }\n\n /** Restore: shared state once, then every flow REBUILT from its blob.\n * Handles held from before the load are closed and inert (Patter's\n * rule); take fresh ones from getFlow()/flows().\n *\n * Property values come from the registry. An envelope that carries them\n * (a standalone engine's, or a version 1 envelope) has them walked, cleaned,\n * and moved into the registry here, over fresh defaults. Otherwise the game\n * loads its registry itself, before or after this call: each flow's bags\n * are handed back to the registry with their values, and the restored\n * flows claim them. The report then covers only what this envelope holds;\n * the registry's own load rule applies to the values.\n *\n * Returns the report `previewLoad` would have given for this envelope: the\n * drift tolerance that makes a load forgiving is what hides its cost, so\n * the cost comes back with the load whether or not anybody looked first. */\n loadGame(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport {\n try {\n return this.loadGameInto(envelope);\n } finally {\n // The game's own refitting load of its registry lands before this one.\n this.internals.reportRefits();\n }\n }\n\n private loadGameInto(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport {\n refuseUnreadableSave(envelope);\n this.assertSameProject(envelope);\n this.assertExternalScopes();\n const plan = this.planLoad(structuredClone(envelope));\n const reg = this.internals.registry;\n if (plan.sections !== undefined) {\n // The envelope carries the values: every bag of this engine back to its\n // defaults, then the cleaned values over them.\n this.reset();\n // The engine's own registry takes the save wholesale. A game's registry may\n // hold values the game loaded for other engines, still waiting: add to\n // those, never replace them.\n if (this.internals.ownsRegistry) reg.load(plan.sections);\n else reg.load(plan.sections, { keepParked: true });\n } else {\n this.dropRun(new Set(plan.flows.map(([id]) => id)));\n }\n for (const id of plan.spent) this.spent.add(id);\n for (const [id, clean] of plan.flows) this.open(id, {}, true).restore(clean);\n if (plan.sections === undefined) return plan.report;\n // With the values in place, every held card is checked as a deal checks\n // it, and what no longer qualifies goes, in the report (the hands-on\n // review, 2026-10, foundation 7). A game that loads its own registry has\n // no values here yet: its next deal does the same.\n const dropped = plan.flows.flatMap(([id]) =>\n (this.flowsById.get(id)?.dropUnqualified() ?? []).map((e) => ({ flow: id, ...e })));\n if (dropped.length === 0) return plan.report;\n return {\n ...plan.report,\n exact: false,\n evicted: byKey([...plan.report.evicted, ...dropped], (e) => [e.flow, e.hand, e.card, e.reason].join(SORT_SEP)),\n };\n }\n\n private assertSameProject(envelope: SaveEnvelope | SaveEnvelopeV1): void {\n if (envelope.content.project !== this.internals.bundle.content.project) {\n throw new StoryletError(`save is for project \"${envelope.content.project}\", bundle is \"${this.internals.bundle.content.project}\"`);\n }\n }\n\n /** The whole-envelope walk: the report, and the cleaned state the apply\n * half writes. Nothing here touches the engine, which is what lets\n * previewLoad and loadGame share it. */\n private planLoad(envelope: SaveEnvelope | SaveEnvelopeV1): {\n report: LoadReport;\n /** The cleaned property values to move into the registry, when the\n * envelope carries any. */\n sections?: Sections;\n spent: string[];\n flows: [string, FlowSave][];\n } {\n const schema = (envelope as { schema?: unknown }).schema;\n if (schema !== SAVE_SCHEMA && schema !== SAVE_SCHEMA_V2 && schema !== SAVE_SCHEMA_V1) throw new StoryletError(`unsupported save schema: ${String(schema)}`);\n const draft = emptyDraft();\n // Where the property values are, if this envelope has them: a version 1\n // envelope's partitions, or a standalone engine's registry sections.\n const flowIds = new Set(Object.keys(envelope.flows ?? {}));\n let moved: { shared: PropsPartition; flows: Map<string, PropsPartition>; rest: Sections } | undefined;\n if (envelope.schema === SAVE_SCHEMA_V1) {\n moved = {\n shared: envelope.shared?.props ?? emptyPartitionValues(),\n flows: new Map(Object.entries(envelope.flows ?? {}).map(([id, f]) => [id, f.props ?? emptyPartitionValues()])),\n rest: {},\n };\n } else if (envelope.registry !== undefined) {\n moved = partitionsFromSections(envelope.registry, flowIds);\n }\n const shared = moved !== undefined\n ? walkPartition(this.internals, this.internals.sharedDecls, moved.shared, undefined, draft)\n : undefined;\n const spent: string[] = [];\n for (const key of envelope.shared?.spent ?? []) {\n const entry = savedCard(this.internals, key);\n if (entry !== undefined) spent.push(entry.card.id);\n else draft.droppedSpent.push(key);\n }\n const flows: [string, FlowSave][] = [];\n const sections: Sections | undefined = moved !== undefined ? { ...moved.rest } : undefined;\n if (sections !== undefined && shared !== undefined) {\n sectionsOf(shared, (kind, id) => (kind === \"story\" ? \"story\" : sharedKey(kind, id!)), sections);\n }\n for (const [id, saved] of Object.entries(envelope.flows ?? {})) {\n const withProps: FlowSave = moved !== undefined\n ? { ...saved, props: moved.flows.get(id) ?? emptyPartitionValues() }\n : { ...saved };\n const clean = this.planFlowRestore(id, withProps, undefined, draft);\n if (sections !== undefined && clean.props !== undefined) {\n // The flow's values go into the registry, where its bags claim them; the\n // restored flow itself carries none.\n sectionsOf(clean.props, (kind, owner) => flowKey(id, kind, owner), sections);\n delete clean.props;\n }\n flows.push([id, clean]);\n }\n return {\n report: finishReport(this.internals.bundle.content, envelope.content, flows.map(([id]) => id), draft),\n ...(sections !== undefined ? { sections } : {}),\n spent, flows,\n };\n }\n\n /** One flow's walk. `otherClaims` is the rest of the world's shared ledger\n * and is present only for a SINGLE-flow restore into a live engine: a\n * whole-envelope load rebuilds every flow from one consistent moment, so\n * there is nobody else to compete with. */\n private planFlowRestore(\n id: string,\n saved: FlowSave,\n otherClaims: Map<string, number> | undefined,\n draft: ReportDraft,\n ): FlowSave {\n const internals = this.internals;\n // A flow blob without properties (a version 2 envelope's: the registry has\n // them) has nothing to walk.\n const props = saved.props !== undefined ? walkPartition(internals, internals.flowDecls, saved.props, id, draft) : undefined;\n\n // Every key the save carries resolves to the build's internal id (see\n // savedCard); one the build no longer has is named by the key itself.\n const cooldowns: Record<string, number> = {};\n for (const [key, turn] of Object.entries(saved.cooldowns ?? {})) {\n const entry = savedCard(internals, key);\n if (entry !== undefined) cooldowns[entry.card.id] = turn;\n else draft.droppedCooldowns.push({ flow: id, card: key });\n }\n\n // A deleted entity has no gameId left, so it is named by the key the save\n // carries; everything the build still knows is named by its gameId.\n const cardName = (key: string): string => {\n const entry = savedCard(internals, key);\n return entry ? effectiveGameId(entry.card) : key;\n };\n const board: Record<string, string[]> = {};\n const restored = new Map<string, number>();\n for (const [handKey, keys] of Object.entries(saved.board ?? {})) {\n const known = savedHand(internals, handKey);\n if (known === undefined) {\n for (const key of keys) {\n draft.evicted.push({ flow: id, hand: handKey, card: cardName(key), reason: \"hand-vanished\" });\n }\n continue;\n }\n const hand = effectiveGameId(known.hand);\n const kept: string[] = [];\n for (const key of keys) {\n const entry = savedCard(internals, key);\n if (entry === undefined) {\n draft.evicted.push({ flow: id, hand, card: key, reason: \"vanished\" });\n continue;\n }\n const cardId = entry.card.id;\n if (otherClaims !== undefined && cardIsShared(entry.card, entry.deck.shared ?? false)) {\n const held = (otherClaims.get(cardId) ?? 0) + (restored.get(cardId) ?? 0);\n if (held >= sharedCap(entry.card)) {\n draft.evicted.push({ flow: id, hand, card: effectiveGameId(entry.card), reason: \"claimed-elsewhere\" });\n continue;\n }\n restored.set(cardId, (restored.get(cardId) ?? 0) + 1);\n }\n kept.push(cardId);\n }\n board[known.hand.id] = kept;\n }\n\n const turns: Record<string, number> = {};\n for (const [key, turn] of Object.entries(saved.turns ?? {})) {\n const box = savedBox(internals, key);\n if (box !== undefined) turns[box.id] = turn;\n }\n\n return {\n ...(props !== undefined ? { props } : {}),\n turns,\n prng: saved.prng,\n cooldowns,\n board,\n playLog: saved.playLog ?? [],\n };\n }\n}\n\n// --- the Flow -----------------------------------------------------------------\n\nexport class Flow {\n readonly id: string;\n private readonly engine: Engine;\n private readonly internals: Internals;\n private closed = false;\n\n private prng: Prng;\n /** Per-box turn counters, keyed by box id (schema 3.4) - per flow. */\n private turnCounts = new Map<string, number>();\n private cooldowns: Record<string, number> = {};\n /** The board: hand contents (card ids, dealt order), keyed by hand id. */\n private boardContents = new Map<string, string[]>();\n private playLog: PlayRecord[] = [];\n // --- play-history indexes -------------------------------------------------\n // The four play-history host functions used to SCAN playLog on every call,\n // and they are called once per candidate card per ask, so dealing was\n // O(candidates x playLog): a shipped game got measurably slower the longer\n // somebody played it, which is the failure mode nobody meets in testing.\n // Measured before the change, 2000 cards: a box with no history condition\n // held flat at 0.3ms while `count_played` went 0.8ms -> 27.9ms as the log\n // reached 4000 plays.\n //\n // These are a pure summary of `playLog`, maintained where it is appended and\n // rebuilt where it is replaced, so they cannot drift from it. They are NOT\n // saved: `playLog` is the record, this is a derivation, and rebuilding on\n // load keeps the save format untouched.\n //\n // The tag keys are the played card's OWN (groupId, tagId) pairs, which is\n // what makes the box-local rule survive: a group name resolves inside the\n // ASKING box, so a card in another box carries different ids and cannot\n // match, exactly as the per-record `inTag` check used to decide.\n private playCount = new Map<string, number>();\n private lastPlayOf = new Map<string, PlayRecord>();\n private tagPlayCount = new Map<string, number>();\n private lastPlayInTag = new Map<string, PlayRecord>();\n /** The per-flow property partitions (the not-shared halves), each bag\n * that declares something registered under this flow's keys. */\n private stores: Partition;\n private readonly registered: string[] = [];\n /** This flow's bags that declare something, under their registry keys. */\n private readonly bagKeys: Array<[string, StateBag]> = [];\n\n private traceHandlers = new Set<TraceHandler>();\n private logEntries: LogEntry[] = [];\n private logSeq = 0;\n\n /** Merged read view per scope, built once (bags are stable for the\n * flow's life): the flow's own bag first, the shared bag behind it.\n * Names are disjoint (shared XOR per-flow by declaration), so \"first\"\n * is routing, not shadowing. */\n private readonly storyReader: ScopeResolver;\n private readonly boxReaders = new Map<string, ScopeResolver>();\n private readonly deckReaders = new Map<string, ScopeResolver>();\n\n /** @internal - built by Engine.openFlow / Engine.loadGame only. */\n constructor(engine: Engine, internals: Internals, id: string, seed: number) {\n this.engine = engine;\n this.internals = internals;\n this.id = id;\n this.prng = makePrng(seed);\n this.stores = buildPartition(internals, internals.flowDecls);\n // Register the bags: each claims whatever the registry holds for its key (a\n // load); openFlow discarded that first for a fresh flow.\n const put = (key: string, bag: StateBag): void => {\n if (bag.declarations().length === 0) return; // holds nothing: not registered\n this.bagKeys.push([key, bag]);\n };\n put(flowKey(id, \"story\"), this.stores.story);\n for (const kind of OWNED_SCOPES) for (const [owner, bag] of this.stores[kind]) put(flowKey(id, kind, owner), bag);\n this.mountBags();\n // A bag that claimed values a refitting load parked says what it put back.\n internals.reportRefits();\n for (const box of internals.bundle.boxes) {\n this.turnCounts.set(box.id, 0);\n for (const hand of box.hands) this.boardContents.set(hand.id, []);\n }\n // Own values only (`ownValue`): a name Object.prototype carries is not a\n // property of any scope here.\n const pair = (own: StateBag | undefined, shared: StateBag | undefined): ScopeResolver => ({\n get: (n) => ownValue(own, n) ?? ownValue(shared, n),\n });\n // The shared bags are built once for the engine's life and reseeded in\n // place by loadGame/reset, so these readers never go stale.\n this.storyReader = pair(this.stores.story, internals.shared.story);\n for (const box of internals.bundle.boxes) {\n this.boxReaders.set(box.id, pair(this.stores.box.get(box.id), internals.shared.box.get(box.id)));\n for (const deck of box.decks) {\n this.deckReaders.set(deck.id, pair(this.stores.deck.get(deck.id), internals.shared.deck.get(deck.id)));\n }\n }\n }\n\n // --- lifetime ----------------------------------------------------------------\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n /** Close this flow: the handle goes inert, every verb throws. */\n close(): void {\n if (this.closed) return;\n this.engine.dropFlow(this.id, this);\n this.markClosed();\n }\n\n /** @internal - the handle goes inert, its bags leave the registry, and it\n * lets go of its trace handlers: a closed flow never emits again, and a\n * handler that holds the flow would otherwise hold it for good on a runtime\n * that counts references (Godot, C++). */\n markClosed(): void {\n this.releaseBags(false);\n this.traceHandlers.clear();\n this.closed = true;\n }\n\n /** @internal - take this flow's bags out of the registry; with `keep`, their\n * values wait there for the flow that replaces this one (a load into the\n * game's registry). Idempotent. */\n releaseBags(keep: boolean): void {\n for (const key of this.registered) this.internals.registry.remove(key, { keep });\n this.registered.length = 0;\n }\n\n /** @internal - the registry keys this flow holds right now. */\n registeredKeys(): string[] {\n return [...this.registered];\n }\n\n /** @internal - register this flow's bags (again): at construction, and when a\n * failed hotSwap hands them back. Each claims what the registry holds for it. */\n mountBags(): void {\n for (const [key, bag] of this.bagKeys) {\n this.internals.registry.mountOwned(key, bag, { owner: OWNER });\n this.registered.push(key);\n }\n }\n\n private assertOpen(): void {\n if (this.closed) throw new StoryletError(`flow \"${this.id}\" is closed`);\n }\n\n /** A box by gameId or id, or the refusal every verb that takes one gives. */\n private resolveBox(boxRef: string): Box<Expression> {\n const box = this.internals.boxesByGameId.get(boxRef) ?? this.internals.boxesById.get(boxRef);\n if (!box) throw new StoryletError(`unknown box \"${boxRef}\"`);\n return box;\n }\n\n /** A box's current turn (schema 3.4), on THIS flow's clock. */\n turn(boxRef: string): number {\n this.assertOpen();\n return this.turnCounts.get(this.resolveBox(boxRef).id) ?? 0;\n }\n\n /** Subscribe to this flow's deal/play trace (schema 5). Returns the\n * unsubscribe. With no subscribers anywhere the flow does no trace work. */\n subscribeTrace(handler: TraceHandler): () => void {\n // A Set: the same handler subscribed twice is registered once (ruling F).\n this.traceHandlers.add(handler);\n return () => { this.traceHandlers.delete(handler); };\n }\n\n private get tracing(): boolean {\n return this.traceHandlers.size > 0 || this.internals.logCap !== undefined || this.internals.engineTracing();\n }\n\n private emit(event: TraceEvent, turn?: number): void {\n if (this.internals.logCap !== undefined) {\n this.logEntries.push({ ...event, seq: this.logSeq++, ...(turn !== undefined ? { turn } : {}) });\n if (this.logEntries.length > this.internals.logCap) this.logEntries.splice(0, this.logEntries.length - this.internals.logCap);\n }\n // From a copy (ruling F): a handler that subscribes or unsubscribes during\n // an event changes who hears the NEXT one, never this one. A live Set\n // would call a handler added mid-delivery straight away.\n for (const handler of [...this.traceHandlers]) handler(event);\n this.internals.emitEngine(this.id, event, turn);\n }\n\n /** The retained flow log (opt-in via the Engine's `log`), oldest first,\n * capped. The introspection seam for hosts and tools; the durable play\n * history in a save stays `playLog` (schema 4) - the log is a\n * flow-lifetime utility and is NOT saved. */\n log(): readonly LogEntry[] {\n return this.logEntries;\n }\n\n /** Empty the retained log; `seq` keeps counting, so ordering across a\n * clear stays meaningful. */\n clearLog(): void {\n this.logEntries = [];\n }\n\n // --- expression plumbing ----------------------------------------------------\n\n private node(expr: Expression): ExprNode {\n let node = this.internals.nodeCache.get(expr);\n if (!node) {\n node = deserialiseAst(expr.ast);\n this.internals.nodeCache.set(expr, node);\n }\n return node;\n }\n\n /** Tag group names are box-scoped: two boxes may name a group the same way\n * (schema 1 - boxes namespace their groups), so a name is only ever\n * resolved inside the box being asked, never bundle-wide. Ids are\n * project-unique and accepted here too, still confined to the box.\n *\n * A box on the project map sees ONE namespace: its own groups, then the\n * map's group (design/project-map-contract.md 3.1). A box that has not\n * opted in does not see the map's name at all, so a peek naming it there\n * is the ordinary unknown-group refusal. Own groups first is stated for\n * determinism only: a bundle that loads never has the two share a name. */\n private groupInBox(box: Box<Expression>, ref: string): TagGroup | undefined {\n const groups = groupsOfBox(this.internals.bundle, box);\n return groups.find((g) => effectiveGameId(g) === ref)\n ?? groups.find((g) => g.id === ref);\n }\n\n /** Fold one play into the indexes. O(the card's tags), not O(the log). */\n private indexPlay(record: PlayRecord): void {\n this.playCount.set(record.card, (this.playCount.get(record.card) ?? 0) + 1);\n this.lastPlayOf.set(record.card, record);\n const entry = this.internals.cardsByGameId.get(record.card);\n if (!entry) return;\n for (const [groupId, tagIds] of Object.entries(entry.card.tags ?? {})) {\n for (const tagId of tagIds) {\n // Keyed by the PLAYED card's box: history is box-specific (D7).\n const key = tagKey(entry.box.id, groupId, tagId);\n this.tagPlayCount.set(key, (this.tagPlayCount.get(key) ?? 0) + 1);\n this.lastPlayInTag.set(key, record);\n }\n }\n }\n\n /** Rebuild the indexes from the log. Called wherever `playLog` is REPLACED\n * rather than appended to, which is `restore` alone. */\n private rebuildPlayIndex(): void {\n this.playCount.clear();\n this.lastPlayOf.clear();\n this.tagPlayCount.clear();\n this.lastPlayInTag.clear();\n for (const record of this.playLog) this.indexPlay(record);\n }\n\n /** One host per box, built once.\n *\n * The closures below read `this.playCount`, `this.turnCounts` and the rest\n * LIVE, so a cached host answers with current state - which is what makes\n * caching safe rather than a snapshot bug. Unreal did this from the start\n * (`hostsByBox_`, built at flow construction) and the other three rebuilt a\n * host, and its closures, on every `evalCtx` call: once per deck per ask,\n * and once per surviving card in the eviction pass. Structural divergence\n * in one port AND the allocation the audit flagged, so the other three\n * copied it (2026-08-29). Lazy rather than eager, so a bundle's unvisited\n * boxes cost nothing. */\n private hostsByBox = new Map<string, StoryletsHost>();\n\n private host(box: Box<Expression>): StoryletsHost {\n const cached = this.hostsByBox.get(box.id);\n if (cached !== undefined) return cached;\n const made = this.makeHost(box);\n this.hostsByBox.set(box.id, made);\n return made;\n }\n\n /** `box` is the box whose ask is being evaluated: the play-history\n * functions take a bare group name, so it resolves there, and they count\n * only that box's own plays (the box is in the index key). That was\n * automatic while every group was a box's; a project-map zone is shared,\n * and its history is still not (design/project-map-contract.md 3.7, D7).\n * History is THIS flow's: countPlayed answers \"have I done this\". */\n private makeHost(box: Box<Expression>): StoryletsHost {\n /** A group NAME and tag name resolved in THIS box, as the index's key,\n * with this box in it: a zone's plays in another box are not this box's\n * history. Undefined when either name is unknown here, which answers\n * \"never\".\n *\n * MEMOISED per (group, tag): the answer depends only on the bundle, which\n * never changes under a flow, and a history condition asks it once per\n * candidate card per ask, each time a walk of the box's groups and then\n * the group's tags. */\n const keys = new Map<string, string | undefined>();\n const keyOf = (group: string, tag: string): string | undefined => {\n const memo = `${group}\\u001f${tag}`;\n if (keys.has(memo)) return keys.get(memo);\n const found = this.groupInBox(box, group);\n const t = found?.tags.find((v) => v.gameId === tag);\n const key = found && t ? tagKey(box.id, found.id, t.id) : undefined;\n keys.set(memo, key);\n return key;\n };\n /** Turns-since is measured on the played card's box's clock (3.4). */\n const since = (record: PlayRecord): number => {\n const entry = this.internals.cardsByGameId.get(record.card);\n if (!entry) return NEVER_PLAYED;\n return (this.turnCounts.get(entry.box.id) ?? 0) - record.turn;\n };\n return {\n nextRandom: () => this.prng.next(),\n countPlayed: (card) => this.playCount.get(card) ?? 0,\n turnsSincePlayed: (card) => {\n const last = this.lastPlayOf.get(card);\n return last ? since(last) : NEVER_PLAYED;\n },\n countPlayedIn: (group, tag) => {\n const key = keyOf(group, tag);\n return key === undefined ? 0 : this.tagPlayCount.get(key) ?? 0;\n },\n turnsSincePlayedIn: (group, tag) => {\n const key = keyOf(group, tag);\n const last = key === undefined ? undefined : this.lastPlayInTag.get(key);\n return last ? since(last) : NEVER_PLAYED;\n },\n };\n }\n\n /** The evaluation environment (schema 3.1/6.2): @box/@deck resolve to the\n * card under evaluation; in hand-condition contexts @deck is an empty bag,\n * so any reference is an eval error (missing-policy throw). Every scope\n * is the flow's MERGED view - its own copies over the shared values,\n * names disjoint - and each host scope reads through the engine's resolver. */\n private evalCtx(box: Box<Expression>, deck: Deck<Expression> | undefined, handEnv: HandEnv): EvalContext {\n const others = this.internals.registryView();\n return {\n scopes: {\n // Every other engine's game-wide scope first (every engine reads every\n // scope); this engine's own tokens are its merged views, over the top.\n ...others.scopes,\n ...this.internals.hostResolvers,\n story: this.storyReader,\n box: this.boxReaders.get(box.id) ?? EMPTY_SCOPE,\n deck: deck ? this.deckReaders.get(deck.id) ?? EMPTY_SCOPE : EMPTY_SCOPE,\n hand: handEnv.bag,\n },\n host: this.host(box) as unknown as Record<string, unknown>,\n // The quality channel, answering for THIS ask's box and deck. Only wired\n // when a quality exists, so a bundle without one evaluates byte-\n // identically to before the feature.\n ...(this.internals.hasQualities || others.qualities !== undefined ? {\n qualities: (scope: string, name: string): readonly string[] | undefined =>\n scope === \"story\" ? this.internals.ladders.story.get(name)\n : scope === \"box\" ? this.internals.ladders.box.get(box.id)?.get(name)\n : scope === \"deck\" && deck ? this.internals.ladders.deck.get(deck.id)?.get(name)\n // @hand is composed, so the ladder belongs to whatever supplied the\n // value THIS ask: the bound tag, or the asking hand. `sources` is\n // already the map that answers that, because write-back needs it.\n : scope === \"hand\" ? this.handLadder(handEnv, name)\n : this.internals.ladders.host.get(scope)?.get(name) ?? others.qualities?.(scope, name),\n } : {}),\n };\n }\n\n /** The ladder behind one composed @hand name, or undefined when the name is\n * not a quality (or came from criteria, which are tag NAMES, never state). */\n private handLadder(handEnv: HandEnv, name: string): readonly string[] | undefined {\n const source = handEnv.sources.get(name);\n if (!source) return undefined;\n return source.kind === \"value\" ? this.internals.ladders.value.get(source.id)?.get(name)\n : source.kind === \"hand\" ? this.internals.ladders.hand.get(source.id)?.get(name)\n : undefined;\n }\n\n private eval(expr: Expression, ctx: EvalContext): ScalarValue {\n return evaluate(this.node(expr), ctx, storyletsDialect);\n }\n\n /** `node` names whose condition it is (`card <gameId>`, ...): a function, so\n * the label is built only when an evaluation fails. */\n private passes(expr: Expression | undefined, ctx: EvalContext, node: () => string): boolean {\n if (!expr) return true;\n try {\n return conditionPasses(this.eval(expr, ctx));\n } catch (e) {\n // An eval error is never a silent pass: the card/deck is unavailable\n // (schema 3.1), and the game hears of it through onError, tracing or\n // not; the trace carries it too.\n const message = e instanceof Error ? e.message : String(e);\n const at = node();\n const source = (expr as { src?: unknown }).src;\n this.internals.onError({ flow: this.id, kind: \"condition\", node: at, ...(typeof source === \"string\" && source !== \"\" ? { source } : {}), message });\n if (this.tracing) {\n this.emit({ type: \"diagnostic\", where: at.startsWith(\"deck \") ? `${at} gate` : `${at} condition`, message });\n }\n return false;\n }\n }\n\n // --- resolving asks (schema 2.6 + 3.6) -----------------------------------------\n\n private tagByGameId(group: TagGroup, gameId: string): Tag | undefined {\n return group.tags.find((t) => t.gameId === gameId);\n }\n\n /** A deal's ask: the hand's template bindings + chosen tags, or its rule's\n * bindings, plus the implicit home binding (schema 2.4). */\n private askForHand(hand: Hand<Expression>, box: Box<Expression>): AskDescriptor {\n const boundTags = new Map<string, string>();\n const askNames: Record<string, string> = {};\n /** A template instance's `chosen` and a standalone hand's rule\n * `bindings` bind the same way, so one loop serves both: a card reading\n * @hand.<group> must not care HOW the group got bound\n * (design/hand-typing.md, the residues). */\n const bindAll = (bindings: Record<string, string> | undefined): void => {\n for (const [groupId, tagId] of Object.entries(bindings ?? {})) {\n // A hole filled from a property rather than with a tag: resolve it\n // now, before tag composition (4.6, the hand that moves).\n if (isHoleRef(tagId)) {\n this.fillHoleFromProperty(hand, groupId, tagId, boundTags, askNames);\n continue;\n }\n boundTags.set(groupId, tagId);\n const found = this.internals.groupsById.get(groupId);\n const tag = found?.group.tags.find((t) => t.id === tagId);\n if (found && tag) askNames[effectiveGameId(found.group)] = effectiveGameId(tag);\n }\n };\n let condition: Expression | undefined;\n if (hand.template !== undefined) {\n const template = this.internals.templatesById.get(hand.template);\n if (!template) throw new StoryletError(`hand \"${effectiveGameId(hand)}\": unknown template \"${hand.template}\"`);\n // A template's FIXED bindings bind and are not named in @hand.\n for (const [groupId, tagId] of Object.entries(template.bindings ?? {})) {\n boundTags.set(groupId, tagId);\n }\n bindAll(hand.chosen);\n condition = template.condition;\n } else {\n bindAll(hand.rule?.bindings);\n condition = hand.rule?.condition;\n }\n boundTags.set(PLACE_GROUP, hand.id);\n this.bindStateGroups(box, boundTags, askNames);\n return { box, hand, ...(condition !== undefined ? { condition } : {}), boundTags, askNames };\n }\n\n /** A peek's ask: raw criteria ({group gameId: tag gameId}), bindings only,\n * no condition slot (schema 3.1; the boundary, Reboot 4). */\n private askForPeek(box: Box<Expression>, criteria: Record<string, string>): AskDescriptor {\n const boundTags = new Map<string, string>();\n const askNames: Record<string, string> = {};\n for (const [groupRef, tagRef] of Object.entries(criteria)) {\n if (groupRef === PLACE_GROUP) {\n const hand = this.internals.handsByGameId.get(tagRef) ?? this.internals.handsById.get(tagRef);\n if (!hand) throw new StoryletError(`peek: unknown hand \"${tagRef}\" in home criteria`);\n boundTags.set(PLACE_GROUP, hand.hand.id);\n continue;\n }\n const found = this.groupInBox(box, groupRef);\n if (!found) {\n throw new StoryletError(`peek: unknown tag group \"${groupRef}\" in box \"${effectiveGameId(box)}\"`);\n }\n const tag = this.tagByGameId(found, tagRef) ?? found.tags.find((t) => t.id === tagRef);\n if (!tag) throw new StoryletError(`peek: unknown tag \"${tagRef}\" in group \"${effectiveGameId(found)}\"`);\n boundTags.set(found.id, tag.id);\n askNames[effectiveGameId(found)] = effectiveGameId(tag);\n }\n this.bindStateGroups(box, boundTags, askNames);\n return { box, boundTags, askNames };\n }\n\n /**\n * Fill one hole from the property its value names: the hand that moves\n * (design/engine-server.md 4.6).\n *\n * The semantics are `bindStateGroups`' below, word for word, applied per\n * HOLE instead of per group: resolved at ask time, and a value naming no tag\n * leaves the hole UNBOUND (a wildcard) with a diagnostic rather than dealing\n * a silently empty hand. What is added is the `@hand` scope - the asking\n * hand's OWN declared state, read here from the flow's merged view (the\n * shared half under the flow's own, so a `shared: true` declaration moves\n * the hole for every flow and a per-flow one moves it for this flow alone).\n *\n * Read BEFORE tag composition, which is the whole reason it is safe: the\n * @hand bag a card sees is built from the bound tags, so resolving a hole\n * from it would be circular. A hand's own declarations are not, so they are.\n */\n private fillHoleFromProperty(\n hand: Hand<Expression>, groupId: string, ref: string,\n boundTags: Map<string, string>, askNames: Record<string, string>,\n ): void {\n const found = this.internals.groupsById.get(groupId);\n const groupName = found ? effectiveGameId(found.group) : groupId;\n const where = `hand ${effectiveGameId(hand)}, tag group ${groupName}`;\n const parsed = parseHoleRef(ref);\n if (!parsed) {\n this.emit({ type: \"diagnostic\", where, message: `\"${ref}\" is not a @hand, @world or @story property reference` });\n return;\n }\n if (!found) {\n this.emit({ type: \"diagnostic\", where, message: `\"${ref}\" fills a tag group that is not in this box` });\n return;\n }\n let value: ScalarValue | undefined;\n if (parsed.scope === \"hand\") {\n value = this.valuesOf(\"hand\", hand.id)[parsed.name];\n } else {\n try {\n value = this.getProperty(`${parsed.scope}.${parsed.name}`);\n } catch {\n value = undefined;\n }\n }\n if (value === undefined) {\n this.emit({ type: \"diagnostic\", where, message: `\"${ref}\" names a property that is not declared` });\n return;\n }\n const wanted = typeof value === \"string\" ? value : String(value);\n const tag = found.group.tags.find((t) => effectiveGameId(t) === wanted);\n if (!tag) {\n this.emit({ type: \"diagnostic\", where, message: `${ref} is \"${wanted}\", which is not one of the tags of \"${groupName}\"` });\n return;\n }\n boundTags.set(groupId, tag.id);\n askNames[groupName] = effectiveGameId(tag);\n }\n\n /**\n * Bind every `boundBy` group in the box from the property it names.\n *\n * The gap this closes: only a hand could bind a group, and `deal` takes no\n * criteria, so an axis driven by state (an act, a chapter) had nowhere to\n * gate. Runs AFTER the hand's own bindings and never overwrites one: an\n * explicit binding is a deliberate act and beats a default.\n *\n * A value naming no tag in the group leaves the group UNBOUND rather than\n * matching nothing. Unbound is a wildcard, so the ask still deals; a silent\n * empty hand would look like content that does not exist, and the diagnostic\n * is what says otherwise.\n */\n private bindStateGroups(box: Box<Expression>, boundTags: Map<string, string>, askNames: Record<string, string>): void {\n // The box's own groups and, when it is on the project map, the map's\n // group: a `boundBy` there binds in every opted-in box (3.2).\n for (const group of groupsOfBox(this.internals.bundle, box)) {\n if (group.boundBy === undefined || boundTags.has(group.id)) continue;\n const ref = /^@(world|story)\\.([a-z][a-z0-9_-]*)$/.exec(group.boundBy);\n if (!ref) {\n this.emit({ type: \"diagnostic\", where: `tag group ${effectiveGameId(group)}`, message: `boundBy \"${group.boundBy}\" is not a @world or @story property reference` });\n continue;\n }\n let value: ScalarValue | undefined;\n try {\n value = this.getProperty(`${ref[1]}.${ref[2]}`);\n } catch {\n this.emit({ type: \"diagnostic\", where: `tag group ${effectiveGameId(group)}`, message: `boundBy \"${group.boundBy}\" names a property that is not declared` });\n continue;\n }\n const wanted = typeof value === \"string\" ? value : String(value);\n const tag = group.tags.find((t) => effectiveGameId(t) === wanted);\n if (!tag) {\n this.emit({ type: \"diagnostic\", where: `tag group ${effectiveGameId(group)}`, message: `${group.boundBy} is \"${wanted}\", which is not one of its tags` });\n continue;\n }\n boundTags.set(group.id, tag.id);\n askNames[effectiveGameId(group)] = effectiveGameId(tag);\n }\n }\n\n // --- @hand composition (schema 3.6) -------------------------------------------\n\n /** A store's full value view for one owner: the shared half under the\n * flow's half. Names are disjoint, so the spread is routing, not\n * shadowing. */\n private valuesOf(kind: Exclude<PartitionKind, \"story\">, id: string): PropertyBag {\n // No prototype: `@hand.constructor` must find nothing, not Object's.\n return Object.assign(Object.create(null) as PropertyBag,\n this.internals.shared[kind].get(id)?.values ?? {},\n this.stores[kind].get(id)?.values ?? {});\n }\n\n private buildHandEnv(ask: AskDescriptor): HandEnv {\n // No prototype, as above: the composed @hand is read by plain index, so an\n // ordinary object would answer `@hand.constructor` with a function.\n const bag: PropertyBag = Object.create(null) as PropertyBag;\n const sources = new Map<string, HandSource>();\n\n // 1. Tag properties of every bound tag (home binds a hand, not a tag).\n for (const [groupId, tagId] of ask.boundTags) {\n if (groupId === PLACE_GROUP) continue;\n for (const [name, value] of Object.entries(this.valuesOf(\"value\", tagId))) {\n bag[name] = value;\n sources.set(name, { kind: \"value\", id: tagId });\n }\n }\n // 2. Hand properties, when the ask is a deal.\n if (ask.hand) {\n for (const [name, value] of Object.entries(this.valuesOf(\"hand\", ask.hand.id))) {\n bag[name] = value;\n sources.set(name, { kind: \"hand\", id: ask.hand.id });\n }\n }\n // 3. Chosen tags / criteria, by group name (the tag's gameId as value).\n for (const [name, value] of Object.entries(ask.askNames)) {\n bag[name] = value;\n sources.set(name, { kind: \"criteria\" });\n }\n return { bag, sources, boundTags: ask.boundTags };\n }\n\n // --- the ask (schema 3.1 + 3.2) ------------------------------------------------\n\n /** The claims ledger, derived from THIS flow's board: card id -> holding\n * hands (schema 3.5). Claims are per flow - another flow holding the\n * card is another playthrough, not a rival hand. */\n private claims(): Map<string, number> {\n const counts = new Map<string, number>();\n for (const contents of this.boardContents.values()) {\n for (const id of contents) counts.set(id, (counts.get(id) ?? 0) + 1);\n }\n return counts;\n }\n\n /** @internal - every card id on THIS flow's board, one entry per holding\n * hand. The engine sums these across live flows for the shared ledger. */\n heldCardIds(): string[] {\n return [...this.boardContents.values()].flat();\n }\n\n\n\n /** The claims step (schema 3.1 step 6) for one card, as a verdict or null\n * for \"available\". Two caps apply to a shared card and they are different\n * statements, so they get different verdicts: `copies` is your own board\n * filling up, `sharedCopies` is somebody else already holding it, and a\n * participant told \"claimed\" about a card sitting on another person's\n * table would read it as an engine fault (design/shared-scarcity 9.3.1).\n *\n * `mine` counts this flow's holdings, `world` every live flow's, and `own`\n * what the hand being dealt holds already: those are the hand's to keep or\n * give up as it re-ranks, so they claim nothing against it. */\n private claimVerdict(\n card: Card<Expression>,\n shared: boolean,\n mine: Map<string, number>,\n world: Map<string, number>,\n own = 0,\n ): \"claimed\" | \"claimed-elsewhere\" | null {\n const id = card.id;\n if ((mine.get(id) ?? 0) - own >= acrossLimit(card)) return \"claimed\";\n if (shared && (world.get(id) ?? 0) - own >= sharedCap(card)) return \"claimed-elsewhere\";\n return null;\n }\n\n /** Tag matching (schema 3.1 step 3): for every bound group the card lists\n * the bound tag or omits the group (wildcard); the home group inverts -\n * a homed card requires a matching home binding (schema 2.4). */\n private tagsMatch(card: Card<Expression>, boundTags: Map<string, string>): boolean {\n const home = card.tags?.[PLACE_GROUP];\n if (home !== undefined && home.length > 0) {\n const bound = boundTags.get(PLACE_GROUP);\n if (bound === undefined || !home.includes(bound)) return false;\n }\n for (const [groupId, tagId] of boundTags) {\n if (groupId === PLACE_GROUP) continue;\n const tags = card.tags?.[groupId];\n if (tags === undefined) {\n // Omission is a wildcard unless the group says otherwise. A required\n // group inverts it, which is what `place` has always done per card.\n if (this.internals.requiredGroups.has(groupId)) return false;\n continue;\n }\n if (!tags.includes(tagId)) return false;\n }\n return true;\n }\n\n /** Run one ask: availability filter then ranking. `claimed` decides the\n * claims step (step 6) per card, returning the verdict that refused it or\n * null for available. `trace` (when a subscriber exists) collects the\n * per-card verdicts. */\n private runAsk(\n ask: AskDescriptor,\n claimed: (card: Card<Expression>, shared: boolean) => \"claimed\" | \"claimed-elsewhere\" | null,\n trace?: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[],\n /** The cards the hand being dealt holds: they win ties, so a re-ranking\n * deal never swaps one for an equal. */\n held?: ReadonlyMap<string, number>,\n ): { ordered: CardEntry[]; handEnv: HandEnv } {\n const { box } = ask;\n const handEnv = this.buildHandEnv(ask);\n // Identity on the trace is by gameId (4.4), so the helper takes the CARD\n // rather than an id: every call site had one in hand, and taking the id\n // was the whole of how the two vocabularies got mixed.\n const verdict = (card: Card<Expression>, v: TraceVerdict): void => {\n trace?.push({ id: effectiveGameId(card), verdict: v });\n };\n\n // The hand's condition: ask-constant, evaluated once (schema 3.1 step 4).\n if (!this.passes(ask.condition, this.evalCtx(box, undefined, handEnv), () => `hand ${ask.hand ? effectiveGameId(ask.hand) : \"\"}`)) {\n return { ordered: [], handEnv };\n }\n\n // Deck gates: evaluated once per ask, in deck (id) order (schema 2.5).\n const gateOk = new Map<string, boolean>();\n for (const deck of box.decks) {\n gateOk.set(deck.id, this.passes(deck.condition, this.evalCtx(box, deck, handEnv), () => `deck ${deck.gameId}`));\n }\n\n const turn = this.turnCounts.get(box.id) ?? 0;\n const scored: { entry: CardEntry; priority: number; spec: number }[] = [];\n for (const deck of box.decks) {\n // ONE context per deck, not per card. It is built from box, deck and\n // handEnv, none of which vary inside this loop, and a condition is a\n // read-only gate (schema 3.1), so nothing can write through it and make\n // sharing visible. Rebuilding it per card cost an EvalContext, a scopes\n // map and five bag wrappers each time: about half the garbage a peek over\n // a large box produced (port-review-2026-08.md, measured).\n const deckCtx = this.evalCtx(box, deck, handEnv);\n const deckShared = deck.shared ?? false;\n for (const card of deck.cards) {\n const shared = cardIsShared(card, deckShared);\n if (!gateOk.get(deck.id)) {\n verdict(card, \"deck-gate\");\n continue;\n }\n // Taken out of the world by somebody's shared one-shot. Checked\n // before the flow's own clock, because \"cooldown\" would point the\n // reader at a turn counter that has nothing to do with it.\n if (shared && this.engine.isTaken(card.id)) {\n verdict(card, \"taken\");\n continue;\n }\n if ((this.cooldowns[card.id] ?? 0) > turn) {\n verdict(card, \"cooldown\");\n continue;\n }\n if (!this.tagsMatch(card, handEnv.boundTags)) {\n verdict(card, \"tags\");\n continue;\n }\n // The label is only read when an eval THROWS and only when tracing, so\n // building it per card was pure waste on the path that matters. Built\n // when tracing is on, where the cost is already accepted.\n if (card.condition && !this.passes(card.condition, deckCtx, () => `card ${card.gameId}`)) {\n verdict(card, \"condition\");\n continue;\n }\n const refused = claimed(card, shared); // claims, last (schema 3.1 step 6)\n if (refused) {\n verdict(card, refused);\n continue;\n }\n\n let priority: number;\n if (typeof card.priority === \"number\") {\n priority = card.priority;\n } else {\n try {\n const v = this.eval(card.priority, deckCtx);\n // NaN is a number to `typeof` and no rank to a sort (ruling G):\n // Inf - Inf, an overflow or a host value gets here.\n if (typeof v !== \"number\" || Number.isNaN(v)) {\n verdict(card, \"priority\");\n continue;\n }\n priority = v;\n } catch (e) {\n if (this.tracing) {\n this.emit({ type: \"diagnostic\", where: `card ${card.gameId} priority`, message: e instanceof Error ? e.message : String(e) });\n }\n verdict(card, \"priority\");\n continue;\n }\n }\n let spec = 0;\n if (box.ranking.specificity && card.condition) {\n const node = this.node(card.condition);\n spec = matchedSpecificity(node, (n) => {\n try {\n return conditionPasses(evaluate(n, deckCtx, storyletsDialect));\n } catch {\n return false;\n }\n });\n }\n scored.push({ entry: { card, deck, box }, priority, spec });\n }\n }\n\n scored.sort((a, b) => b.priority - a.priority || b.spec - a.spec); // stable\n // Seeded shuffle of each maximal tie run; runs of 1 consume no draws.\n let i = 0;\n while (i < scored.length) {\n let j = i + 1;\n while (j < scored.length\n && scored[j]!.priority === scored[i]!.priority\n && scored[j]!.spec === scored[i]!.spec) j++;\n if (j - i > 1) {\n // Held cards first, in the order they were scored; then the rest,\n // shuffled, or drawn by weight when their weights differ. Held cards\n // take no draws, so a deal draws what it always drew.\n const run = orderRun(scored.slice(i, j), held, this.prng);\n // Written back element by element, as all three ports do. It used to be\n // `scored.splice(i, j - i, ...run)`, which SPREADS one argument per\n // element: with every card at the default priority 0 the tie run is the\n // whole list, and a box of 150 000 such cards threw `RangeError:\n // Maximum call stack size exceeded` rather than dealing. Far-fetched\n // for one box, but it was a crash where the ports had no limit, and the\n // reference is supposed to be the thing they are transliterated from.\n for (let k = 0; k < run.length; k++) scored[i + k] = run[k]!;\n }\n i = j;\n }\n for (const s of scored) {\n trace?.push({ id: effectiveGameId(s.entry.card), verdict: \"dealt\", priority: s.priority, specificity: s.spec });\n }\n return { ordered: scored.map((s) => s.entry), handEnv };\n }\n\n /** Flip eligible-but-not-taken trace entries to \"capped\". `taken` is keyed\n * by GAMEID, as the trace rows are (4.4): the two must move together or\n * every dealt card silently reads as capped. */\n private capTrace(\n trace: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[],\n taken: ReadonlySet<string>,\n ): void {\n for (const entry of trace) {\n if (entry.verdict === \"dealt\" && !taken.has(entry.id)) entry.verdict = \"capped\";\n }\n }\n\n private view(entry: CardEntry): DealtCard {\n const { card } = entry;\n return {\n id: card.id,\n gameId: effectiveGameId(card),\n ...(card.title !== undefined ? { title: card.title } : {}),\n ...(card.purpose !== undefined ? { purpose: card.purpose } : {}),\n // Fields are copied: the game may change what it is handed, and the\n // bundle's own maps are every later deal's, in every engine on it (the\n // hands-on review, 2026-10, foundation 6). Extension values are NOT: they\n // are the extension runtime's data, read-only by contract, and can be\n // large, so a copy per deal would charge every game for a mistake good\n // readers don't make (a family ruling, 2026-10-28, as Patter's).\n ...(card.fields !== undefined ? { fields: structuredClone(card.fields) } : {}),\n ...(card.extensions !== undefined ? { extensions: card.extensions } : {}),\n };\n }\n\n private handCapacity(hand: Hand<Expression>): number {\n if (hand.slots !== undefined) return hand.slots;\n const declared = hand.template !== undefined\n ? this.internals.templatesById.get(hand.template)?.slots\n : hand.rule?.slots;\n return declared === undefined || declared === \"unbounded\" ? Infinity : declared;\n }\n\n private resolveHand(ref: string): { hand: Hand<Expression>; box: Box<Expression> } {\n const found = this.internals.handsByGameId.get(ref) ?? this.internals.handsById.get(ref);\n if (!found) throw new StoryletError(`unknown hand \"${ref}\"`);\n return found;\n }\n\n // --- host surface (schema 5) ---------------------------------------------------\n\n /** Look at the top of the stock through raw tag criteria (schema 3.1):\n * claims respected, nothing registered, nothing left behind but the\n * trace line. You can never play a card you only peeked. */\n peek(boxRef: string, criteria: Record<string, string> = {}, n?: number): RankedList {\n this.assertOpen();\n const box = this.resolveBox(boxRef);\n const ask = this.askForPeek(box, criteria);\n const claimCounts = this.claims();\n // Skipped outright when the bundle shares nothing, which is most bundles:\n // the ledger walks every live flow's whole board, and an empty map answers\n // every question the same way a computed one would.\n const worldClaims = this.internals.hasShared ? this.engine.sharedClaims() : new Map<string, number>();\n const trace = this.tracing ? [] : undefined;\n // A peek consumes NO draws (ruling A, 2026-10-06): its tie shuffle and any\n // random() in a condition run on a throwaway COPY of the flow's generator,\n // so looking at the stock any number of times leaves the next deal exactly\n // as it was, and the peek still answers as a deal would now. The host's\n // closures read `this.prng` live, which is what the swap relies on.\n const own = this.prng;\n this.prng = makePrng(own.state());\n let ordered: CardEntry[];\n try {\n ({ ordered } = this.runAsk(ask, (card, shared) => this.claimVerdict(card, shared, claimCounts, worldClaims), trace));\n } finally {\n this.prng = own;\n }\n // CLAMPED. `slice(0, -1)` drops the last card and returns the rest, so a\n // negative n answered with almost the whole list here while every port\n // returned nothing (2026-08-29).\n const listed = n === undefined ? ordered : ordered.slice(0, Math.max(n, 0));\n if (trace) {\n this.capTrace(trace, new Set(listed.map((e) => effectiveGameId(e.card))));\n this.emit({ type: \"peek\", box: effectiveGameId(box), criteria, cards: trace }, this.turnCounts.get(box.id) ?? 0);\n }\n return { box: effectiveGameId(box), cards: listed.map((e) => this.view(e)) };\n }\n\n /** Refresh one hand (schema 3.5); returns its new shape. */\n deal(handRef: string): DealtCard[] {\n this.assertOpen();\n const { hand } = this.resolveHand(handRef);\n return this.dealMany([handRef])[effectiveGameId(hand)] ?? [];\n }\n\n /** Drop the cards a hand holds that are no longer available to it, as a deal\n * does before it fills, and say which went and why: the deal's first pass,\n * and the load's check of what it put back (`dropUnqualified`). */\n private evictUnavailable(hand: Hand<Expression>, box: Box<Expression>): { card: string; reason: CheckReason }[] {\n const ask = this.askForHand(hand, box);\n const handEnv = this.buildHandEnv(ask);\n const conditionOk = this.passes(ask.condition, this.evalCtx(box, undefined, handEnv), () => `hand ${effectiveGameId(hand)}`);\n // ONE context per deck, as the fill builds them (see runAsk): the gate\n // and every surviving card of that deck are evaluated against it.\n const gateOk = new Map<string, boolean>();\n const deckCtx = new Map<string, EvalContext>();\n for (const deck of box.decks) {\n const ctx = this.evalCtx(box, deck, handEnv);\n deckCtx.set(deck.id, ctx);\n gateOk.set(deck.id, this.passes(deck.condition, ctx, () => `deck ${deck.gameId}`));\n }\n const turn = this.turnCounts.get(box.id) ?? 0;\n const evicted: { card: string; reason: CheckReason }[] = [];\n // The card is named by gameId (4.4). A card the build no longer has -\n // the `vanished` branch below - has no gameId left, so it is named by\n // the id the board carries, which is the rule a load report has always\n // used for the same reason.\n const evict = (cardId: string, reason: CheckReason): false => {\n const known = this.internals.cardsById.get(cardId);\n evicted.push({ card: known ? effectiveGameId(known.card) : cardId, reason });\n return false;\n };\n const survivors = (this.boardContents.get(hand.id) ?? []).filter((cardId) => {\n if (!conditionOk) return evict(cardId, \"hand-condition\");\n const entry = this.internals.cardsById.get(cardId);\n if (!entry) return evict(cardId, \"vanished\"); // edited content: dropped\n // Its deck is in ANOTHER box now (a load, resume or hot swap into an\n // edited build): gone from this hand's box, as a deleted card is\n // (ruling D). It was evicted as \"deck-gate\" before, by a missing map key.\n if (entry.box.id !== box.id) return evict(cardId, \"vanished\");\n if (!gateOk.get(entry.deck.id)) return evict(cardId, \"deck-gate\");\n // Taken out of the world by somebody's shared one-shot (ruling C):\n // checked where the fill checks it, after the gate and before the\n // flow's own clock, so a card held by one playthrough goes from its\n // hand once another has spent it, as a cooldown would evict it.\n if (cardIsShared(entry.card, entry.deck.shared ?? false) && this.engine.isTaken(cardId)) {\n return evict(cardId, \"taken\");\n }\n if ((this.cooldowns[cardId] ?? 0) > turn) return evict(cardId, \"cooldown\");\n if (!this.tagsMatch(entry.card, handEnv.boundTags)) return evict(cardId, \"tags\");\n // The label is read only when an eval throws AND tracing is on.\n if (!this.passes(entry.card.condition, deckCtx.get(entry.deck.id)!, () => `card ${entry.card.gameId}`)) {\n return evict(cardId, \"condition\");\n }\n return true;\n });\n this.boardContents.set(hand.id, survivors);\n return evicted;\n }\n\n /** @internal - after a load puts this flow's board back, drop every held\n * card that no longer qualifies, as a deal would (the hands-on review,\n * 2026-10, foundation 7: a save from before a content edit could put back a\n * card whose condition is now false, and nothing said so). Every hand, in\n * id order, on a copy of the flow's generator, so the check draws nothing\n * and the next deal is exactly what it would have been. */\n dropUnqualified(): { hand: string; card: string; reason: CheckReason }[] {\n const own = this.prng;\n this.prng = makePrng(own.state());\n try {\n const out: { hand: string; card: string; reason: CheckReason }[] = [];\n for (const handId of [...this.internals.handsById.keys()].sort()) {\n const { hand, box } = this.internals.handsById.get(handId)!;\n for (const e of this.evictUnavailable(hand, box)) out.push({ hand: effectiveGameId(hand), ...e });\n }\n return out;\n } finally {\n this.prng = own;\n }\n }\n\n /** Re-deal several / all hands (schema 3.5): seeded hand-order shuffle\n * (fairness), evict, seed the ledger from survivors, fill in order.\n * Returns the dealt slice - the new contents of exactly the hands this\n * call dealt, keyed by hand gameId (board() stays the whole-board read).\n *\n * The eviction pass evaluates each hand's condition and deck gates, and the\n * fill evaluates them again, asking for the hand twice. That is the draw\n * count of a deal and the corpus pins it: merging the two would move every\n * random() in a gate and the count of diagnostics (engine review 2026-10,\n * section 4). */\n dealMany(handRefs?: string[]): Record<string, DealtCard[]> {\n this.assertOpen();\n const dealt = (handRefs ?? [...this.internals.handsById.keys()].sort())\n .map((ref) => this.resolveHand(ref));\n shuffleInPlace(dealt, this.prng);\n\n // Eviction first: drop dealt cards no longer available to their hand\n // (minus the claims check against their own seat). Trace events fire after\n // the state they report has landed (a handler reading the board sees the\n // eviction).\n for (const { hand, box } of dealt) {\n const evicted = this.evictUnavailable(hand, box);\n if (this.tracing) {\n const turn = this.turnCounts.get(box.id) ?? 0;\n for (const e of evicted) this.emit({ type: \"evict\", hand: effectiveGameId(hand), card: e.card, reason: e.reason }, turn);\n }\n }\n\n const claimCounts = this.claims();\n // The world ledger is taken once for the whole batch and kept in step with\n // the local one below, so two hands in the SAME deal cannot both take the\n // last shared copy. Skipped outright when the bundle shares nothing (most\n // bundles): an empty map answers every question the same way.\n const worldClaims = this.internals.hasShared ? this.engine.sharedClaims() : new Map<string, number>();\n for (const { hand, box } of dealt) {\n // A deal makes the hand right (the hands-on review, 2026-10, ruling 1):\n // it ranks everything eligible, the cards it holds included, and the hand\n // becomes the top of that ranking, full or not. A held card wins a tie,\n // so an equal card never displaces it; one a better card displaces goes\n // back to the stock, \"outranked\".\n const held = this.boardContents.get(hand.id) ?? [];\n const own = new Map<string, number>();\n for (const id of held) own.set(id, (own.get(id) ?? 0) + 1);\n const ask = this.askForHand(hand, box);\n const trace = this.tracing ? [] : undefined;\n // At most `inOneHand` times in this hand; in one hand of this flow at a\n // time unless the card says any (or, from an older bundle, `copies`);\n // at most `sharedCopies` hands anywhere (schema 3.5, shared-scarcity 5).\n const { ordered } = this.runAsk(ask,\n (card, shared) => this.claimVerdict(card, shared, claimCounts, worldClaims, own.get(card.id) ?? 0), trace, own);\n // The top of the ranking, each card as many times as one hand may hold it.\n const want = new Map<string, number>();\n let room = this.handCapacity(hand);\n for (const e of ordered) {\n if (room <= 0) break;\n const n = Math.min(inOneHandOf(e.card), room);\n want.set(e.card.id, n);\n room -= n;\n }\n // Held cards keep their places; newcomers follow, in rank order.\n const next: string[] = [];\n const outranked: string[] = [];\n for (const id of held) {\n const n = want.get(id) ?? 0;\n if (n > 0) { next.push(id); want.set(id, n - 1); } else outranked.push(id);\n }\n for (const e of ordered) {\n for (let n = want.get(e.card.id) ?? 0; n > 0; n--) next.push(e.card.id);\n want.delete(e.card.id);\n }\n this.boardContents.set(hand.id, next);\n for (const id of held) {\n claimCounts.set(id, (claimCounts.get(id) ?? 1) - 1);\n worldClaims.set(id, (worldClaims.get(id) ?? 1) - 1);\n }\n for (const id of next) {\n claimCounts.set(id, (claimCounts.get(id) ?? 0) + 1);\n worldClaims.set(id, (worldClaims.get(id) ?? 0) + 1);\n }\n // Emitted after the hand is set: a handler reading board() sees the deal.\n if (trace) {\n const turn = this.turnCounts.get(box.id) ?? 0;\n for (const id of outranked) {\n this.emit({ type: \"evict\", hand: effectiveGameId(hand), card: effectiveGameId(this.internals.cardsById.get(id)!.card), reason: \"outranked\" }, turn);\n }\n this.capTrace(trace, new Set(next.map((id) => effectiveGameId(this.internals.cardsById.get(id)!.card))));\n this.emit({ type: \"deal\", hand: effectiveGameId(hand), cards: trace }, turn);\n }\n }\n\n return Object.fromEntries(dealt.map(({ hand }) => [\n effectiveGameId(hand),\n (this.boardContents.get(hand.id) ?? []).map((id) => this.view(this.internals.cardsById.get(id)!)),\n ]));\n }\n\n /** The board: current hand contents, in dealt order, keyed by hand gameId\n * (schema 5). Read it for what is out; peek the stock for what could\n * come.\n *\n * `boxRef` (a box gameId or id) narrows the read to that box's hands, in\n * the same shape and the same order: \"give me the barks hands\" is a\n * common host query, and boxes are how a game separates its storylet\n * systems, so the grouping belongs here rather than in every host. An\n * unknown box throws, as it does on turn() and peek(). */\n board(boxRef?: string): Record<string, DealtCard[]> {\n this.assertOpen();\n const keep = boxRef !== undefined ? this.resolveBox(boxRef).id : undefined;\n return Object.fromEntries([...this.boardContents.entries()]\n .filter(([handId]) => keep === undefined || this.internals.handsById.get(handId)!.box.id === keep)\n .map(([handId, ids]) => [\n effectiveGameId(this.internals.handsById.get(handId)!.hand),\n ids.map((id) => this.view(this.internals.cardsById.get(id)!)),\n ]));\n }\n\n /** Resolve a played/inspected card within a hand on the board. */\n private resolveDealt(cardId: string, handRef: string): { entry: CardEntry; ask: AskDescriptor } {\n const entry = this.internals.cardsById.get(cardId) ?? this.internals.cardsByGameId.get(cardId);\n if (!entry) throw new StoryletError(`unknown card \"${cardId}\"`);\n const { hand, box } = this.resolveHand(handRef);\n if (!(this.boardContents.get(hand.id) ?? []).includes(entry.card.id)) {\n throw new StoryletError(`card \"${effectiveGameId(entry.card)}\" is not dealt to hand \"${effectiveGameId(hand)}\"`);\n }\n return { entry, ask: this.askForHand(hand, box) };\n }\n\n /** Outcome availability, evaluated against CURRENT state on every ask\n * (schema 3.1/5) - never a deal-time snapshot. */\n outcomes(cardId: string, from: string): OutcomeView[] {\n this.assertOpen();\n const { entry, ask } = this.resolveDealt(cardId, from);\n const ctx = this.evalCtx(entry.box, entry.deck, this.buildHandEnv(ask));\n return entry.card.outcomes.map((o) => ({\n id: o.id,\n gameId: effectiveGameId(o),\n ...(o.title !== undefined ? { title: o.title } : {}),\n ...(o.purpose !== undefined ? { purpose: o.purpose } : {}),\n // Fields copied, as a dealt card's (`view`).\n ...(o.fields !== undefined ? { fields: structuredClone(o.fields) } : {}),\n ...(o.extensions !== undefined ? { extensions: o.extensions } : {}), // read-only, as a dealt card's\n available: this.passes(o.condition, ctx, () => `outcome ${effectiveGameId(entry.card)}/${effectiveGameId(o)}`),\n }));\n }\n\n /** Apply an outcome (schema 3.7): the card must sit in a hand on the\n * board (you never play a card from inside the deck). All-or-nothing:\n * throws before any mutation on a gated-shut outcome or a refused write\n * target, and a refusal changes nothing at all.\n *\n * A card with NO outcomes is played with none, named as \"\" (the\n * no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,\n * whose play means \"shown\". It is everything a play is except the writes:\n * the play log and the history functions count it, the box's turn moves by\n * the usual rule, the redraw rests it and it leaves its hand. \"\" is the one\n * spelling in all four runtimes, because a Blueprint pin cannot be absent.\n * Only the empty-for-empty case is new: \"\" on a card that has outcomes is\n * refused, and a named outcome on a card with none is refused as before. */\n play(cardId: string, outcomeGameId: string, from: string, opts: PlayOptions = {}): void {\n this.assertOpen();\n const { entry, ask } = this.resolveDealt(cardId, from);\n const bare = outcomeGameId === \"\";\n if (bare && entry.card.outcomes.length > 0) {\n throw new StoryletError(`card \"${effectiveGameId(entry.card)}\" has outcomes (${entry.card.outcomes.map((o) => effectiveGameId(o)).join(\", \")}); name the one played`);\n }\n const outcome = bare ? undefined : entry.card.outcomes.find((o) => effectiveGameId(o) === outcomeGameId);\n if (!bare && !outcome) throw new StoryletError(`card \"${effectiveGameId(entry.card)}\" has no outcome \"${outcomeGameId}\"`);\n\n const handEnv = this.buildHandEnv(ask);\n const ctx = this.evalCtx(entry.box, entry.deck, handEnv);\n if (outcome && !this.passes(outcome.condition, ctx, () => `outcome ${effectiveGameId(entry.card)}/${outcomeGameId}`)) {\n throw new StoryletError(`outcome \"${outcomeGameId}\" on \"${effectiveGameId(entry.card)}\" is gated shut`);\n }\n\n // The played card's box's clock advances (schema 3.4); computed up\n // front so the play and its writes log as one action, one turn stamp.\n //\n // A TIMED box (design/engine-server.md 4.8) defaults to advancing\n // NOTHING: its clock is time, the host ticks it, and a play is not a\n // tick. A call that names `advanceTurns` still gets what it asked for,\n // in either kind of box, because the call says otherwise. This is the\n // whole of `turn`'s effect on the engine: the seconds are never read.\n const perPlay = entry.box.turn !== undefined ? 0 : this.internals.bundle.settings.playAdvancesTurns;\n const newTurn = (this.turnCounts.get(entry.box.id) ?? 0) + (opts.advanceTurns ?? perPlay);\n\n // A play is ALL-OR-NOTHING (ruling B, 2026-10-06). Every target is\n // resolved and checked first, so a refusal the engine can know in advance\n // lands nothing and emits nothing; then every right-hand side evaluates\n // against PRE-play state (schema 3.7); then the writes land. A refusal\n // only the landing can meet puts back what the earlier writes replaced.\n // The write events fire once all of them have landed.\n const changes = Object.entries(outcome?.changes ?? {})\n .map(([target, expr]) => ({ target, expr, plan: this.planWrite(target, entry, handEnv) }));\n const writes = changes.map((c) => ({ ...c, value: this.eval(c.expr, ctx) }));\n const landed: { target: string; plan: WritePlan; value: ScalarValue; prev: ScalarValue | undefined }[] = [];\n try {\n for (const w of writes) landed.push({ target: w.target, plan: w.plan, value: w.value, prev: this.landWrite(w.plan, w.value) });\n } catch (e) {\n for (const l of [...landed].reverse()) this.undoWrite(l.plan, l.prev);\n throw e;\n }\n if (this.tracing) {\n for (const { target, plan, value, prev } of landed) {\n this.emit({ type: \"write\", target, path: plan.path, value, ...(prev !== undefined ? { prev } : {}) }, newTurn);\n }\n }\n\n const outcomeId = outcome ? effectiveGameId(outcome) : \"\";\n const record: PlayRecord = { card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn };\n this.playLog.push(record);\n this.indexPlay(record);\n if (entry.card.redraw === \"never\") {\n // A shared one-shot leaves the world rather than this flow: the engine\n // holds it, so every flow is refused. A finite redraw deliberately does\n // NOT share, whatever the deck says - a cooldown is an absolute turn of\n // this flow's box clock, and there is no shared clock to compare it\n // against (design/shared-scarcity.md 9.3.2).\n if (cardIsShared(entry.card, entry.deck.shared ?? false)) this.engine.markTaken(entry.card.id);\n else this.cooldowns[entry.card.id] = Number.MAX_SAFE_INTEGER;\n } else if (typeof entry.card.redraw === \"number\") {\n this.cooldowns[entry.card.id] = newTurn + entry.card.redraw;\n }\n // The card leaves its hand, releasing its claim (schema 3.5/3.7).\n const handId = ask.hand!.id;\n // One copy, for a card the hand holds more than once (`inOneHand`).\n const contents = this.boardContents.get(handId) ?? [];\n const at = contents.indexOf(entry.card.id);\n this.boardContents.set(handId, [...contents.slice(0, at), ...contents.slice(at + 1)]);\n this.turnCounts.set(entry.box.id, newTurn);\n // Emitted last: a handler reading the board and the clock sees the play.\n if (this.tracing) this.emit({ type: \"play\", card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn }, newTurn);\n }\n\n /**\n * Take a card out of a hand without playing it: the player turned it down,\n * or the game wants the room (the hands-on review, 2026-10, ruling: discard).\n * No outcome, no play in the log, no move of the clock, no play count. It\n * starts its Redraw as a play would, so a re-ranking deal doesn't hand it\n * straight back: a finite Redraw waits that many turns from now, a \"never\"\n * card is gone for good (for everyone, when it's shared), and an \"always\"\n * card can come up at the next deal. One copy leaves, for a card the hand\n * holds more than once.\n */\n discard(cardId: string, from: string): void {\n this.assertOpen();\n const { entry, ask } = this.resolveDealt(cardId, from);\n const turn = this.turnCounts.get(entry.box.id) ?? 0;\n if (entry.card.redraw === \"never\") {\n if (cardIsShared(entry.card, entry.deck.shared ?? false)) this.engine.markTaken(entry.card.id);\n else this.cooldowns[entry.card.id] = Number.MAX_SAFE_INTEGER;\n } else if (typeof entry.card.redraw === \"number\") {\n this.cooldowns[entry.card.id] = turn + entry.card.redraw;\n }\n const handId = ask.hand!.id;\n const contents = this.boardContents.get(handId) ?? [];\n const at = contents.indexOf(entry.card.id);\n this.boardContents.set(handId, [...contents.slice(0, at), ...contents.slice(at + 1)]);\n if (this.tracing) this.emit({ type: \"discard\", hand: effectiveGameId(ask.hand!), card: effectiveGameId(entry.card), turn }, turn);\n }\n\n /** One owned property's address, owner segment and all (4.4). */\n private address(kind: OwnedScope, id: string): string {\n return addressOf(this.internals, kind, id);\n }\n\n /** Find the bag that declares one change's name: the flow's bag when the\n * property is per-flow, the shared bag when it is shared - the\n * union/partition invariant made executable. Read by OWN value, so a name\n * Object.prototype carries is no property. Throws, landing nothing, when\n * no bag declares it or the declaration is `writable: false`. */\n private planBag(kind: Exclude<PartitionKind, \"story\"> | \"story\", id: string | undefined, name: string, path: string): WritePlan {\n const own = kind === \"story\" ? this.stores.story : id !== undefined ? this.stores[kind].get(id) : undefined;\n const shared = kind === \"story\" ? this.internals.shared.story : id !== undefined ? this.internals.shared[kind].get(id) : undefined;\n const bag = ownValue(own, name) !== undefined ? own\n : ownValue(shared, name) !== undefined ? shared\n : undefined;\n if (bag === undefined) throw new StoryletError(`no property at \"${path}\"`);\n // The kernel would refuse this at `set`, with this text; asked here so the\n // refusal comes before any of the play's writes has landed.\n if (bag.declarations().some((d) => d.name === name && d.writable === false)) {\n throw new StoryletError(`'${name}' is read-only`);\n }\n return { kind: \"bag\", bag, name, path };\n }\n\n /** Resolve and check one change target, writing nothing: every refusal a\n * write can meet that the engine can know in advance is met HERE, so a\n * play checks all its targets before any write lands (ruling B). */\n private planWrite(target: string, entry: CardEntry, handEnv: HandEnv): WritePlan {\n const match = /^@([a-z][a-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_-]*)$/.exec(target);\n if (!match) throw new StoryletError(`bad change target \"${target}\"`);\n const [, scope, name] = match as unknown as [string, string, string];\n const host = this.internals.hostScopes.get(scope);\n if (host !== undefined) {\n if (!host.set) throw new StoryletError(`@${scope}.${name} cannot be written: the host bound @${scope} read-only`);\n // The story's own promise, kept HERE and only here, because here is where\n // the story does the writing: it is consulted before the seam, so an\n // outcome never reaches the bag (which would let a host write past it)\n // nor a bound resolver (which cannot tell the two apart). The host's\n // setProperty is its own path and never asks. Patter's runtime refuses the\n // same write through the shared kernel, so both read one way.\n if (hostReadOnly(host.spec, name)) throw new StoryletError(`'@${scope}.${name}' is read-only (writable: false)`);\n return { kind: \"host\", host, name, path: `${scope}.${name}` };\n }\n switch (scope) {\n case \"story\": return this.planBag(\"story\", undefined, name, `story.${name}`);\n case \"box\": return this.planBag(\"box\", entry.box.id, name, `${this.address(\"box\", entry.box.id)}.${name}`);\n case \"deck\": return this.planBag(\"deck\", entry.deck.id, name, `${this.address(\"deck\", entry.deck.id)}.${name}`);\n case \"hand\": {\n // Write-back routing (schema 3.6): the composed name remembers its\n // source store; writes to criteria/chosen-tag names are errors.\n const source = handEnv.sources.get(name);\n if (!source) throw new StoryletError(`@hand.${name} is not composed in this ask`);\n if (source.kind === \"criteria\") throw new StoryletError(`@hand.${name} is a chosen tag / criteria name and cannot be written`);\n return this.planBag(source.kind, source.id, name, `${this.address(source.kind, source.id)}.${name}`);\n }\n default: {\n // Another engine's game-wide scope (`@patter.x`): the family's shared\n // vocabulary lets a card write it, and the registry keeps that engine's\n // rules (a read-only property is refused when the write lands, and the\n // play then puts back what it had written). A story write, so no host flag.\n if (this.internals.registry.has(scope)) return { kind: \"scope\", scope, name, path: `${scope}.${name}` };\n if (this.internals.bundle.externalScopes?.includes(scope)) {\n throw new StoryletError(`@${scope}.${name} cannot be written: no engine on this registry registered @${scope}`);\n }\n throw new StoryletError(`bad change target scope \"@${scope}\"`);\n }\n }\n }\n\n /** Land one planned change; returns the value it replaced (for the log's\n * \"0 -> 1\" reading). An engine write: the bag's subscribers fire (the\n * firing rule). */\n private landWrite(plan: WritePlan, value: ScalarValue): ScalarValue | undefined {\n switch (plan.kind) {\n case \"host\": {\n const prev = plan.host.resolver.get(plan.name);\n plan.host.set!(plan.name, value);\n return prev;\n }\n case \"bag\": return plan.bag.set(plan.name, value).prev;\n case \"scope\": {\n const prev = this.internals.registry.get(plan.scope, plan.name);\n this.internals.registry.set(plan.scope, plan.name, value);\n return prev;\n }\n }\n }\n\n /** Put back what one landed change replaced, when a LATER change in the same\n * play was refused as it landed (a refusal no plan could see coming: another\n * engine's read-only property, a host resolver that throws). Silent and as\n * the host, since this is the engine undoing itself, not the story writing. */\n private undoWrite(plan: WritePlan, prev: ScalarValue | undefined): void {\n if (prev === undefined) return;\n switch (plan.kind) {\n case \"host\": plan.host.set!(plan.name, prev, true); return;\n case \"bag\": plan.bag.set(plan.name, prev, { silent: true, reason: \"play refused\", host: true }); return;\n case \"scope\": this.internals.registry.set(plan.scope, plan.name, prev, { host: true }); return;\n }\n }\n\n /** Advance one box's clock (schema 3.4): a turn is one draw-from-stock\n * session for that box, on THIS flow's clock. */\n advanceTurns(boxRef: string, n = 1): void {\n this.assertOpen();\n const box = this.resolveBox(boxRef);\n const next = (this.turnCounts.get(box.id) ?? 0) + n;\n this.turnCounts.set(box.id, next);\n if (this.tracing) this.emit({ type: \"turns\", box: effectiveGameId(box), turn: next }, next);\n }\n\n // --- state access (host surface + test tooling) ---------------------------------\n\n /** Every box, bundle order: identity + this flow's clock (the enumeration\n * surface examiners key their turns sections on; parity member). */\n listBoxes(): BoxView[] {\n this.assertOpen();\n return this.internals.bundle.boxes.map((b) => ({\n id: b.id,\n gameId: effectiveGameId(b),\n ...(b.title !== undefined ? { title: b.title } : {}),\n turn: this.turnCounts.get(b.id) ?? 0,\n // Read-only and uncopied, as a dealt card's extension values (`view`).\n ...(b.extensions !== undefined ? { extensions: b.extensions } : {}),\n }));\n }\n\n /** THIS flow's kernel bags with their store path prefixes (the state\n * logger's mount surface; parity member). The shared bags are the\n * Engine's listBags; the flows are rebuilt by loadGame, so consumers\n * re-enumerate after a load. */\n listBags(): BagMount[] {\n this.assertOpen();\n const mounts: BagMount[] = [{ prefix: \"story\", bag: this.stores.story }];\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of this.stores[kind]) mounts.push({ prefix: addressOf(this.internals, kind, id), bag });\n }\n return mounts;\n }\n\n /** The flow's FULL merged view as examiner rows (the property examiner /\n * editor surface, parity across all runtimes): the host scopes read\n * through their resolvers, then per scope the shared values and this\n * flow's own. Bundle order: host scopes (@world first when the bundle\n * lists it first), story, then per-box / per-deck / per-hand / per-tag\n * stores. */\n listProperties(): PropertyRow[] {\n this.assertOpen();\n const out: PropertyRow[] = hostRows(this.internals);\n const add = (_prefix: string, shared: StateBag | undefined, own: StateBag | undefined): void => {\n for (const bag of [shared, own]) {\n if (bag === undefined) continue;\n for (const row of bag.rows()) out.push(row);\n }\n };\n add(\"story\", this.internals.shared.story, this.stores.story);\n for (const kind of OWNED_SCOPES) {\n const ids = new Set([...this.internals.shared[kind].keys(), ...this.stores[kind].keys()]);\n for (const id of ids) {\n add(addressOf(this.internals, kind, id), this.internals.shared[kind].get(id), this.stores[kind].get(id));\n }\n }\n return out;\n }\n\n /** Read by path: \"world.x\", \"story.gold\", \"value.docks.danger\",\n * \"box.village.heat\", \"deck.wares.n\", \"hand.the-elder.zone\" - the flow's\n * merged view, routed by the declaration's sharing.\n *\n * The owner segment is the entity's GAMEID, the name it is called by\n * everywhere else (4.4). Its internal id is accepted for this release and\n * earns a `diagnostic` naming the address to move to; the next lockstep\n * release refuses it. */\n getProperty(path: string): ScalarValue {\n this.assertOpen();\n const found = this.resolvePath(propertyPath(path));\n const value = found.kind === \"host\" ? found.host.resolver.get(found.name)\n : found.kind === \"scope\" ? this.internals.registry.get(found.token, found.name)\n : ownValue(found.own, found.name) ?? ownValue(found.shared, found.name);\n if (value === undefined) throw new StoryletError(`no property at \"${path}\"`);\n return value;\n }\n\n setProperty(path: string, value: ScalarValue): void {\n this.assertOpen();\n const found = this.resolvePath(propertyPath(path));\n checkHostWrite(this.internals, path, value);\n if (found.kind === \"host\") {\n if (!found.host.set) throw new StoryletError(`@${found.host.spec.token} is read-only here: the host bound no write`);\n found.host.set(found.name, value, true);\n return;\n }\n if (found.kind === \"scope\") {\n this.internals.registry.set(found.token, found.name, value, { host: true });\n return;\n }\n const bag = ownValue(found.own, found.name) !== undefined ? found.own\n : ownValue(found.shared, found.name) !== undefined ? found.shared\n : undefined;\n if (bag === undefined) throw new StoryletError(`no property at \"${path}\"`);\n // A host write: silent under the firing rule (no subscriber feedback\n // loop), visible to the bag's audit hook, and flagged HOST so a\n // `writable: false` does not refuse the game its own value.\n bag.set(found.name, value, { silent: true, reason: \"host setProperty\", host: true });\n }\n\n private resolvePath(path: string):\n | { kind: \"host\"; host: HostScopeState; name: string }\n | { kind: \"scope\"; token: string; name: string }\n | { kind: \"bag\"; own?: StateBag; shared?: StateBag; name: string } {\n const parts = path.split(\".\");\n const host = parts.length === 2 ? this.internals.hostScopes.get(parts[0]!) : undefined;\n if (host !== undefined) return { kind: \"host\", host, name: parts[1]! };\n if (parts.length === 2 && parts[0] !== \"story\" && this.internals.registry.has(parts[0]!)) {\n return { kind: \"scope\", token: parts[0]!, name: parts[1]! };\n }\n if (parts.length === 2 && parts[0] === \"story\") {\n return { kind: \"bag\", own: this.stores.story, shared: this.internals.shared.story, name: parts[1]! };\n }\n if (parts.length === 3 && isOwnedScope(parts[0])) {\n const kind = parts[0];\n const [, segment, name] = parts as unknown as [string, string, string];\n const owner = ownerOrThrow(this.internals, kind, segment, name);\n if (owner.legacy && this.tracing) {\n this.emit({ type: \"diagnostic\", where: \"property address\", message: legacyAddressMessage(this.internals, kind, segment, name) });\n }\n const own = this.stores[kind].get(owner.id);\n const shared = this.internals.shared[kind].get(owner.id);\n if (own === undefined && shared === undefined) throw new StoryletError(`no ${kind} store \"${segment}\"`);\n return { kind: \"bag\", ...(own !== undefined ? { own } : {}), ...(shared !== undefined ? { shared } : {}), name };\n }\n throw new StoryletError(`bad property path \"${path}\"`);\n }\n\n // --- persistence (schema 4) -------------------------------------------------\n\n /**\n * This flow's DURABLE HALF, the player's pocket (ruling H): every per-flow\n * `durable` property's value by its address, and every per-flow durable\n * one-shot this flow has spent, by card gameId. A later run opens the\n * player's flow with it, `openFlow(id, { durable })`; the shared half is\n * `engine.saveDurable()`.\n */\n saveDurable(): DurableSave {\n this.assertOpen();\n const { bundle, durable } = this.internals;\n return structuredClone({\n schema: DURABLE_SCHEMA,\n content: bundle.content,\n values: durableValues(this.stores, durable.flow),\n spent: durable.flowCards\n .filter((e) => this.cooldowns[e.card.id] === Number.MAX_SAFE_INTEGER)\n .map((e) => effectiveGameId(e.card)).sort(),\n });\n }\n\n /** @internal - write a planned pocket into this freshly opened flow\n * (openFlow's `durable`, which has already checked and planned it). Its\n * bags hold their defaults, so only what the plan carries is written. */\n writeDurable(plan: DurablePlan): void {\n for (const [prop, value] of plan.values) putDurable(durableBag(this.stores, prop), prop.decl.name, value);\n for (const cardId of plan.cards) this.cooldowns[cardId] = Number.MAX_SAFE_INTEGER;\n }\n\n /** @internal - this flow's blob: inside the engine's envelope without its\n * properties (the registry has them), or parked whole by saveFlow.\n * Engine-side plumbing; a host parks a flow with `engine.saveFlow`. Keyed\n * by Game ID (save version 3; see savedCard): the turns by box, the\n * cooldowns by card, the board by hand with its cards, in the order the\n * flow holds them. */\n snapshot(withProps: boolean): FlowSave {\n const internals = this.internals;\n const boxKey = (id: string): string => {\n const box = internals.boxesById.get(id);\n return box !== undefined ? effectiveGameId(box) : id;\n };\n const handKey = (id: string): string => {\n const found = internals.handsById.get(id);\n return found !== undefined ? effectiveGameId(found.hand) : id;\n };\n return {\n ...(withProps ? { props: partitionValues(this.stores) } : {}),\n turns: Object.fromEntries([...this.turnCounts].map(([id, turn]) => [boxKey(id), turn])),\n prng: this.prng.state(),\n cooldowns: Object.fromEntries(Object.entries(this.cooldowns).map(([id, turn]) => [cardKey(internals, id), turn])),\n board: Object.fromEntries([...this.boardContents].map(([id, cards]) => [handKey(id), cards.map((c) => cardKey(internals, c))])),\n playLog: this.playLog,\n };\n }\n\n /** @internal - restore a freshly opened flow from its blob (loadGame, and\n * openFlow's `restore`, both of which clean the blob first). Orphaned keys\n * (deleted entities) drop; new declarations keep defaults. Engine-side\n * plumbing that skips the checks a restore through `openFlow` makes, which\n * is why a host resumes a flow through `openFlow` and never through this. */\n restore(saved: FlowSave): void {\n if (saved.props !== undefined) loadPartition(this.stores, saved.props);\n this.turnCounts = new Map(this.internals.bundle.boxes.map((b) => [b.id, 0]));\n for (const [boxId, turn] of Object.entries(saved.turns ?? {})) {\n if (this.turnCounts.has(boxId)) this.turnCounts.set(boxId, turn);\n }\n this.prng = makePrng(saved.prng);\n this.cooldowns = saved.cooldowns ?? {};\n this.playLog = saved.playLog ?? [];\n this.rebuildPlayIndex();\n this.boardContents = new Map(Object.entries(saved.board ?? {})\n .filter(([handId]) => this.internals.handsById.has(handId))\n .map(([handId, ids]) => [handId, ids.filter((id) => this.internals.cardsById.has(id))]));\n for (const handId of this.internals.handsById.keys()) {\n if (!this.boardContents.has(handId)) this.boardContents.set(handId, []);\n }\n }\n}\n","// ---------------------------------------------------------------------------\n// The Storylet Engine version, and the one check against it.\n//\n// A game, a tool or an extension that ships apart from the engine checks that\n// the Storylet Engine it finds is one it was built for. Unity's and npm's\n// packages can declare a version range, but Unreal's .uplugin and Godot's\n// plugin.cfg can't, and no manifest is readable by game code in a shipped\n// build, so the check that works on every engine runs in code:\n// STORYLET_ENGINE_VERSION says what this runtime is, and versionInRange tests\n// it. The same pair as Patterplay's (PATTERPLAY_VERSION, versionInRange), held\n// to the same corpus cases, so both families answer alike.\n// ---------------------------------------------------------------------------\n\n/** This runtime's version, as in its package manifest. Written by `npm run bump:play`; never edit it by hand. */\nexport const STORYLET_ENGINE_VERSION = \"1.0.1\";\n\ntype Parsed = { core: [string, string, string]; pre: string[] };\n\nconst SEMVER = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/;\n\nfunction parse(v: string): Parsed | null {\n const m = typeof v === \"string\" ? SEMVER.exec(v) : null;\n if (!m) return null;\n return { core: [m[1]!, m[2]!, m[3]!], pre: m[4] ? m[4].split(\".\") : [] };\n}\n\n/** Two runs of digits compared as whole numbers of any length: leading zeros dropped, then the longer is larger. */\nfunction compareDigits(x: string, y: string): number {\n x = x.replace(/^0+(?=\\d)/, \"\");\n y = y.replace(/^0+(?=\\d)/, \"\");\n if (x.length !== y.length) return x.length < y.length ? -1 : 1;\n return x === y ? 0 : x < y ? -1 : 1;\n}\n\n/** Semantic-version precedence: negative when a comes first, positive when b does, 0 when they're equal. */\nfunction compare(a: Parsed, b: Parsed): number {\n for (let i = 0; i < 3; i++) {\n const c = compareDigits(a.core[i]!, b.core[i]!);\n if (c) return c;\n }\n // A prerelease comes before its release.\n if (!a.pre.length || !b.pre.length) return b.pre.length - a.pre.length;\n for (let i = 0; i < Math.min(a.pre.length, b.pre.length); i++) {\n const x = a.pre[i]!, y = b.pre[i]!;\n if (x === y) continue;\n const xn = /^\\d+$/.test(x), yn = /^\\d+$/.test(y);\n if (xn && yn) {\n const c = compareDigits(x, y);\n if (c) return c;\n continue;\n }\n if (xn !== yn) return xn ? -1 : 1; // a numeric identifier comes before a word\n return x < y ? -1 : 1;\n }\n return a.pre.length - b.pre.length;\n}\n\n/**\n * Whether `version` is at least `min` and below `maxExclusive`, by semantic-version precedence (build metadata\n * after `+` is ignored, and a prerelease comes before its release). False when any of the three isn't a version.\n * Something built for the Storylet Engine 0.15 checks `versionInRange(STORYLET_ENGINE_VERSION, \"0.15.0\", \"0.16.0\")`.\n */\nexport function versionInRange(version: string, min: string, maxExclusive: string): boolean {\n const v = parse(version), lo = parse(min), hi = parse(maxExclusive);\n if (!v || !lo || !hi) return false;\n return compare(v, lo) >= 0 && compare(v, hi) < 0;\n}\n","// ---------------------------------------------------------------------------\n// describeBundle - the bundle inspector's runtime half (design/engine-runtimes.md\n// section 2, piece 6).\n//\n// A BUNDLE-level API, deliberately NOT a session method: it answers the\n// integrator's question - \"I dropped a .storyletsc into my project, what may\n// my game code call?\" - from the imported asset alone, with no session, no\n// state and no game running. That makes the boundary rule (design 4) visible:\n// hands are what deal() takes, tag groups + tags are what peek() criteria are\n// drawn from, declared properties are what expressions read and a host may\n// set. Card lists are deliberately absent: cards are the engine's business,\n// counts are the orientation an integrator needs.\n//\n// Everything is in bundle order, so the description is deterministic and two\n// runtimes render the same rows in the same sequence. The property scopes are\n// the static twin of session.listProperties(): the same stores, in the same\n// order, before anything is instantiated (a hand instance carries its\n// template's declarations, exactly as the session's hand bags do).\n// ---------------------------------------------------------------------------\n\nimport { defaultFor } from \"@wildwinter/scoperegistry\";\nimport { bundleHostScopes, effectiveGameId, groupsOfBox, isHoleRef } from \"@storylet-studio/model\";\nimport type {\n Box, Bundle, Expression, Hand, HostScopeDecl, PropertyDecl, PropertyType, ScalarValue,\n} from \"@storylet-studio/model\";\n\n/** What bundle this is: the staleness/identity triple plus the schema tag. */\nexport interface BundleIdentity {\n /** The bundle schema tag (\"storylets/bundle@2\"; \"@1\" from before host\n * scopes, \"@0\" from before the project map). */\n schema: string;\n /** content.project - the project name a save must agree with. */\n project: string;\n /** content.version - the authored bundle version. */\n version: string;\n /** content.hash - hash32 over the canonical source shards (schema 2.8). */\n hash: string;\n /** \"full\" | \"stripped\": whether authoring metadata (titles) survived. */\n metadata: string;\n}\n\n/** One hole this hand fills from a property rather than with a tag: the hand\n * MOVES when that property is written (design/engine-server.md 4.6). `group`\n * is the tag group's gameId, `from` the reference exactly as authored. */\nexport interface MovableHole {\n group: string;\n from: string;\n}\n\n/** One hand: the deal() surface. `gameId` is the name deal() is called with. */\nexport interface HandSummary {\n gameId: string;\n title?: string;\n /** The owning box's gameId (peek's first argument for the same stock). */\n box: string;\n /** The effective slot cap: the hand's override, else its template's or\n * rule's, else \"unbounded\". */\n slots: number | \"unbounded\";\n /** The hand template's gameId; absent for a standalone (inline-rule) hand. */\n template?: string;\n /**\n * The holes filled from a property, in bundle order. Absent when the hand\n * has none, which is the ordinary case.\n *\n * Reported because it is the one thing about a hand an integrator cannot see\n * from its name: a movable hole means writing that property MOVES the hand,\n * so it is the difference between a fixed kiosk and a performer who walks\n * about. `setProperty` is the whole verb; there is no other.\n */\n movable?: MovableHole[];\n}\n\n/** One tag group and its tags, by gameId: the peek() criteria surface (a\n * criteria entry is `{ [group gameId]: tag gameId }`). */\nexport interface TagGroupSummary {\n gameId: string;\n tags: string[];\n}\n\n/** One box: identity, its ranking policy, its tag groups, and counts. */\nexport interface BoxSummary {\n gameId: string;\n title?: string;\n /** The box is on the project map (design/project-map-contract.md 3.7): it\n * may name the map's group in peek criteria beside its own `tagGroups`,\n * which list the box's OWN groups only. Absent is \"not on the map\". */\n usesMap?: true;\n /** The only per-box ranking policy (Reboot 2.2). */\n ranking: { specificity: boolean };\n /** Present on a box whose clock the GAME moves, so a play advances nothing:\n * an integrator reading a bundle needs it to know which boxes their host\n * must tick. With `seconds`, a TIMED box (design/engine-server.md 4.8) and\n * how long one of its turns lasts; without, the game's own beat (a\n * calendar). Absent is the ordinary box, whose clock a play moves. */\n turn?: { seconds?: number };\n /** How many cards in this box are DURABLE (design/engine-server.md 4.2):\n * their `redraw: \"never\"` spend outlives the run, and a server has to lift\n * and restore it. A count rather than a list, like every other number here:\n * an integrator needs to know whether this box has any such cards at all,\n * and which ones is the authoring tool's question. Absent when there are\n * none, which is the ordinary bundle. */\n durableCards?: number;\n tagGroups: TagGroupSummary[];\n counts: {\n decks: number;\n cards: number;\n hands: number;\n templates: number;\n tagGroups: number;\n };\n}\n\n/** One declared property: what expressions read and what a host may set. */\nexport interface PropertySummary {\n name: string;\n type: PropertyType;\n default: ScalarValue;\n /** Enum / flags options, where declared. */\n values?: string[];\n /** Declared DURABLE (design/engine-server.md 4.2): the value survives a run,\n * and a server lifts and restores it across one. The engine never reads it;\n * it is reported because it is the difference between a value an integrator\n * may reset and one somebody is going to expect back. Absent = run-scoped. */\n durable?: true;\n purpose?: string;\n}\n\n/** The scope a declaration block belongs to. `tag` declarations compose into\n * @hand for any ask that binds the tag (schema 3.6). `host` is a host scope\n * other than @world (`@player`), its token the `owner`. */\nexport type PropertyScopeKind = \"world\" | \"host\" | \"story\" | \"box\" | \"deck\" | \"hand\" | \"tag\";\n\n/** One scope's declared properties. `owner` is the owning entity's gameId\n * (empty for world / story), or a host scope's token; `box` names its box;\n * `group` names a tag's group. A zone of the project map is a `tag` scope\n * with `group` and NO `box`: it belongs to none. */\nexport interface PropertyScopeSummary {\n scope: PropertyScopeKind;\n owner: string;\n /** A host scope that declares no names, so takes any (`world` or `host`\n * only). Absent for every other scope. */\n opaque?: true;\n box?: string;\n group?: string;\n properties: PropertySummary[];\n}\n\n/**\n * The project map (design/project-map-contract.md 3.7): its group, which boxes\n * are on it, and how much geometry the bundle carries.\n *\n * Counts rather than the geometry itself, which is the same judgement the rest\n * of this file makes: an inspector answers \"what is in here\", and a host that\n * wants the polygons reads `bundle.map.geometry` directly. The geometry counts\n * are zero when the build did not ask for geometry (`export.map`); the group is\n * there regardless, because hands and cards reference it.\n */\nexport interface MapSummary {\n /** The zone group's gameId: the name an opted-in box's peek criteria use. */\n group: string;\n /** Its tags (the zones), by gameId. */\n tags: string[];\n /** The opted-in boxes, by gameId, in bundle order. */\n boxes: string[];\n /** Drawn zones in the carried geometry. */\n zones: number;\n backgrounds: number;\n /** Box gameId -> placed hands standing on the map (design/engine-server.md\n * 4.3): where the kiosks are. Only boxes with a site have a key. */\n sites: Record<string, number>;\n}\n\n/** What a bundle offers a host, read from the asset alone. */\nexport interface BundleDescription {\n identity: BundleIdentity;\n /** Orientation, not inventory: no card lists (Reboot 2.1). */\n totals: {\n boxes: number;\n decks: number;\n cards: number;\n hands: number;\n templates: number;\n tagGroups: number;\n };\n boxes: BoxSummary[];\n /** Every hand in the bundle, box by box: the deal() surface. */\n hands: HandSummary[];\n /** The host scopes in bundle order (world, and any others such as player),\n * story, then per box: the box, its decks, its hands, its tags; then the\n * project map's zones, once. Scopes that declare nothing are omitted (world\n * and story always show, so their absence reads as \"this bundle declares\n * none\"; an undeclared world shows first, empty). */\n properties: PropertyScopeSummary[];\n /** The project map, when the bundle has one. Absent is the bundle with no\n * map at all. */\n map?: MapSummary;\n}\n\nconst summarise = (decls: readonly (PropertyDecl | HostScopeDecl)[]): PropertySummary[] =>\n decls.map((d: PropertyDecl) => ({\n name: d.name,\n type: d.type,\n default: defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n ...(d.durable === true ? { durable: true as const } : {}),\n ...(d.purpose !== undefined ? { purpose: d.purpose } : {}),\n }));\n\n/** How many cards in a box are durable: the card's own flag, else its deck's\n * (design/engine-server.md 4.2, the same inheritance `shared` has). */\nconst durableCardCount = (box: Box<Expression>): number =>\n box.decks.reduce((n, deck) =>\n n + deck.cards.filter((card) => (card.durable ?? deck.durable) === true).length, 0);\n\n/** A hand's declared @hand state: a template instance inherits its template's\n * declarations, a standalone hand declares its own (schema 2.6) - the same\n * rule the session's hand bags are built on. */\nconst handDecls = (hand: Hand<Expression>, box: Box<Expression>): PropertyDecl[] => {\n if (hand.template !== undefined) {\n return box.handTemplates.find((t) => t.id === hand.template)?.properties ?? [];\n }\n return hand.properties ?? [];\n};\n\n/** The hand's movable holes, in the bundle's own key order: every `chosen` /\n * rule-binding value that is a property reference rather than a tag (4.6).\n * The group is looked up where the engine looks it up: the box's own groups\n * and, for a box on the project map, the map's group, so the roaming\n * character whose hole names a zone is reported rather than lost (3.7). A\n * group id neither place holds is a bundle the engine refuses or cannot\n * bind, and is skipped rather than reported under its raw id: the\n * description speaks gameIds throughout. */\nconst movableHoles = (bundle: Bundle, hand: Hand<Expression>, box: Box<Expression>): MovableHole[] => {\n const filled = hand.template !== undefined ? hand.chosen : hand.rule?.bindings;\n const out: MovableHole[] = [];\n for (const [groupId, value] of Object.entries(filled ?? {})) {\n if (!isHoleRef(value)) continue;\n const group = groupsOfBox(bundle, box).find((g) => g.id === groupId);\n if (group === undefined) continue;\n out.push({ group: effectiveGameId(group), from: value });\n }\n return out;\n};\n\n/** The effective slot cap, resolved the way the session resolves capacity. */\nconst handSlots = (hand: Hand<Expression>, box: Box<Expression>): number | \"unbounded\" => {\n if (hand.slots !== undefined) return hand.slots;\n const declared = hand.template !== undefined\n ? box.handTemplates.find((t) => t.id === hand.template)?.slots\n : hand.rule?.slots;\n return declared === undefined ? \"unbounded\" : declared;\n};\n\n/** Describe a compiled bundle: the callable surface of an imported asset, no\n * session required (design/engine-runtimes.md 2, piece 6). Bundle order\n * throughout; the same shape every runtime returns. */\nexport function describeBundle(bundle: Bundle): BundleDescription {\n const boxes: BoxSummary[] = [];\n const hands: HandSummary[] = [];\n const hosts = bundleHostScopes(bundle);\n const properties: PropertyScopeSummary[] = [\n ...(hosts.some((h) => h.token === \"world\") ? [] : [{ scope: \"world\" as const, owner: \"\", properties: [] }]),\n ...hosts.map((h): PropertyScopeSummary => ({\n ...(h.token === \"world\" ? { scope: \"world\" as const, owner: \"\" } : { scope: \"host\" as const, owner: h.token }),\n ...(h.declarations === undefined ? { opaque: true as const } : {}),\n properties: summarise(h.declarations ?? []),\n })),\n { scope: \"story\", owner: \"\", properties: summarise(bundle.story.properties) },\n ];\n const totals = { boxes: 0, decks: 0, cards: 0, hands: 0, templates: 0, tagGroups: 0 };\n\n for (const box of bundle.boxes) {\n const boxGameId = effectiveGameId(box);\n const cards = box.decks.reduce((n, deck) => n + deck.cards.length, 0);\n boxes.push({\n gameId: boxGameId,\n ...(box.title !== undefined ? { title: box.title } : {}),\n ...(box.usesMap === true ? { usesMap: true as const } : {}),\n ranking: { specificity: box.ranking.specificity },\n ...(box.turn !== undefined ? { turn: box.turn.seconds !== undefined ? { seconds: box.turn.seconds } : {} } : {}),\n ...(durableCardCount(box) > 0 ? { durableCards: durableCardCount(box) } : {}),\n tagGroups: box.tagGroups.map((group) => ({\n gameId: effectiveGameId(group),\n tags: group.tags.map((tag) => effectiveGameId(tag)),\n })),\n counts: {\n decks: box.decks.length,\n cards,\n hands: box.hands.length,\n templates: box.handTemplates.length,\n tagGroups: box.tagGroups.length,\n },\n });\n totals.boxes += 1;\n totals.decks += box.decks.length;\n totals.cards += cards;\n totals.hands += box.hands.length;\n totals.templates += box.handTemplates.length;\n totals.tagGroups += box.tagGroups.length;\n\n for (const hand of box.hands) {\n const template = hand.template !== undefined\n ? box.handTemplates.find((t) => t.id === hand.template)\n : undefined;\n const movable = movableHoles(bundle, hand, box);\n hands.push({\n gameId: effectiveGameId(hand),\n ...(hand.title !== undefined ? { title: hand.title } : {}),\n box: boxGameId,\n slots: handSlots(hand, box),\n ...(template !== undefined ? { template: effectiveGameId(template) } : {}),\n ...(movable.length > 0 ? { movable } : {}),\n });\n }\n\n // The property scopes, in the session's store order: box, decks, hands,\n // tags. Empty declaration blocks are dropped (nothing to read or set).\n const push = (scope: PropertyScopeKind, owner: string, decls: PropertyDecl[], group?: string): void => {\n if (decls.length === 0) return;\n properties.push({\n scope, owner, box: boxGameId, ...(group !== undefined ? { group } : {}),\n properties: summarise(decls),\n });\n };\n push(\"box\", boxGameId, box.properties);\n for (const deck of box.decks) push(\"deck\", effectiveGameId(deck), deck.properties);\n for (const hand of box.hands) push(\"hand\", effectiveGameId(hand), handDecls(hand, box));\n for (const group of box.tagGroups) {\n for (const tag of group.tags) {\n push(\"tag\", effectiveGameId(tag), tag.properties ?? [], effectiveGameId(group));\n }\n }\n }\n\n // The project map's zones, ONCE and after every box, whichever boxes use\n // them: the same order the engine's value bags are built in. A `tag` scope\n // with no `box`, because a zone belongs to none.\n const map = bundle.map;\n if (map !== undefined) {\n const group = effectiveGameId(map.group);\n for (const tag of map.group.tags) {\n const decls = tag.properties ?? [];\n if (decls.length > 0) properties.push({ scope: \"tag\", owner: effectiveGameId(tag), group, properties: summarise(decls) });\n }\n totals.tagGroups += 1;\n }\n\n return {\n identity: {\n schema: bundle.schema,\n project: bundle.content.project,\n version: bundle.content.version,\n hash: bundle.content.hash,\n metadata: bundle.metadata,\n },\n totals,\n boxes,\n hands,\n properties,\n ...(map !== undefined ? {\n map: {\n group: effectiveGameId(map.group),\n tags: map.group.tags.map((tag) => effectiveGameId(tag)),\n boxes: bundle.boxes.filter((box) => box.usesMap === true).map((box) => effectiveGameId(box)),\n zones: map.geometry?.zones.length ?? 0,\n backgrounds: map.geometry?.backgrounds?.length ?? 0,\n sites: Object.fromEntries(Object.entries(map.geometry?.sites ?? {}).map(([box, sites]) => [box, sites.length])),\n },\n } : {}),\n };\n}\n","// ---------------------------------------------------------------------------\n// Field read helpers: a box's card and outcome fields, and any node's Storylet\n// extension values, as a host reads them.\n//\n// The engine hands a card's fields (and an outcome's) to the host exactly as\n// the bundle wrote them: sparse, a field the card leaves out simply absent.\n// That is deliberate, because \"the card sets nothing\" and \"the card sets the\n// default\" are different facts a host may care about. Most hosts want the\n// filled-in picture, though, and the declared defaults ride in the bundle, so\n// these pure helpers do that resolution once rather than in every game.\n//\n// Patter's gameData helpers (gameDataFields, gameDataValue, effectiveGameData)\n// are the same idea, by the same rule (patterkit design/family-parity-2026-10.md\n// item 7), and every Storylet Engine runtime carries these four.\n//\n// Extension values follow the same rule (patterkit design/proposals/\n// patter-storylet-extensions.md, \"At runtime\"): raw on the node, declarations\n// with every default resolved in the bundle, and the SAME two helpers fill\n// them in, given `extensionDecls`. `extensionsOf` reads the values of a node\n// no view carries (a deck, a hand, a tag), by the address a game already uses.\n// ---------------------------------------------------------------------------\n\nimport type { Bundle, ExtensionFieldDecl, FieldDecl, NodeExtensions, ScalarValue } from \"@storylet-studio/model\";\n\ntype BundleBoxes = Pick<Bundle, \"boxes\">;\n\nconst boxOf = (bundle: BundleBoxes, box: string) =>\n bundle.boxes.find((b) => b.gameId === box) ?? bundle.boxes.find((b) => b.id === box);\n\nconst own = (record: object, name: string): boolean => Object.prototype.hasOwnProperty.call(record, name);\n\n/** A box's card template, by its gameId (or id). Empty when the box declares\n * none or is not in the bundle. */\nexport function fieldDecls(bundle: BundleBoxes, box: string): FieldDecl[] {\n return boxOf(bundle, box)?.fields ?? [];\n}\n\n/** A box's outcome fields, by its gameId (or id). Empty when it declares none. */\nexport function outcomeFieldDecls(bundle: BundleBoxes, box: string): FieldDecl[] {\n return boxOf(bundle, box)?.outcomeFields ?? [];\n}\n\n/** A declaration as the two helpers below read it: a field's, or an extension field's. */\ninterface Declared<V> { readonly name: string; readonly default?: V }\n\n/** One field's effective value: the card's (or outcome's) own when it sets\n * one, else the field's declared default; undefined for a name declared\n * nowhere and set nowhere. */\nexport function fieldValue<V = ScalarValue>(\n decls: readonly Declared<V>[], values: Readonly<Record<string, V>> | undefined, name: string,\n): V | undefined {\n if (values !== undefined && own(values, name)) return values[name];\n // The declaration's own default is the bundle's, shared by every card that\n // leaves the field out: a structured one is handed out as a copy, or a game\n // changing it would change it for all of them (the hands-on review, 2026-10,\n // foundation 6).\n const fallback = decls.find((f) => f.name === name)?.default;\n return fallback !== null && typeof fallback === \"object\" ? structuredClone(fallback) : fallback;\n}\n\n/** Every declared field resolved (the value set, else the default), plus any\n * key set with no declaration, kept as it is. */\nexport function effectiveFields<V = ScalarValue>(\n decls: readonly Declared<V>[], values: Readonly<Record<string, V>> | undefined,\n): Record<string, V> {\n const out: Record<string, V> = {};\n for (const f of decls) {\n const v = fieldValue(decls, values, f.name);\n if (v !== undefined) out[f.name] = v;\n }\n for (const [k, v] of Object.entries(values ?? {})) if (!own(out, k)) out[k] = v;\n return out;\n}\n\n/** One extension's declarations for a node kind (`card`, `outcome`, `box`,\n * `deck`, `tagGroup`, `tag`, `handTemplate`, `hand`, `project`), every default\n * resolved. Empty when the bundle has no such extension or it declares nothing\n * for the kind. */\nexport function extensionDecls(bundle: Pick<Bundle, \"extensions\">, extension: string, kind: string): ExtensionFieldDecl[] {\n const ext = bundle.extensions !== undefined && own(bundle.extensions, extension) ? bundle.extensions[extension] : undefined;\n return ext !== undefined && own(ext.fields, kind) ? ext.fields[kind] ?? [] : [];\n}\n\n/**\n * A node's extension values, by extension id, raw; `{}` when it has none or the\n * address names nothing. For the nodes no view carries, by the names a game\n * already holds: `project` (no address); `box`, `deck`, `card` and `hand` by\n * gameId; `handTemplate` as \"box/template\"; `outcome` as \"card/outcome\";\n * `tagGroup` and `tag` as \"box/name\", or the bare name for the project map's.\n */\nexport function extensionsOf(bundle: Pick<Bundle, \"boxes\" | \"map\" | \"extensions\">, kind: string, address = \"\"): NodeExtensions {\n // The node's values themselves, read-only by contract, as the engine's views\n // hand them out (a family ruling, 2026-10-28): the record round them is new.\n const values = (node: { extensions?: NodeExtensions } | undefined): NodeExtensions => ({ ...(node?.extensions ?? {}) });\n const [first, second] = address.includes(\"/\") ? [address.slice(0, address.indexOf(\"/\")), address.slice(address.indexOf(\"/\") + 1)] : [address, undefined];\n const named = <T extends { id: string; gameId?: string }>(items: readonly T[], name: string): T | undefined =>\n items.find((x) => (x.gameId ?? x.id) === name);\n const cards = () => bundle.boxes.flatMap((b) => b.decks.flatMap((d) => d.cards));\n switch (kind) {\n case \"project\": {\n const out: NodeExtensions = {};\n for (const [id, ext] of Object.entries(bundle.extensions ?? {})) if (ext.project !== undefined) out[id] = ext.project;\n return out;\n }\n case \"box\": return values(named(bundle.boxes, address));\n case \"deck\": return values(named(bundle.boxes.flatMap((b) => b.decks), address));\n case \"card\": return values(named(cards(), address));\n case \"outcome\": return second === undefined ? {} : values(named(named(cards(), first)?.outcomes ?? [], second));\n case \"hand\": return values(named(bundle.boxes.flatMap((b) => b.hands), address));\n case \"handTemplate\": return second === undefined ? {} : values(named(named(bundle.boxes, first)?.handTemplates ?? [], second));\n case \"tagGroup\":\n if (second === undefined) return bundle.map !== undefined && (bundle.map.group.gameId ?? bundle.map.group.id) === first ? values(bundle.map.group) : {};\n return values(named(named(bundle.boxes, first)?.tagGroups ?? [], second));\n case \"tag\":\n if (second === undefined) return values(bundle.map !== undefined ? named(bundle.map.group.tags, first) : undefined);\n return values(named((named(bundle.boxes, first)?.tagGroups ?? []).flatMap((g) => g.tags), second));\n default: return {};\n }\n}\n","// ---------------------------------------------------------------------------\n// The storylets state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry (design/engine-runtimes.md 3.4 is the design of\n// record). The kernel does the work - push-based property logging on the\n// PropertyBag audit hook, so a write logs the moment it lands, plus a diff of\n// everything that has no audit hook - and this supplies the two product-shaped\n// pieces: which bags to watch, and the non-property state (turns / cooldowns /\n// board) as flattened paths.\n//\n// The core lived here, marked \"moves into the kernel package wholesale when the\n// vendor-sync slice lands\". It has. Patterplay's logger, which diffed save\n// snapshots and so could not see a value that changed and changed back, is an\n// adapter over the same core now.\n//\n// Flattened path scheme:\n// world.x / story.x / box.<gameId>.x / deck.<gameId>.x / hand.<gameId>.x /\n// value.<tagGameId>.x, and value.<boxGameId>/<tagGameId>.x for a tag gameId\n// two boxes share (4.4)\n// turn:<boxGameId> per-box clocks\n// cooldown:<cardGameId> next-eligible turns\n// board:<handGameId> hand contents (card Game IDs, dealt order)\n// (Game IDs as the save keys them from version 3; internal ids before.)\n// Line format: `${label}${path}: ${from} -> ${to}`, `<unset>` for undefined.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@storylet-studio/runtime\";\nimport type { FlowSave, ScalarValue } from \"@storylet-studio/model\";\nimport {\n createStateLogger as createKernelStateLogger, diffState,\n} from \"@wildwinter/scoperegistry\";\nimport type {\n StateSnapshot, StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions,\n} from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @storylet-studio/play-helpers should not have to care that they moved.\nexport { createKernelStateLogger, diffState };\nexport type { StateSnapshot, StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions };\n\n/** The full flattened snapshot of ONE FLOW's view - the shared partitions\n * plus that flow's own - plus its turns / cooldowns / board. @world is not\n * here for the same reason it is not in a save envelope: the host owns that\n * container and mounts/saves it itself (createWorldContainer).\n *\n * Taken off the BAGS, which is what a save envelope is made of, rather than\n * off the envelope itself. The two used to be interchangeable; from 4.4 they\n * are not, because a property ADDRESS names its owner by gameId while the\n * envelope stays keyed by internal id (a save has to survive a rename). The\n * bags carry the address, so reading them is what keeps this snapshot and\n * the live logger's lines in ONE path space - which is the invariant the\n * whole diff rests on. */\nexport function snapshotState(engine: Engine, flow: Flow): StateSnapshot {\n const out: StateSnapshot = {};\n // Shared under the flow's own: names are disjoint (shared XOR per-flow by\n // declaration), so one path space holds both without collision.\n for (const { bag } of [...engine.listBags(), ...flow.listBags()]) {\n for (const row of bag.rows()) {\n if (row.value !== undefined) out[row.path] = row.value as ScalarValue;\n }\n }\n Object.assign(out, extraState(flowBlob(engine, flow.id)));\n return out;\n}\n\n/** ONE flow's blob, or undefined for a flow that is not open (a just-closed\n * one: no paths). `saveFlow` clones that flow alone, where reading it out of\n * `saveGame()` cloned the whole engine, every flow and every property, on\n * each capture. */\nfunction flowBlob(engine: Engine, id: string): FlowSave | undefined {\n return engine.getFlow(id) !== undefined ? engine.saveFlow(id) : undefined;\n}\n\n/** The storylets path-provider adapter for non-property state (design 3.4):\n * one flow's turns / cooldowns / board as flattened paths, off its blob\n * (absent for a just-closed flow: no paths). */\nfunction extraState(saved: FlowSave | undefined): StateSnapshot {\n const out: StateSnapshot = {};\n if (saved === undefined) return out;\n for (const [boxId, turn] of Object.entries(saved.turns)) out[`turn:${boxId}`] = turn;\n for (const [cardId, at] of Object.entries(saved.cooldowns)) out[`cooldown:${cardId}`] = at;\n for (const [handId, cards] of Object.entries(saved.board)) out[`board:${handId}`] = [...cards];\n return out;\n}\n\n/** The storylets state logger: the kernel core mounted on the SHARED bags\n * (engine.listBags()) and one flow's own (flow.listBags()) - the same\n * prefixes, one path space, names disjoint - plus the flow's turns /\n * cooldowns / board adapter. A host that wants @world lines mounts its\n * world container's bag through createKernelStateLogger itself. */\nexport function createStateLogger(engine: Engine, flow: Flow, opts: StateLoggerOptions = {}): StateLogger {\n // By NAME, not by handle: loadGame rebuilds every flow and the handle we\n // were given goes inert; capture()'s re-mount picks up the rebuilt one.\n const id = flow.id;\n const live = (): Flow | undefined => engine.getFlow(id);\n return createKernelStateLogger({\n // A BagMount's `prefix` (\"story\", \"deck.<id>\") is the engine's label for the mount;\n // the kernel composes paths from the BAG's own pathPrefix (\"story.\", \"deck.<id>.\")\n // and needs none passed. Same strings, one owner.\n mounts: () => [...engine.listBags(), ...(live()?.listBags() ?? [])].map(({ bag }) => ({ bag })),\n extra: () => extraState(flowBlob(engine, id)),\n }, opts);\n}\n","// ---------------------------------------------------------------------------\n// Save-file plumbing over the .storyletsave file (storylets/savefile@1): the\n// HOST's file - the engine's envelope (storylets/save@3; @2 and @1 still read) plus,\n// when the host keeps one, its @world container. That is\n// \"host saves its container once, each engine saves its own envelope\"\n// (design/flows.md) folded into one file for the single-host case. These\n// helpers are the string boundary - a foreign or malformed blob throws\n// rather than corrupting a run.\n// ---------------------------------------------------------------------------\n\nimport { SAVEFILE_SCHEMA, SAVE_SCHEMA, SAVE_SCHEMA_V1, SAVE_SCHEMA_V2 } from \"@storylet-studio/model\";\nimport type { LoadReport, PropertyBag, SaveFile } from \"@storylet-studio/model\";\nimport type { Engine } from \"@storylet-studio/runtime\";\nimport type { WorldContainer } from \"./world.js\";\n\n/** The current engine state (and the host's @world values, if given) as\n * pretty-printed .storyletsave JSON. */\nexport function serializeState(engine: Engine, world?: PropertyBag): string {\n return JSON.stringify(saveState(engine, world), null, 2);\n}\n\n/**\n * Capture the whole engine (and the host's @world values, if it keeps any) as\n * the tagged save-file OBJECT.\n *\n * Four verbs, in Patterplay's pairing (`patter` play-helpers `save.ts`, and\n * the same in all four of its runtimes): saveState / loadState work on the\n * PARSED object, serializeState / deserializeState work on TEXT.\n *\n * This reference had a different shape until 2026-08-29 - `deserializeState`\n * parsed and did not restore, `loadState` took text - so one name meant two\n * things across the four Storylets runtimes, and neither matched the family.\n * Godot and Unreal already had Patter's shape; these two were brought to it.\n */\nexport function saveState(engine: Engine, world?: PropertyBag): SaveFile {\n return {\n schema: SAVEFILE_SCHEMA,\n engine: engine.saveGame(),\n ...(world !== undefined ? { world } : {}),\n };\n}\n\n/** What a load may be told to do besides restoring. */\nexport interface LoadStateOptions {\n /** Handed the engine's LoadReport for this file as the load happens: what\n * the save cost against the running build (cards evicted, properties\n * dropped, defaulted or retyped), the same report `engine.loadGame` and\n * `engine.previewLoad` return. A callback rather than a return value\n * because these functions already return the file's @world values. */\n onReport?: (report: LoadReport) => void;\n /** The host's @world container (`createWorldContainer`, or anything with\n * the same `values` and `load`). Given, the file's @world values go into it\n * BEFORE the engine loads, so the load's check of each held card reads the\n * saved world rather than the one the game had; a refused load puts the\n * container back as it was. Without it the values are only returned, and\n * the game must apply them before loading for the same check. */\n world?: Pick<WorldContainer, \"values\" | \"load\">;\n}\n\n/** Restore a {@link saveState} file into an engine. EVERY FLOW IS REBUILT, so\n * the Flow handles you held before are inert: re-take them with\n * `engine.getFlow(id)`, NOT `engine.openFlow(id)`. `openFlow` on an existing\n * id REPLACES it, which here throws away the hand the file just restored, and\n * the failure lands later, as `play()` refusing a card as \"not dealt\". (The\n * engine's `onReplacedFlow` hook reports exactly this.) Throws on a foreign or malformed\n * file, and the runtime's own project check still applies. Returns the file's\n * @world values, if any. Pass the host's container as `opts.world` and they\n * are applied before the load (see LoadStateOptions.world); the engine itself\n * never touches them. */\nexport function loadState(engine: Engine, file: SaveFile, opts: LoadStateOptions = {}): PropertyBag | undefined {\n if (!file || typeof file !== \"object\"\n || file.schema !== SAVEFILE_SCHEMA\n || (file.engine?.schema !== SAVE_SCHEMA && file.engine?.schema !== SAVE_SCHEMA_V2 && file.engine?.schema !== SAVE_SCHEMA_V1)) {\n throw new Error(`not a storylets save (expected schema \"${SAVEFILE_SCHEMA}\")`);\n }\n const container = opts.world;\n // A copy: a container's values can be its live record, which the load below writes.\n const before = container !== undefined ? structuredClone(container.values()) : undefined;\n if (container !== undefined && file.world !== undefined) container.load(file.world);\n let report: LoadReport;\n try {\n report = engine.loadGame(file.engine);\n } catch (e) {\n if (container !== undefined && before !== undefined) container.load(before);\n throw e;\n }\n opts.onReport?.(report);\n return file.world;\n}\n\n/** Parse + restore a {@link serializeState} string: the TEXT twin of\n * loadState, as Patterplay pairs them. Throws on malformed JSON, a foreign\n * file or a project mismatch. Returns the file's @world values for the host. */\nexport function deserializeState(engine: Engine, json: string, opts: LoadStateOptions = {}): PropertyBag | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"not valid JSON\");\n }\n return loadState(engine, parsed as SaveFile, opts);\n}\n","// ---------------------------------------------------------------------------\n// A single themed tooltip the family controls, replacing native `title`\n// rollovers: those are unstyled OS chrome and appear on the platform's slow\n// (~1s) delay, which is exactly the seam design-language section 4 (\"coherent\n// to the edges\") says not to leave showing.\n//\n// ONE floating bubble, document-DELEGATED: any element carrying a `data-tip`\n// attribute gets it, so a caller just sets `data-tip=\"...\"` anywhere and wires\n// nothing. The bubble anchors above the element (flipping below near the top\n// edge), uses our own snappy delay and the shared panel-enter motion.\n// `initTooltips()` is idempotent, so it is safe to call from more than one\n// place in an app that has several entry points.\n//\n// Lifted from Patterpad's patterpad-surface/web/tooltip.ts, which proved it,\n// with the one app-specific rule (suppressed in Writing View) generalised into\n// the `suppressed` option so the shell knows nothing about either app's modes.\n// ---------------------------------------------------------------------------\n\n// Bold markers: a producer wraps a span in these PRIVATE control chars (U+0002 STX / U+0003 ETX,\n// never present in user text) and the renderer turns it into a <strong>. Lets a data-tip bold part\n// of itself (a name, a \"VO:\" prefix) without any HTML, and each segment is still set via\n// textContent, so there is no injection risk from user content.\nconst B_OPEN = \"\\u0002\";\nconst B_CLOSE = \"\\u0003\";\n\n/** Wrap `s` so it renders bold inside a tooltip (see the marker note above). */\nexport function tipBold(s: string): string { return `${B_OPEN}${s}${B_CLOSE}`; }\n\n/** Render `text` into `host`, turning `\\u0002…\\u0003`-marked spans into <strong>; all set as text (safe). */\nfunction renderTip(host: HTMLElement, text: string): void {\n host.textContent = \"\";\n const re = /\\u0002([^\\u0003]*)\\u0003/g;\n let last = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) host.append(text.slice(last, m.index));\n const strong = document.createElement(\"strong\");\n strong.textContent = m[1] ?? \"\";\n host.append(strong);\n last = m.index + m[0].length;\n }\n if (last < text.length) host.append(text.slice(last));\n}\n\nexport interface TooltipOptions {\n /** Called before every show: return true to keep the bubble down. For a mode\n * that wants no chrome at all (Patterpad's Writing View). */\n suppressed?: () => boolean;\n}\n\nlet inited = false;\nlet checked = false; // the one-shot no-host warning below has been scheduled\nlet suppressed: (() => boolean) | undefined;\nconst SHOW_DELAY = 350; // ms - snappier than the OS title delay, slow enough not to flicker on pass-through\nconst EDGE = 6; // viewport inset so the bubble never touches the window edge\nconst GAP = 7; // px between the anchor and the bubble\n\nlet tip: HTMLDivElement | null = null;\nlet timer = 0;\nlet active: HTMLElement | null = null;\n/** The subject of a rect-anchored tip (see `tipAt`), when there is one. */\nlet activeKey: string | null = null;\n\nfunction ensureEl(): HTMLDivElement {\n if (tip) return tip;\n const el = document.createElement(\"div\");\n el.className = \"tooltip\";\n el.setAttribute(\"role\", \"tooltip\");\n el.hidden = true;\n document.body.appendChild(el);\n tip = el;\n return el;\n}\n\n/** A rectangle in VIEWPORT coordinates, for anchoring a tip to something that is\n * not a DOM element: a shape on a canvas, a run of text inside one. */\nexport interface TipRect { left: number; top: number; width: number; height: number }\n\nconst rectOf = (anchor: HTMLElement | TipRect): TipRect =>\n (anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor);\n\n/** Anchor the (already-visible, so measurable) bubble above the anchor, flipping below near the top. */\nfunction place(anchor: HTMLElement | TipRect): void {\n const t = ensureEl();\n const r = rectOf(anchor);\n const tr = t.getBoundingClientRect();\n let top = r.top - tr.height - GAP;\n if (top < EDGE) top = r.top + r.height + GAP; // not enough room above -> below\n let left = r.left + r.width / 2 - tr.width / 2;\n left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));\n t.style.left = `${Math.round(left)}px`;\n t.style.top = `${Math.round(top)}px`;\n}\n\nfunction show(anchor: HTMLElement): void {\n const text = anchor.dataset[\"tip\"];\n if (!text) return;\n paint(anchor, text);\n}\n\n/** Fill, place and reveal the bubble. The one path: both the delegated\n * `data-tip` route and `tipAt` end up here, so a canvas tip and a button tip\n * are the same bubble with the same look and the same motion. */\nfunction paint(anchor: HTMLElement | TipRect, text: string): void {\n if (suppressed?.()) return;\n const t = ensureEl();\n // A modal <dialog> (settings, a confirm) renders in the browser TOP LAYER, above every normal-flow\n // element whatever its z-index. If the anchor is inside one, move the bubble into that dialog so it\n // shares the top layer; otherwise keep it on <body>. Re-parenting per show also moves it back out\n // once the modal closes.\n const host: HTMLElement = (anchor instanceof HTMLElement ? anchor.closest<HTMLElement>(\"dialog[open]\") : null)\n ?? document.body;\n if (t.parentElement !== host) host.appendChild(t);\n active = anchor instanceof HTMLElement ? anchor : null;\n renderTip(t, text);\n t.hidden = false;\n t.classList.remove(\"show\"); void t.offsetWidth; // restart the enter animation\n place(anchor); // measure + position now it has content + is visible\n t.classList.add(\"show\");\n}\n\nfunction hide(): void {\n if (timer) { window.clearTimeout(timer); timer = 0; }\n active = null;\n activeKey = null;\n if (tip) { tip.hidden = true; tip.classList.remove(\"show\"); }\n}\n\nfunction schedule(anchor: HTMLElement): void {\n if (anchor === active) return;\n if (timer) window.clearTimeout(timer);\n timer = window.setTimeout(() => show(anchor), SHOW_DELAY);\n}\n\n/**\n * Show a tip for something that has no DOM node of its own: a card on a canvas,\n * a zone, a pin. The caller says WHERE (a viewport rectangle) and WHAT, and\n * identifies the subject with `key`.\n *\n * The key is what makes this usable from a canvas. Re-calling with the same key\n * re-places the bubble WITHOUT restarting the delay, so a tip can follow its\n * shape while the camera moves; calling with a different key starts the wait\n * again, so sweeping the pointer across a dense board does not strobe.\n *\n * The bubble is ordinary DOM, so it holds its size whatever the canvas is\n * zoomed to. That is the point of it: the label it stands in for is the one\n * thing on a canvas that must not shrink with everything else.\n */\nexport function tipAt(key: string, rect: TipRect, text: string): void {\n if (key === activeKey) {\n // Already up for this subject: move it, do not re-animate it.\n if (tip && !tip.hidden) { renderTip(tip, text); place(rect); }\n return;\n }\n // A different subject: down first, then wait again. Leaving the old bubble up\n // during the wait would put one card's name over another card, which is worse\n // than no name at all - and it matches the delegated route, where leaving an\n // element hides the tip before the next one is scheduled.\n hide();\n activeKey = key;\n timer = window.setTimeout(() => { timer = 0; paint(rect, text); }, SHOW_DELAY);\n}\n\n/** Take any tip down: the pointer has left, or a gesture has started. */\nexport function hideTip(): void { hide(); }\n\n/**\n * Mount the renderer because something is about to speak the grammar.\n *\n * The themed tooltip is not a feature a host turns on; it is the renderer for a grammar the shell's own\n * components already speak, and `data-tip` with nothing listening draws NOTHING - not the themed\n * bubble, not the platform one. That silence cost Patterpad two releases of a pin with no tooltip, and\n * cost it again when a `title` was converted to `data-tip` in a window that had never mounted this.\n *\n * So `el` calls this whenever it sets a tip. Mounting a delegated listener is the same class of act as\n * `confirmDialog` appending to `document.body`, which this package has always done.\n */\nexport function ensureTooltipHost(): void { initTooltips(); }\n\n/**\n * One deferred check that an app writing `data-tip` ITSELF has a host, warning if not.\n *\n * `ensureTooltipHost` covers anything built through `el`. It cannot cover a `data-tip` written straight\n * into markup or set by hand, which is the case that leaves a whole window of dead tips with no shell\n * component involved - so that one gets a warning rather than silence. Scheduled once, after the\n * current task, so it sees a rendered document rather than an empty one.\n */\nexport function checkTooltipHost(): void {\n if (checked) return;\n checked = true;\n setTimeout(() => {\n if (inited || typeof document === \"undefined\") return;\n if (document.querySelector(\"[data-tip]\")) {\n console.warn(\"app-shell: something set `data-tip` but no tooltip host is mounted, so those rollovers will not draw. Call initTooltips() once per window.\");\n }\n }, 0);\n}\n\nconst tipAncestor = (n: EventTarget | null): HTMLElement | null =>\n (n instanceof Element ? n.closest<HTMLElement>(\"[data-tip]\") : null);\n\n/**\n * Wire the one delegated controller. Mounting happens once; OPTIONS do not.\n *\n * An explicit call is always authoritative about its options, even after something else has already\n * mounted the listener. That ordering is not hypothetical, and Storyletter caught it before it shipped:\n * once components self-mount (see `ensureTooltipHost`), a host's own\n * `initTooltips({ suppressed })` could land second and be dropped by an early return, silently taking\n * a rule like \"no tooltips in Writing View\" with it. Nothing would fail and nothing would show.\n *\n * Components therefore call this with NO arguments: they mount the renderer without claiming the\n * options, and the host keeps the last word on behaviour.\n */\nexport function initTooltips(opts: TooltipOptions = {}): void {\n if (opts.suppressed !== undefined) suppressed = opts.suppressed; // an explicit call always wins\n if (inited) return;\n inited = true;\n\n document.addEventListener(\"pointerover\", (e) => {\n const el = tipAncestor(e.target);\n if (el) schedule(el);\n });\n document.addEventListener(\"pointerout\", (e) => {\n const el = tipAncestor(e.target);\n if (!el) return;\n const to = (e as PointerEvent).relatedTarget;\n if (to instanceof Node && el.contains(to)) return; // still inside the same anchor\n hide();\n });\n // Keyboard parity: reveal on focus, dismiss on blur.\n document.addEventListener(\"focusin\", (e) => { const el = tipAncestor(e.target); if (el) schedule(el); });\n document.addEventListener(\"focusout\", () => hide());\n // It's ANCHORED (not cursor-following), so anything that moves the layout or context dismisses it.\n document.addEventListener(\"pointerdown\", () => hide(), true);\n document.addEventListener(\"scroll\", () => { if (active) hide(); }, true);\n document.addEventListener(\"keydown\", (e) => { if (e.key === \"Escape\") hide(); });\n window.addEventListener(\"blur\", () => hide());\n}\n","// ---------------------------------------------------------------------------\n// `el`: the tiny tag-typed element factory both apps build their imperative,\n// idempotent views with.\n//\n// A SUPERSET of the two apps' prior signatures, so neither rewrites call sites:\n// - Patterpad: el(tag, cls?, text?) (positional)\n// - Storylet Studio: el(tag, { className, text, ... }, ...kids) (options bag)\n// A string second argument is the className; an object is the props bag; string\n// children become text nodes. So `el(\"div\", \"c\", \"t\")` and\n// `el(\"div\", { className: \"c\" }, \"t\")` produce identical DOM.\n// ---------------------------------------------------------------------------\n\nimport { ensureTooltipHost, checkTooltipHost } from \"./tooltip.js\";\nimport { iconNode, iconNameOfGlyph, isIconName, type IconName } from \"./icons.js\";\n\nexport type Child = Node | string | null | undefined;\n\nexport interface ElProps {\n className?: string;\n text?: string;\n /** A NATIVE rollover. Prefer `tip`: `title` is OS chrome on the platform's own\n * slow delay, which is the seam the themed tooltip exists to close. */\n title?: string;\n /** A themed rollover (`data-tip`, picked up by the delegated controller that\n * `initTooltips()` wires). Also the accessible name, since the elements that\n * want one are usually icon buttons with no text of their own. */\n tip?: string;\n onClick?: (event: MouseEvent) => void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n classNameOrProps?: string | ElProps,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n const props: ElProps = typeof classNameOrProps === \"string\"\n ? { className: classNameOrProps }\n : classNameOrProps ?? {};\n if (props.className) node.className = props.className;\n if (props.text !== undefined && props.text !== null) node.textContent = props.text;\n if (props.title !== undefined) node.title = props.title;\n if (props.tip !== undefined) {\n node.dataset[\"tip\"] = props.tip;\n node.setAttribute(\"aria-label\", props.tip);\n // Setting a tip mounts the renderer. Without this, `data-tip` draws nothing at all in a window whose\n // host never called `initTooltips()`, which is a silent failure rather than a degraded one. No\n // options passed: the host stays authoritative about behaviour (see tooltip.ts).\n ensureTooltipHost();\n }\n if (props.onClick) node.addEventListener(\"click\", props.onClick as EventListener);\n for (const child of children) {\n if (child === null || child === undefined) continue;\n node.append(child);\n }\n checkTooltipHost(); // one-shot: warns if the APP wrote data-tip and nothing mounted a host\n return node;\n}\n\n/** A small square icon button (the move/delete controls list editors share).\n * Its rollover is the THEMED one (`data-tip`), so an app that has called\n * `initTooltips()` gets our bubble rather than the platform's.\n *\n * `name` is a word from the vocabulary (\"up\", \"close\"), drawn at 14px. A\n * typed glyph from the deprecated `icon` table (\"↑\", \"✕\") is accepted for\n * now and drawn as the word it stood for, so an app that has not moved its\n * call sites yet still gets the drawn set; any other string is set as text,\n * which is the old behaviour and on its way out. */\nexport function iconBtn(name: IconName | (string & {}), title: string, onClick: () => void, disabled = false, danger = false): HTMLButtonElement {\n const b = el(\"button\", `shell-icon${danger ? \" danger\" : \"\"}`);\n b.type = \"button\"; b.dataset[\"tip\"] = title; b.setAttribute(\"aria-label\", title);\n const word = isIconName(name) ? name : iconNameOfGlyph(name);\n if (word) b.append(iconNode(word)); else b.textContent = name;\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n}\n\n/** The sentence under a settings row: plain text, or nodes when it carries\n * markup (a `code` for a file name, a `b` for an option's name). */\nexport type FieldHint = string | Node | Array<string | Node>;\n\n/**\n * A captioned field, the family's settings row (the 2026-10 review, ruling C):\n * the caption on the left and the control inline beside it,\n * `<label class=\"shell-labelled\"><span class=\"shell-fieldcap\">…</span>control</label>`.\n *\n * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no\n * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption\n * wrapped around a control containing buttons (`tagChips`, whose every chip carries a remove button)\n * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:\n * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value\n * in the list. Reported as \"clicking a value removes the wrong one\", which is not a wrong-index bug at\n * all - the click never reached the chip. The same rule makes a control GROUP safe to caption: a field\n * with its own Choose… or Import… button beside it (`.shell-inline`) captions the field, and the button\n * stays a button.\n *\n * When the control holds no labelable field (a chips editor with the add input removed, say), this is a\n * plain `<div>`: no caption behaviour is better than a caption that presses something.\n *\n * With a `hint`, the row and the sentence under it come back together in a `.shell-field`, the hint as\n * the field's description (`aria-describedby`) rather than inside the label, where it would become part\n * of the field's NAME and be read out every time it takes focus. Without one, the row is exactly what it\n * always was.\n */\nexport function labelled(label: string, control: HTMLElement, hint?: FieldHint): HTMLElement {\n const target = control.matches(\"input, select, textarea\")\n ? control\n : control.querySelector<HTMLElement>(\"input:not([type=button]):not([type=submit]), select, textarea\");\n const w = el(target ? \"label\" : \"div\", \"shell-labelled\");\n w.append(el(\"span\", \"shell-fieldcap\", label), control);\n if (target) {\n if (!target.id) target.id = uid();\n (w as HTMLLabelElement).htmlFor = target.id;\n }\n if (hint === undefined) return w;\n const note = el(\"p\", \"shell-fieldhint\");\n note.append(...(Array.isArray(hint) ? hint : [hint]));\n note.id = uid();\n if (target) {\n const by = target.getAttribute(\"aria-describedby\");\n target.setAttribute(\"aria-describedby\", by ? `${by} ${note.id}` : note.id);\n }\n return el(\"div\", \"shell-field\", w, note);\n}\n\nconst uid = (): string => `shell-f-${Math.random().toString(36).slice(2, 9)}`;\n\nexport interface LabelledToggleOptions {\n checked?: boolean;\n /** The sentence under the row: what turning it on does. */\n hint?: FieldHint;\n /** An option that only means something while the row above it is set (Patterpad's \"Embed source\n * language for debug\" under an IDs-only build): indented under that row with a rule, so it reads as\n * belonging to it rather than as a peer. */\n sub?: boolean;\n disabled?: boolean;\n /** Fired on every flip, with the new state. The input is handed back too, for a caller that reads it\n * on Save instead. */\n onChange?: (checked: boolean) => void;\n}\n\n/**\n * The settings toggle, `labelled`'s partner (ruling C): the caption on the left, the checkbox beside it\n * where any other row has its control, and the optional hint under the row. One shape in both apps, so\n * a switch in one app's settings looks like a switch in the other's. Clicking the caption flips it, as\n * the label is the checkbox's.\n *\n * Returns the outermost row (to append, hide, or indent) and the input (to read or listen to).\n */\nexport function labelledToggle(label: string, opts: LabelledToggleOptions = {}): { row: HTMLElement; input: HTMLInputElement } {\n const input = el(\"input\", \"shell-toggle-input\");\n input.type = \"checkbox\";\n input.checked = opts.checked ?? false;\n input.disabled = opts.disabled ?? false;\n const { onChange } = opts;\n if (onChange) input.addEventListener(\"change\", () => onChange(input.checked));\n const row = labelled(label, input, opts.hint);\n // On the caption-and-box line itself, whether or not a hint wraps it.\n (row.matches(\".shell-labelled\") ? row : row.querySelector(\".shell-labelled\"))?.classList.add(\"shell-toggle\");\n if (opts.sub) row.classList.add(\"shell-suboption\");\n return { row, input };\n}\n\n/** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);\n * a no-op when the target is out of range. Returns whether anything moved. */\nexport function moveItem<T>(arr: T[], i: number, delta: number): boolean {\n const j = i + delta;\n if (j < 0 || j >= arr.length) return false;\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n return true;\n}\n\n/** The icon size inside a chip's remove / move buttons: the chip is set at\n * 0.76rem, so its controls draw smaller than a toolbar button's 14px. */\nconst CHIP_ICON = 10;\n\n/** A tag-style editor for a string-list field (an enum's allowed values / flags): removable chips + an\n * add input (Enter or \",\" commits; blank / duplicate ignored). Mutates `holder.values` in place (read\n * back on save). `onChange` fires after any add / remove so callers can refresh a dependent control. */\nexport function tagChips(holder: { values?: string[] }, onChange?: () => void): HTMLElement {\n const wrap = el(\"div\", \"shell-tags\");\n const input = el(\"input\", \"shell-tag-input\");\n // Sentence-cased like every other hint (the microcopy ruling, 2026-08-28):\n // lowercase fragments read as adrift, and a placeholder is a hint speaking.\n input.type = \"text\"; input.placeholder = \"Add value\"; input.spellcheck = false;\n const makeChip = (v: string): HTMLElement => {\n const chip = el(\"span\", \"shell-tag\", v);\n const x = el(\"button\", \"shell-tag-x\", iconNode(\"close\", CHIP_ICON));\n x.type = \"button\"; x.dataset[\"tip\"] = `Remove ${v}`; x.setAttribute(\"aria-label\", `Remove ${v}`);\n x.addEventListener(\"click\", () => { holder.values = (holder.values ?? []).filter((o) => o !== v); chip.remove(); onChange?.(); });\n chip.append(x);\n return chip;\n };\n const commit = (): void => {\n const v = input.value.trim();\n if (v && !(holder.values ?? []).includes(v)) { (holder.values ??= []).push(v); wrap.insertBefore(makeChip(v), input); onChange?.(); }\n input.value = \"\";\n };\n input.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \",\") { e.preventDefault(); commit(); } });\n input.addEventListener(\"blur\", commit);\n for (const v of holder.values ?? []) wrap.append(makeChip(v));\n wrap.append(input);\n return wrap;\n}\n\n/** What `stageChips` tells its caller beyond \"something changed\". */\nexport interface StageChipsOptions {\n /**\n * A stage was renamed in place, `from` to `to`. Given, a rename calls this\n * INSTEAD of `onChange`: values are stored by stage name, so whoever knows\n * where those values live has to carry them, and doing it as one edit is\n * theirs to arrange. Absent, a rename is an ordinary change.\n */\n onRename?: (from: string, to: string) => void;\n /** The stages were reordered (after `onChange`): the one edit that changes\n * what every ordering gate means without changing any value. */\n onReorder?: () => void;\n}\n\n/**\n * An ORDERED chips editor for a string-list field whose order means something\n * (a quality's stages: `advance()` walks them, so 2 comes after 1): numbered\n * chips, each with move-earlier / move-later / remove, plus an add input\n * (Enter or \",\" commits; blank / duplicate ignored). A stage's name renames\n * in place when clicked (Enter or blur commits, Escape cancels; blank, the same\n * name or another stage's is ignored). Mutates `holder.stages` in place;\n * `onChange` fires after any add / move / remove, and after a rename unless\n * `opts.onRename` takes it.\n *\n * Patterpad's, lifted (ui-review-2026-09, finding 3); Storyletter had been\n * faking it with `tagChips` over a `values` holder, which loses the order\n * controls. Same class family as `tagChips` (`.shell-tags`), so it is styled by\n * settings.css already.\n */\nexport function stageChips(holder: { stages?: string[] }, onChange?: () => void, opts: StageChipsOptions = {}): HTMLElement {\n const wrap = el(\"div\", \"shell-tags shell-stages\");\n const input = el(\"input\", \"shell-tag-input\");\n input.type = \"text\"; input.placeholder = \"Add stage\"; input.spellcheck = false;\n const control = (name: IconName, tip: string, disabled: boolean, onClick: () => void): HTMLButtonElement => {\n const b = el(\"button\", \"shell-tag-x shell-tag-move\", iconNode(name, CHIP_ICON));\n b.type = \"button\"; b.dataset[\"tip\"] = tip; b.setAttribute(\"aria-label\", tip);\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n };\n // The name, editable in place. A span with a button's role rather than a\n // <button>, so the chip's own controls stay its only buttons.\n const nameLabel = (chip: HTMLElement, v: string, i: number): HTMLElement => {\n const label = el(\"span\", \"shell-tag-label\", `${i + 1}. ${v}`);\n label.setAttribute(\"role\", \"button\"); label.tabIndex = 0;\n label.dataset[\"tip\"] = `Rename ${v}`;\n const edit = (): void => {\n const field = el(\"input\", \"shell-tag-input shell-stage-rename\");\n field.type = \"text\"; field.value = v; field.spellcheck = false;\n field.setAttribute(\"aria-label\", `Rename ${v}`);\n let done = false;\n const finish = (keep: boolean): void => {\n if (done) return;\n done = true;\n const to = field.value.trim();\n const stages = holder.stages ?? [];\n if (keep && to !== \"\" && to !== v && !stages.includes(to)) {\n stages[stages.indexOf(v)] = to;\n rebuild();\n if (opts.onRename) opts.onRename(v, to); else onChange?.();\n } else {\n rebuild();\n }\n };\n field.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\") { e.preventDefault(); finish(true); }\n else if (e.key === \"Escape\") { e.preventDefault(); e.stopPropagation(); finish(false); }\n });\n field.addEventListener(\"blur\", () => finish(true));\n label.replaceWith(field);\n chip.classList.add(\"editing\");\n field.focus(); field.select();\n };\n // The chips usually sit inside a <label> for the add input (`labelled`),\n // whose activation would move focus there and close the rename at once.\n label.addEventListener(\"click\", (e) => { e.preventDefault(); edit(); });\n label.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); edit(); } });\n return label;\n };\n const moved = (): void => { rebuild(); onChange?.(); opts.onReorder?.(); };\n const rebuild = (): void => {\n for (const c of Array.from(wrap.children)) if (c !== input) c.remove();\n const stages = holder.stages ?? [];\n stages.forEach((v, i) => {\n const chip = el(\"span\", \"shell-tag\");\n chip.dataset[\"stage\"] = v;\n chip.append(\n nameLabel(chip, v, i),\n control(\"back\", `Move ${v} earlier`, i === 0, () => { if (moveItem(stages, i, -1)) moved(); }),\n control(\"forward\", `Move ${v} later`, i === stages.length - 1, () => { if (moveItem(stages, i, 1)) moved(); }),\n );\n const x = el(\"button\", \"shell-tag-x\", iconNode(\"close\", CHIP_ICON));\n x.type = \"button\"; x.dataset[\"tip\"] = `Remove ${v}`; x.setAttribute(\"aria-label\", `Remove ${v}`);\n x.addEventListener(\"click\", () => { holder.stages = (holder.stages ?? []).filter((o) => o !== v); rebuild(); onChange?.(); });\n chip.append(x);\n wrap.insertBefore(chip, input);\n });\n };\n const commit = (): void => {\n const v = input.value.trim();\n if (v && !(holder.stages ?? []).includes(v)) { (holder.stages ??= []).push(v); rebuild(); onChange?.(); }\n input.value = \"\";\n };\n input.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \",\") { e.preventDefault(); commit(); } });\n input.addEventListener(\"blur\", commit);\n wrap.append(input);\n rebuild();\n return wrap;\n}\n\n// --- drag-to-reorder ---------------------------------------------------------\n// One drag at a time per window, shared across every wired list, so the\n// midpoint test knows what is being carried without a dataTransfer round trip.\nlet reorderDragId: string | null = null;\nconst clearDropMarks = (host: ParentNode): void =>\n host.querySelectorAll(\".drop-before, .drop-after, .drop-into\").forEach((e) => e.classList.remove(\"drop-before\", \"drop-after\", \"drop-into\"));\n\nexport interface ReorderOptions {\n /** Makes `el` a container too (a folder row): its middle half takes a drop\n * INTO it, wearing `.drop-into`, and a drop there calls this instead of\n * `onMove`. Its top and bottom quarters still mean before and after. */\n onInto?: (id: string, targetId: string) => void;\n}\n\n/**\n * Make `el` a drag source and a drop target for HTML5 drag-to-reorder among\n * its siblings. While carried it wears `.dragging`; a hovered target wears\n * `.drop-before` or `.drop-after` by the midpoint test on `axis` (\"y\" for\n * rows, \"x\" for a wrapping grid); a drop calls `onMove(draggedId, before,\n * targetId)` and the caller reorders its model and re-renders.\n *\n * Storyletter's `wireCardDrag`, lifted (ui-review-2026-09, finding 30);\n * Patterpad's scene list uses it too, with `onInto` for its folders. The\n * marks are classes only: the app draws them (Patterpad's `box-shadow: 0 -2px\n * 0 0 var(--accent)` pair is the family's look).\n */\nexport function wireReorder(el: HTMLElement, id: string, axis: \"x\" | \"y\", onMove: (id: string, before: boolean, targetId: string) => void, opts: ReorderOptions = {}): void {\n el.draggable = true;\n el.dataset[\"reorderId\"] = id;\n const host = (): ParentNode => el.parentElement ?? el;\n el.addEventListener(\"dragstart\", (e) => {\n reorderDragId = id; el.classList.add(\"dragging\");\n if (e.dataTransfer) { e.dataTransfer.effectAllowed = \"move\"; e.dataTransfer.setData(\"text/plain\", id); }\n });\n el.addEventListener(\"dragend\", () => { reorderDragId = null; el.classList.remove(\"dragging\"); clearDropMarks(host()); });\n el.addEventListener(\"dragover\", (e) => {\n if (!reorderDragId || reorderDragId === id) return;\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = \"move\";\n const r = el.getBoundingClientRect();\n const at = axis === \"y\" ? (e.clientY - r.top) / (r.height || 1) : (e.clientX - r.left) / (r.width || 1);\n clearDropMarks(host());\n el.classList.add(opts.onInto && at >= 0.25 && at <= 0.75 ? \"drop-into\" : at < 0.5 ? \"drop-before\" : \"drop-after\");\n });\n el.addEventListener(\"dragleave\", () => el.classList.remove(\"drop-before\", \"drop-after\", \"drop-into\"));\n el.addEventListener(\"drop\", (e) => {\n e.preventDefault();\n const into = el.classList.contains(\"drop-into\");\n const before = el.classList.contains(\"drop-before\");\n clearDropMarks(host());\n if (!reorderDragId || reorderDragId === id) return;\n if (into) opts.onInto?.(reorderDragId, id); else onMove(reorderDragId, before, id);\n });\n}\n\n// --- metadata and breadcrumbs ---------------------------------------------------\n// \"Icons are drawn, and so are separators\" (design-language.md section 4,\n// 2026-09-16): metadata is never joined with \"·\" and a breadcrumb is never a\n// string with \"›\" in it. Both apps had been building exactly those strings\n// (ui-review-2026-09, marker A); these two helpers make the same lines as DOM,\n// with the separator drawn by CSS (controls.css) or by the icon vocabulary.\n\n/**\n * A metadata line: `parts` as spans in a flex row, separated by a drawn\n * 3px disc (a `::before` on every part after the first, in controls.css),\n * so `metaLine([\"6 runs\", \"200 max steps\", \"seed 4\"])` is what\n * \"6 runs · 200 max steps · seed 4\" used to be, without the typed dot.\n * Empty, null and undefined parts are skipped, so a caller can pass an\n * optional segment without branching.\n */\nexport function metaLine(parts: (string | Node | null | undefined)[]): HTMLElement {\n const row = el(\"span\", \"shell-meta\");\n for (const part of parts) {\n if (part === null || part === undefined || part === \"\") continue;\n const seg = el(\"span\", \"shell-meta-part\");\n seg.append(part);\n row.append(seg);\n }\n return row;\n}\n\nexport interface Crumb {\n label: string;\n /** Makes the crumb a button. Ignored on the last crumb, which is where the\n * reader already is. */\n onClick?: (event: MouseEvent) => void;\n}\n\n/**\n * A breadcrumb trail: each crumb a span, or a button when it carries\n * `onClick` and is not the last, with `iconNode(\"forward\", 12)` drawn between\n * them. `breadcrumb([\"Box\", \"Deck\", \"Card\"])` is what \"Box › Deck › Card\"\n * used to be; the chevrons are the family's own drawing, not a symbol-font\n * character.\n */\nexport function breadcrumb(parts: (string | Crumb)[]): HTMLElement {\n const trail = el(\"nav\", \"shell-crumbs\");\n trail.setAttribute(\"aria-label\", \"Where this is\");\n parts.forEach((part, i) => {\n const crumb: Crumb = typeof part === \"string\" ? { label: part } : part;\n const last = i === parts.length - 1;\n if (i > 0) {\n const sep = iconNode(\"forward\", 12);\n sep.classList.add(\"shell-crumb-sep\");\n trail.append(sep);\n }\n if (crumb.onClick && !last) {\n const b = el(\"button\", { className: \"shell-crumb shell-crumb-link\", text: crumb.label, onClick: crumb.onClick });\n b.type = \"button\";\n trail.append(b);\n } else {\n const s = el(\"span\", \"shell-crumb\", crumb.label);\n if (last) s.setAttribute(\"aria-current\", \"location\");\n trail.append(s);\n }\n });\n return trail;\n}\n","// ---------------------------------------------------------------------------\n// Typed value controls: how both apps show and set a value of a declared type.\n//\n// The first is the quality's. A quality is a step on a ladder, so its value is\n// shown as the LADDER, with the current rung marked, rather than a dropdown that\n// shows one name and hides where it stands (patterkit design/proposals/\n// patter-qualities.md section 4). Storyletter's Board drew it this way first;\n// it lives here so Storyletter's Fields tab, Patterpad's play panel and Game\n// Data rows, and the play inspectors all draw the same strip.\n//\n// An UNSET value (a field or game-data value the node leaves out) names what it\n// falls back to, \"default (x)\", by the family's one rule: the declaration's own\n// default, else the type's (a quality's first stage). A value that is no longer\n// a stage (a deleted one) is shown as such, so it can be put back on the ladder.\n//\n// No Electron, no app state: this module is DOM only, and is its own entry point\n// (`@wildwinter/app-shell/value-control`) so a play inspector outside the apps\n// can take it.\n// ---------------------------------------------------------------------------\n\nimport { el, tagChips } from \"./dom.js\";\n\n/** A declaration as these controls read it: a property's, a field's or a game-data field's. */\nexport interface ValueDeclLike {\n type: string;\n default?: unknown;\n /** enum / flags */\n values?: readonly string[];\n /** quality: the ladder, in order */\n stages?: readonly string[];\n}\n\n/**\n * What an unset value of `decl` falls back to, as the words a control shows: its own default where it has\n * one, else its type's (False, 0, the first value, no flags, the first stage, empty text). The kernel's\n * `defaultFor`, in words; a host reads the same value from the bundle.\n */\nexport function fallbackText(decl: ValueDeclLike): string {\n const d = decl.default;\n if (d !== undefined && d !== null) {\n if (typeof d === \"boolean\") return d ? \"True\" : \"False\";\n if (Array.isArray(d)) return d.length === 0 ? \"none\" : d.join(\", \");\n return d === \"\" ? \"none\" : String(d);\n }\n switch (decl.type) {\n case \"boolean\": return \"False\";\n case \"number\": return \"0\";\n case \"enum\": return decl.values?.[0] ?? \"none\";\n case \"quality\": return decl.stages?.[0] ?? \"first stage\";\n case \"flags\": return \"none\";\n default: return \"empty\";\n }\n}\n\n/** The label an unset value shows: \"default (x)\", naming what it falls back to. */\nexport const defaultLabel = (decl: ValueDeclLike): string => `default (${fallbackText(decl)})`;\n\n/**\n * The quiet line under a ladder that has just been reordered: how many gates order against it. A reorder\n * changes no value, and is the one edit that changes what a gate MEANS without saying so (patter-qualities.md\n * section 2), so the editor says so. Empty when no gate orders the quality.\n */\nexport function stageGateNote(gates: number): string {\n if (gates <= 0) return \"\";\n return gates === 1\n ? \"1 gate compares against this ladder, and it now reads the new order.\"\n : `${gates} gates compare against this ladder, and each now reads the new order.`;\n}\n\nexport interface RungStripOptions {\n /** The ladder, in order. */\n stages: readonly string[];\n /** The value held, or undefined when unset: a field or game-data value the node leaves out. */\n value: string | undefined;\n /** The declaration, for what an unset value falls back to (its default, else the first stage). */\n decl?: ValueDeclLike;\n /** Pick a stage. Absent: the strip is read-only. */\n onPick?: (stage: string) => void;\n /** Clear back to unset. With it, the strip leads with \"default (x)\", which clears the value. */\n onClear?: () => void;\n /** The rollover on a rung that is not the current one (default: `Set to \"x\"`). */\n pickTip?: (stage: string) => string;\n /**\n * The document to build in, for a host outside the apps (a play inspector in a page or a frame of its\n * own). Given, rollovers are plain `title`s and the family's themed tooltips are not mounted; the host\n * styles `.seg`, `.seg-opt` and `.shell-rung` itself. Absent: the apps' document and themed rollovers.\n */\n doc?: Document;\n}\n\n/**\n * A quality's value as its ladder: one rung per stage, in order, the current one marked (`on`). Unset, the\n * rung it falls back to is marked as the fallback (`fallback`), and with `onClear` a leading \"default (x)\"\n * option is the current choice. A value that is not a stage (a deleted one) is shown after the ladder,\n * marked `stray`, until a rung is picked.\n */\nexport function rungStrip(opts: RungStripOptions): HTMLElement {\n const make = opts.doc === undefined ? el : (tag: \"span\" | \"button\", props: { className: string; text?: string; tip?: string; onClick?: () => void }) => {\n const node = opts.doc!.createElement(tag);\n node.className = props.className;\n if (props.text !== undefined) node.textContent = props.text;\n if (props.tip !== undefined) { node.title = props.tip; node.setAttribute(\"aria-label\", props.tip); }\n if (props.onClick) node.addEventListener(\"click\", props.onClick);\n if (tag === \"button\") (node as HTMLButtonElement).type = \"button\";\n return node;\n };\n const strip = make(\"span\", { className: \"seg shell-rungs\" });\n const decl: ValueDeclLike = opts.decl ?? { type: \"quality\", stages: opts.stages };\n const unset = opts.value === undefined;\n const fallback = typeof decl.default === \"string\" && opts.stages.includes(decl.default) ? decl.default : opts.stages[0];\n const readOnly = opts.onPick === undefined;\n if (opts.onClear) {\n strip.append(make(\"button\", {\n className: `seg-opt shell-rung shell-rung-default${unset ? \" on\" : \"\"}`, text: defaultLabel(decl),\n tip: unset ? \"Not set here: the declared default holds\" : \"Clear the value, back to the default\",\n onClick: () => { if (!unset) opts.onClear!(); },\n }));\n }\n for (const stage of opts.stages) {\n const current = opts.value === stage;\n const falls = unset && stage === fallback;\n const rung = make(readOnly ? \"span\" : \"button\", {\n className: `seg-opt shell-rung${current ? \" on\" : \"\"}${falls ? \" fallback\" : \"\"}`, text: stage,\n tip: current ? \"The current stage\" : falls ? \"The stage it falls back to\" : readOnly ? stage : (opts.pickTip?.(stage) ?? `Set to \"${stage}\"`),\n ...(readOnly ? {} : { onClick: () => { if (!current) opts.onPick!(stage); } }),\n });\n strip.append(rung);\n }\n if (!unset && !opts.stages.includes(opts.value!)) {\n strip.append(make(\"span\", {\n className: \"seg-opt shell-rung stray on\", text: opts.value!,\n tip: `\"${opts.value}\" is not one of the stages: pick the one it belongs at now`,\n }));\n }\n return strip;\n}\n\n// ---------------------------------------------------------------------------\n// The field row: ONE control for a node's value of a declared field, driven by\n// the declared type, for the family's seven types (text, multiline, number,\n// boolean, enum, flags, quality). Storyletter's card and outcome Fields and\n// Patterpad's Game Data rows both draw it, which settles the disagreements the\n// parity review found (patterkit design/family-parity-2026-10.md item 10):\n//\n// - the declared purpose is shown, under the control, not hidden in a rollover;\n// - unset says what it falls back to, \"default (x)\", by `defaultLabel`;\n// - flags are a picker of the declared values, not comma-separated text;\n// - multiline is a text area;\n// - a value the declaration refuses (not a number, not one of the values, not\n// a stage) is said under the control, and the app may add its own message;\n// - text commits as typed, and Escape puts back what the field held when it\n// took focus (the family's field rule).\n//\n// Committing as typed means a host that redraws on every change (Patterpad's\n// inspector rebuilds its whole stack) would drop the focus at every keystroke.\n// `fieldRows()` is the answer: it hands back the SAME element for the same key\n// across a redraw, updated in place, and gives the focus (and the caret) back\n// to it once the redraw has put it in the page again.\n// ---------------------------------------------------------------------------\n\n/** A value a field row commits: a flags value is the list of names set. */\nexport type FieldValue = string | number | boolean | string[];\n\n/** A field's declaration as the row reads it: a card field's, an outcome field's or a game-data field's. */\nexport interface FieldDeclLike extends ValueDeclLike {\n name: string;\n /** What the field is for, shown under the control. */\n purpose?: string;\n /** A sentence about each value (an enum's values, the flags, a quality's stages, a pick's fixed words), as an\n * extension's manifest gives them: the chosen value's is shown under the control, and each chosen flag's. */\n valueHelp?: Readonly<Record<string, string>>;\n}\n\n/**\n * The help for what `decl` holds: the chosen enum value's, stage's or pick word's (the one it falls back to, when\n * unset), or each chosen flag's, \"name: sentence\" a line each. Empty when the declaration describes none of them.\n */\nexport function valueHelpText(decl: FieldDeclLike, value: unknown): string {\n const help = decl.valueHelp;\n if (help === undefined) return \"\";\n const said = (v: string): string | undefined => (Object.prototype.hasOwnProperty.call(help, v) ? help[v] : undefined);\n if (decl.type === \"enum\" || decl.type === \"quality\") {\n const held = typeof value === \"string\" && value !== \"\" ? value\n : typeof decl.default === \"string\" && decl.default !== \"\" ? decl.default\n : decl.type === \"enum\" ? decl.values?.[0] : decl.stages?.[0];\n return held === undefined ? \"\" : said(held) ?? \"\";\n }\n if (decl.type === \"flags\") {\n const held = Array.isArray(value) ? value.map(String) : Array.isArray(decl.default) ? decl.default.map(String) : [];\n return held.flatMap((v) => { const h = said(v); return h === undefined ? [] : [`${v}: ${h}`]; }).join(\"\\n\");\n }\n if (decl.type === \"pick\") {\n // A pick's help describes its fixed words only; empty is automatic, so nothing is chosen to describe.\n const held = typeof value === \"string\" && value !== \"\" ? value : typeof decl.default === \"string\" ? decl.default : \"\";\n return held === \"\" ? \"\" : said(held) ?? \"\";\n }\n return \"\";\n}\n\nexport interface FieldRowOptions {\n decl: FieldDeclLike;\n /** The node's own value, typed (a flags value is a string list), or undefined where the node sets none. */\n value: unknown;\n /** Commit a value; `undefined` clears it, back to the default. Absent: the row is read-only. */\n onCommit?: (value: FieldValue | undefined) => void;\n /** Drawn but not editable, as when there is no node to write to. */\n disabled?: boolean;\n /** A message of the app's own about this value (a check the row cannot make), shown under the control. */\n problem?: string;\n /** The app's own sentence INSTEAD of the row's (a renamed stage said better than \"not one of the stages\").\n * Given, it is the whole message; `problem` is ignored. */\n replaceProblem?: string;\n /** The caption; default the field's name. */\n label?: string | Node;\n /** Added to the row and its caption, so a row sits in its app's layout. */\n className?: string;\n labelClassName?: string;\n /** The app's class for a text field or select, used instead of the shell's `.field`. */\n controlClassName?: string;\n}\n\nexport interface FieldRowHandle {\n /** The row: its caption, then the control with the purpose and any message under it. */\n readonly el: HTMLElement;\n /** Redraw for a new value, declaration or message, keeping the element. A field being typed in keeps its\n * text: what the author is typing is newer than what the app last stored. */\n update(opts: FieldRowOptions): void;\n}\n\n/** The type's fallback as a value, for a flags picker to start from when the node sets none. */\nconst fallbackFlags = (decl: ValueDeclLike): string[] => (Array.isArray(decl.default) ? decl.default.filter((v): v is string => typeof v === \"string\") : []);\n\n/** What the row says about a value the declaration refuses, or \"\" when it fits. */\nexport function fieldValueProblem(decl: ValueDeclLike, value: unknown): string {\n if (value === undefined || value === null || value === \"\") return \"\";\n switch (decl.type) {\n case \"number\":\n return typeof value === \"number\" && Number.isFinite(value) ? \"\" : \"Not a number.\";\n case \"boolean\":\n return typeof value === \"boolean\" ? \"\" : \"Not True or False.\";\n case \"enum\":\n return decl.values && !decl.values.includes(String(value)) ? `\"${String(value)}\" is not one of the values.` : \"\";\n case \"quality\":\n return decl.stages && !decl.stages.includes(String(value)) ? `\"${String(value)}\" is not one of the stages.` : \"\";\n case \"flags\": {\n if (!Array.isArray(value)) return \"Not a list of flags.\";\n const strays = decl.values ? value.filter((v) => !decl.values!.includes(String(v))) : [];\n if (strays.length === 0) return \"\";\n return strays.length === 1 ? `\"${String(strays[0])}\" is not one of the values.` : `${strays.map((s) => `\"${String(s)}\"`).join(\", \")} are not among the values.`;\n }\n default:\n return \"\";\n }\n}\n\nconst isTextual = (type: string): boolean => type !== \"boolean\" && type !== \"enum\" && type !== \"flags\" && type !== \"quality\";\n\n/** Everything that decides which control is drawn: a change here rebuilds it, anything else updates it. */\nconst controlShape = (o: FieldRowOptions): string => JSON.stringify([\n o.decl.type, o.decl.values ?? null, o.decl.stages ?? null, o.decl.default ?? null,\n o.onCommit === undefined, o.disabled === true, o.controlClassName ?? \"\",\n o.decl.type === \"quality\" && (o.decl.stages?.length ?? 0) === 0,\n]);\n\n/**\n * One declared field's value as a row: the caption, then the control the declared type calls for, with the\n * purpose and any message under it. See the section comment above for the rules it carries.\n */\nexport function fieldRow(initial: FieldRowOptions): FieldRowHandle {\n let opts = initial;\n const row = el(\"div\", `shell-fieldrow${opts.className ? ` ${opts.className}` : \"\"}`);\n const caption = el(\"span\", `shell-fieldrow-label${opts.labelClassName ? ` ${opts.labelClassName}` : \"\"}`);\n caption.id = `shell-fr-${Math.random().toString(36).slice(2, 9)}`;\n const body = el(\"div\", \"shell-fieldrow-body\");\n const purpose = el(\"p\", \"shell-fieldrow-purpose\");\n const valueHelp = el(\"p\", \"shell-fieldrow-purpose shell-fieldrow-valuehelp\");\n const problem = el(\"p\", \"shell-fieldrow-problem\");\n problem.setAttribute(\"role\", \"status\");\n row.append(caption, body);\n\n let shape = \"\";\n /** Redraw the control for the current value; set by whichever control is built. */\n let show: (value: unknown) => void = () => {};\n /** A local problem the control found while typing (a number that is not one yet). */\n let typing = \"\";\n\n const commit = (v: FieldValue | undefined): void => { opts.onCommit?.(v); };\n const editable = (): boolean => opts.onCommit !== undefined && opts.disabled !== true;\n // An app's own field class replaces the shell's `.field`, so its controls look like its other fields.\n const ctl = (base: string): string => (opts.controlClassName ? `${base.replace(/^field /, \"\")} ${opts.controlClassName}` : base);\n const labelledBy = (node: HTMLElement): void => {\n node.setAttribute(\"aria-labelledby\", caption.id);\n const described = [purpose.id, problem.id].filter(Boolean).join(\" \");\n if (described) node.setAttribute(\"aria-describedby\", described);\n };\n purpose.id = `${caption.id}-p`; problem.id = `${caption.id}-m`;\n\n const sayProblem = (): void => {\n // The chosen value's own help travels with every change of value, as the message does.\n const help = valueHelpText(opts.decl, opts.value);\n valueHelp.textContent = help;\n valueHelp.hidden = help === \"\";\n const words = opts.replaceProblem !== undefined\n ? [opts.replaceProblem].filter((w) => w !== \"\")\n : [typing || fieldValueProblem(opts.decl, opts.value), opts.problem ?? \"\"].filter((w) => w !== \"\");\n problem.textContent = words.join(\" \");\n problem.hidden = words.length === 0;\n row.classList.toggle(\"has-problem\", words.length > 0);\n row.classList.toggle(\"shell-fieldrow-textual\", isTextual(opts.decl.type));\n };\n\n const build = (): HTMLElement => {\n const decl = opts.decl;\n typing = \"\";\n if (decl.type === \"quality\" && (decl.stages?.length ?? 0) > 0) {\n const holder = el(\"span\", \"shell-fieldrow-rungs\");\n show = (value) => {\n holder.replaceChildren(rungStrip({\n stages: decl.stages!, value: typeof value === \"string\" && value !== \"\" ? value : undefined, decl,\n ...(editable() ? { onPick: (stage: string) => { opts = { ...opts, value: stage }; show(stage); sayProblem(); commit(stage); }, onClear: () => { opts = { ...opts, value: undefined }; show(undefined); sayProblem(); commit(undefined); } } : {}),\n }));\n const strip = holder.firstElementChild as HTMLElement | null;\n if (strip) { strip.setAttribute(\"role\", \"group\"); labelledBy(strip); }\n };\n return holder;\n }\n if (decl.type === \"boolean\" || decl.type === \"enum\") {\n const sel = el(\"select\", ctl(\"field shell-fieldrow-select\"));\n labelledBy(sel);\n sel.disabled = !editable();\n const choices: Array<[string, string]> = decl.type === \"boolean\"\n ? [[\"true\", \"True\"], [\"false\", \"False\"]]\n : (decl.values ?? []).map((v): [string, string] => [v, v]);\n show = (value) => {\n const current = value === undefined || value === null || value === \"\" ? \"\" : String(value);\n sel.replaceChildren();\n const add = (v: string, label: string): void => { const o = el(\"option\", undefined, label); o.value = v; sel.append(o); };\n add(\"\", defaultLabel(decl));\n for (const [v, label] of choices) add(v, label);\n // A value the declaration no longer offers stays visible, so the author sees what is held.\n if (current !== \"\" && !choices.some(([v]) => v === current)) add(current, current);\n sel.value = current;\n };\n sel.addEventListener(\"change\", () => {\n const raw = sel.value;\n const v: FieldValue | undefined = raw === \"\" ? undefined : decl.type === \"boolean\" ? raw === \"true\" : raw;\n opts = { ...opts, value: v }; sayProblem(); commit(v);\n });\n return sel;\n }\n if (decl.type === \"flags\" && (decl.values?.length ?? 0) > 0) {\n const holder = el(\"span\", \"shell-flags\");\n holder.setAttribute(\"role\", \"group\");\n labelledBy(holder);\n show = (value) => {\n const unset = !Array.isArray(value);\n const held = unset ? fallbackFlags(decl) : (value as unknown[]).map(String);\n holder.replaceChildren();\n const chip = (text: string, cls: string, pressed: boolean | undefined, tip: string, onClick?: () => void): void => {\n const b = el(onClick && editable() ? \"button\" : \"span\", { className: `shell-flag${cls}`, text, tip });\n b.setAttribute(\"aria-label\", `${text}: ${tip}`); // the tip alone would hide the value's name\n if (b instanceof HTMLButtonElement) { b.type = \"button\"; b.addEventListener(\"click\", onClick!); }\n if (pressed !== undefined) b.setAttribute(\"aria-pressed\", String(pressed));\n holder.append(b);\n };\n const set = (next: string[] | undefined): void => { opts = { ...opts, value: next }; show(next); sayProblem(); commit(next); };\n chip(defaultLabel(decl), ` shell-flag-default${unset ? \" on\" : \"\"}`, undefined,\n unset ? \"Not set here: the declared default holds\" : \"Clear the value, back to the default\",\n unset ? undefined : () => set(undefined));\n for (const v of decl.values!) {\n const on = held.includes(v);\n chip(v, `${on ? \" on\" : \"\"}${on && unset ? \" fallback\" : \"\"}`, on, on ? `Unset ${v}` : `Set ${v}`,\n () => set(on ? held.filter((x) => x !== v) : decl.values!.filter((x) => x === v || held.includes(x)).concat(held.filter((x) => !decl.values!.includes(x)))));\n }\n // A held value the declaration does not offer: shown, struck, and a click drops it.\n for (const v of held.filter((x) => !decl.values!.includes(x))) {\n chip(v, \" stray on\", true, `\"${v}\" is not one of the values: click to drop it`, () => set(held.filter((x) => x !== v)));\n }\n };\n return holder;\n }\n if (decl.type === \"flags\") {\n // No values declared to pick from: the shell's chip editor, each name typed.\n const holder = el(\"span\", \"shell-fieldrow-chips\");\n show = (value) => {\n const box = { values: Array.isArray(value) ? (value as unknown[]).map(String) : [] };\n const chips = tagChips(box, () => {\n const next = box.values.length > 0 ? [...box.values] : undefined;\n opts = { ...opts, value: next }; sayProblem(); commit(next);\n });\n const input = chips.querySelector(\"input\");\n if (input) { labelledBy(input); input.placeholder = Array.isArray(value) ? \"Add value\" : defaultLabel(decl); input.disabled = !editable(); }\n chips.querySelectorAll(\"button\").forEach((b) => { (b as HTMLButtonElement).disabled = !editable(); });\n holder.replaceChildren(chips);\n };\n return holder;\n }\n // text, multiline, number, and any type the row does not know: typed, committed as typed.\n const multiline = decl.type === \"multiline\";\n const field = multiline ? el(\"textarea\", ctl(\"field shell-fieldrow-text\")) : el(\"input\", ctl(\"field shell-fieldrow-text\"));\n if (field instanceof HTMLInputElement) {\n field.type = \"text\";\n if (decl.type === \"number\") field.inputMode = \"decimal\";\n } else field.rows = 3;\n labelledBy(field);\n field.placeholder = defaultLabel(decl);\n field.disabled = !editable();\n const asText = (value: unknown): string =>\n value === undefined || value === null ? \"\" : Array.isArray(value) ? value.join(\", \") : String(value);\n const read = (raw: string): { value?: FieldValue; bad?: string } => {\n if (raw.trim() === \"\") return {};\n if (decl.type !== \"number\") return { value: raw };\n const n = Number(raw.trim());\n return Number.isFinite(n) ? { value: n } : { bad: \"Not a number.\" };\n };\n let atFocus = \"\";\n show = (value) => {\n // What the author is typing is newer than the stored value, so a focused field keeps its text.\n // So does one the author was in when a redraw took the row out of the page, until they leave it.\n if (field.ownerDocument.activeElement === field || keeper.restoring(field) || keeper.holds(row)) return;\n field.value = asText(value);\n };\n const take = (): void => {\n const r = read(field.value);\n typing = r.bad ?? \"\";\n if (r.bad === undefined) opts = { ...opts, value: r.value };\n sayProblem();\n if (r.bad === undefined) commit(r.value);\n };\n field.addEventListener(\"focus\", () => { if (!keeper.restoring(field)) atFocus = field.value; });\n field.addEventListener(\"input\", take);\n field.addEventListener(\"keydown\", (e) => {\n if ((e as KeyboardEvent).key !== \"Escape\" || field.value === atFocus) return;\n field.value = atFocus;\n take();\n });\n return field;\n };\n\n const draw = (next: FieldRowOptions): void => {\n const decl = next.decl;\n const label = next.label ?? decl.name;\n if (typeof label === \"string\") caption.textContent = label; else caption.replaceChildren(label);\n caption.className = `shell-fieldrow-label${next.labelClassName ? ` ${next.labelClassName}` : \"\"}`;\n row.className = `shell-fieldrow${next.className ? ` ${next.className}` : \"\"}`;\n const nextShape = controlShape(next);\n opts = next;\n if (nextShape !== shape) {\n shape = nextShape;\n body.replaceChildren(build());\n }\n show(next.value);\n purpose.textContent = decl.purpose ?? \"\";\n if (decl.purpose) body.append(purpose); else purpose.remove();\n body.append(valueHelp, problem);\n sayProblem();\n };\n\n draw(initial);\n keeper.watch(row);\n return { el: row, update: draw };\n}\n\n// --- keeping the focus across a redraw -------------------------------------\n//\n// A host that rebuilds its view takes the row out of the page and puts it back.\n// Taking a focused element out drops its focus, which is the same thing as\n// the author losing their place mid-word. So a row remembers whether the\n// author was in it, and where the caret was, and the cache gives both back\n// once the row is in the page again (a microtask: after the host's redraw).\n\ninterface FocusMemo { field: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement; start: number | null; end: number | null }\n\nconst keeper = (() => {\n const memos = new WeakMap<HTMLElement, FocusMemo>();\n const restoringNow = new WeakSet<Element>();\n /** When each row was last handed back by the cache, to tell a move's late blur from the author's. */\n const redrawn = new WeakMap<HTMLElement, number>();\n const put = (row: HTMLElement): void => {\n const memo = memos.get(row);\n if (!memo) return;\n const doc = row.ownerDocument;\n const active = doc.activeElement;\n if (!row.isConnected || !memo.field.isConnected || !row.contains(memo.field)) return;\n if (active && active !== doc.body && !row.contains(active)) { memos.delete(row); return; }\n if (active === memo.field && memo.field.matches(\":focus\")) return;\n restoringNow.add(memo.field);\n try {\n memo.field.focus({ preventScroll: true });\n if (!(memo.field instanceof HTMLSelectElement) && memo.start !== null) memo.field.setSelectionRange(memo.start, memo.end ?? memo.start);\n } finally { restoringNow.delete(memo.field); }\n };\n return {\n watch(row: HTMLElement): void {\n const note = (e: Event): void => {\n const t = e.target;\n if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement)) return;\n const text = !(t instanceof HTMLSelectElement);\n memos.set(row, { field: t, start: text ? (t as HTMLInputElement).selectionStart : null, end: text ? (t as HTMLInputElement).selectionEnd : null });\n };\n row.addEventListener(\"focusin\", note);\n row.addEventListener(\"input\", note);\n row.addEventListener(\"keyup\", note);\n row.addEventListener(\"mouseup\", note);\n // A blur the author made (the row still in the page) forgets; one caused by the row leaving the page\n // does not, and is what the cache puts back. Chromium drops the focus of a moved element LATE: the\n // blur arrives after the redraw, the row already back in the page, so a blur to nowhere just after a\n // redraw is the move's, and the focus goes straight back.\n // Chromium may also fire that blur synchronously, as the host clears its view and before the cache\n // has handed the row back: so the decision waits for the microtask, by which time a row the redraw\n // asked for again is marked as redrawn since the blur.\n row.addEventListener(\"focusout\", (e) => {\n const at = performance.now();\n const late = (e as FocusEvent).relatedTarget === null && at - (redrawn.get(row) ?? -Infinity) < 250;\n if (late && memos.has(row)) { queueMicrotask(() => put(row)); return; }\n queueMicrotask(() => {\n if ((redrawn.get(row) ?? -Infinity) >= at) { put(row); return; }\n if (row.isConnected && !row.contains(row.ownerDocument.activeElement)) memos.delete(row);\n });\n });\n },\n /** Give the focus back to the row, if the author was in it and has not gone anywhere else since. */\n restore(row: HTMLElement): void {\n if (!memos.has(row)) return;\n redrawn.set(row, performance.now());\n // At the microtask, not now: the event that caused the redraw may still note the caret.\n queueMicrotask(() => put(row));\n },\n restoring: (field: Element): boolean => restoringNow.has(field),\n /** Forget the row's focus if the author has since gone somewhere else (before the row is redrawn). */\n settle(row: HTMLElement): void {\n const active = row.ownerDocument.activeElement;\n if (active && active !== row.ownerDocument.body && !row.contains(active)) memos.delete(row);\n },\n /** The row the author is in (before a redraw takes it out). */\n holds: (row: HTMLElement): boolean => memos.has(row),\n };\n})();\n\n/**\n * The focus keeping `fieldRows` does, for a host (or a structured row) that keeps row elements itself: call\n * `settle` before it updates a row and `restore` once the row is back in the page.\n */\nexport const fieldRowFocus = {\n settle: (row: HTMLElement): void => keeper.settle(row),\n restore: (row: HTMLElement): void => keeper.restore(row),\n};\n\nexport interface FieldRows {\n /**\n * The row for `key` (a node and a field, say `${nodeId}:${field}`): the same element as last time when\n * there was one, updated for these options, else a new one. A row the author was typing in gets its focus\n * and caret back once the redraw has put it in the page.\n */\n row(key: string, opts: FieldRowOptions): HTMLElement;\n /** Forget every row not asked for since the last sweep (call at the end of a redraw). */\n sweep(): void;\n}\n\n/** A cache of field rows for a host that redraws its whole view on every change. */\nexport function fieldRows(): FieldRows {\n const rows = new Map<string, { handle: FieldRowHandle; used: boolean }>();\n return {\n row(key, opts) {\n const had = rows.get(key);\n if (had) {\n had.used = true;\n keeper.settle(had.handle.el);\n had.handle.update(opts);\n keeper.restore(had.handle.el);\n return had.handle.el;\n }\n const handle = fieldRow(opts);\n rows.set(key, { handle, used: true });\n return handle.el;\n },\n sweep() {\n for (const [key, entry] of rows) { if (!entry.used) rows.delete(key); else entry.used = false; }\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// The property examiner/editor, JS idiom: a self-styled DOM panel (the\n// parity member Unity renders as an EditorWindow, Unreal as a Slate tab,\n// Godot as an in-game panel). Rows come from live().listProperties() and\n// are built once (declared properties are fixed for a bundle); values\n// refresh on a poll that SKIPS the focused widget; every row has a\n// reset-to-default that disables itself at the default. Edits commit via\n// flow.setProperty, which is a silent host write under the firing rule.\n// Save state / Load state carry the whole run over the .storyletsave string\n// boundary (save.ts); a filter narrows the property rows; the read-only\n// turns and board sections mirror the engine examiners (design 2.4).\n// The JS game runs in-process, so the engine and a flow are passed directly\n// (no debug registry needed here).\n//\n// The log panel (design 2.3: the flow's retained log surfaced in every\n// examiner; the old port's Unreal log panel is the high-water mark): the\n// lines of live().log() behind per-kind filters (a peek files under Deal -\n// both are asks), with Autoscroll, Copy and Clear. Empty until the engine\n// is created with the log option.\n// ---------------------------------------------------------------------------\n/// <reference lib=\"dom\" />\n\nimport { rungStrip } from \"@wildwinter/app-shell/value-control\";\nimport type { Engine, EngineLogEntry, Flow, LogEntry, PropertyRow } from \"@storylet-studio/runtime\";\nimport type { ScalarValue } from \"@storylet-studio/model\";\nimport { serializeState, deserializeState } from \"./save.js\";\n\nexport interface PropertyInspectorOptions {\n /** Mount point; defaults to document.body. */\n container?: HTMLElement;\n title?: string;\n /** Value-refresh poll; 0 disables polling. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n el: HTMLElement;\n refresh(): void;\n destroy(): void;\n}\n\nconst STYLE_ID = \"sl-inspector-style\";\nconst CSS = `\n.sl-insp { font: 12px system-ui, sans-serif; color: var(--ink, #222); background: var(--surface, #fafafa);\n border: 1px solid var(--line, #ccc); border-radius: 8px; padding: 10px 12px; max-width: 26rem; }\n.sl-insp h3 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--muted, #666); }\n.sl-insp .sl-head { display: flex; align-items: baseline; gap: 6px; }\n.sl-insp .sl-head h3 { flex: 1; }\n.sl-insp .sl-save, .sl-insp .sl-load { font: inherit; font-size: 11px; padding: 1px 6px; cursor: pointer; }\n.sl-insp .sl-filter { display: block; width: 100%; box-sizing: border-box; margin: 0 0 6px; }\n.sl-insp .sl-group { margin: 8px 0 2px; font-weight: 600; font-size: 11px; color: var(--muted, #666); }\n.sl-insp .sl-section { margin: 10px 0 2px; font-weight: 600; font-size: 11px; text-transform: uppercase;\n letter-spacing: 0.06em; color: var(--muted, #666); }\n.sl-insp .sl-line { padding: 1px 0; }\n.sl-insp .sl-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; }\n.sl-insp .sl-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.sl-insp input[type=\"text\"], .sl-insp input[type=\"number\"], .sl-insp select {\n font: inherit; width: 9rem; padding: 1px 4px; }\n.sl-insp .sl-reset { border: 0; background: none; cursor: pointer; color: var(--muted, #666); }\n.sl-insp .sl-reset:disabled { opacity: 0.3; cursor: default; }\n.sl-insp .sl-logbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 2px 0; }\n.sl-insp .sl-logbar label { display: inline-flex; align-items: center; gap: 2px; }\n.sl-insp .sl-logbar button { font: inherit; font-size: 11px; padding: 1px 6px; cursor: pointer; }\n.sl-insp .sl-log { font-family: ui-monospace, monospace; font-size: 11px; max-height: 12rem;\n overflow: auto; white-space: pre; border: 1px solid var(--line, #ccc); padding: 4px 6px; }\n.sl-insp details.sl-fold > summary { cursor: pointer; margin: 10px 0 2px; font-weight: 600;\n font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted, #666); }\n.sl-insp .sl-ident { font-family: ui-monospace, monospace; font-size: 11px; }\n.sl-insp .sl-ident b { font-weight: 600; }\n.sl-insp .sl-note { color: var(--muted, #666); }\n.sl-insp .seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--line, #ccc); border-radius: 4px; overflow: hidden; }\n.sl-insp .seg-opt { background: none; border: 0; cursor: pointer; color: var(--muted, #666);\n font: 400 11px ui-monospace, monospace; padding: 1px 6px; }\n.sl-insp .seg-opt + .seg-opt { border-left: 1px solid var(--line, #ccc); }\n.sl-insp .seg-opt.on { color: var(--ink, #222); font-weight: 600; background: var(--accent-soft, #e6eef0); cursor: default; }\n.sl-insp .shell-rung.stray { color: #9a3b2e; text-decoration: line-through; }\n`;\n\n/** Inject the shared panel stylesheet once. Exported so the bundle inspector\n * (bundle-inspector.ts) renders in the same CSS grammar. */\nexport function ensureInspectorStyle(): void {\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.append(style);\n}\n\nconst eq = (a: ScalarValue | undefined, b: ScalarValue | undefined): boolean =>\n JSON.stringify(a) === JSON.stringify(b);\n\n// --- the log panel (design 2.3) ---------------------------------------------\n\n/** The filterable kinds; a peek files under \"deal\" (both are asks). */\nconst LOG_KINDS = [\"deal\", \"play\", \"write\", \"evict\", \"turns\", \"diagnostic\"] as const;\nconst LOG_KIND_LABELS: Record<(typeof LOG_KINDS)[number], string> = {\n deal: \"Deal\", play: \"Play\", write: \"Write\", evict: \"Evict\", turns: \"Turns\", diagnostic: \"Diag\",\n};\n\nconst logKindOf = (e: LogEntry): (typeof LOG_KINDS)[number] =>\n e.type === \"peek\" ? \"deal\" : e.type === \"discard\" ? \"play\" : e.type;\n\nconst showVal = (v: ScalarValue | undefined): string =>\n v === undefined ? \"<unset>\" : JSON.stringify(v);\n\n/** One line per entry, `[turn]`-stamped where the event has a box context\n * (write lines share the state logger's `path: from -> to` reading). */\nexport function formatLogEntry(e: LogEntry | EngineLogEntry): string {\n // The run's log names the flow that acted, after the turn stamp and in the\n // same place all four examiners put it; a flow's own log omits it, because\n // its section heading already says whose it is.\n return formatLogBody(e, \"flow\" in e && e.flow ? `${e.flow} ` : \"\");\n}\n\nfunction formatLogBody(e: LogEntry, flow: string): string {\n const stamp = (e.turn !== undefined ? `[${e.turn}] ` : \"[-] \") + flow;\n switch (e.type) {\n case \"deal\": {\n const dealt = e.cards.filter((c) => c.verdict === \"dealt\").map((c) => c.id);\n return `${stamp}deal ${e.hand}: ${dealt.length > 0 ? dealt.join(\", \") : \"(none)\"} (${e.cards.length} considered)`;\n }\n case \"peek\": {\n const crit = Object.entries(e.criteria).map(([g, t]) => `${g}=${t}`).join(\", \");\n const listed = e.cards.filter((c) => c.verdict === \"dealt\").map((c) => c.id);\n return `${stamp}peek ${e.box}${crit ? ` [${crit}]` : \"\"}: `\n + `${listed.length > 0 ? listed.join(\", \") : \"(none)\"} (${e.cards.length} considered)`;\n }\n case \"evict\": return `${stamp}evict ${e.card} from ${e.hand} (${e.reason})`;\n // A card played with none (\"\") has no outcome to name.\n case \"play\": return `${stamp}play ${e.card}${e.outcome === \"\" ? \"\" : ` -> ${e.outcome}`}`;\n case \"write\": return `${stamp}write ${e.path}: ${showVal(e.prev)} -> ${showVal(e.value)}`;\n case \"turns\": return `${stamp}turns ${e.box} -> ${e.turn}`;\n // A card the game took out of its hand unplayed: filed with the plays.\n case \"discard\": return `${stamp}discard ${e.card} from ${e.hand}`;\n default: return `${stamp}diagnostic ${e.where}: ${e.message}`;\n }\n}\n\n/** The group label a row files under (\"world\", \"story\", \"box <id>\", ...). */\nconst groupOf = (path: string): string => {\n const parts = path.split(\".\");\n return parts.length === 3 ? `${parts[0]} ${parts[1]}` : parts[0]!;\n};\n\nexport function createPropertyInspector(engine: Engine, flow: Flow, opts: PropertyInspectorOptions = {}): PropertyInspector {\n ensureInspectorStyle();\n\n // loadGame rebuilds every flow and the handle we were given goes inert\n // (the runtime's stale-handle rule), so every read goes through this\n // accessor and Load state re-takes the same-named flow.\n let liveFlow = flow;\n const live = (): Flow => liveFlow;\n\n const el = document.createElement(\"div\");\n el.className = \"sl-insp\";\n\n // Header: the title plus Save state / Load state (the .storyletsave\n // string boundary, in every examiner - the parity rule, design 2.4).\n const head = document.createElement(\"div\");\n head.className = \"sl-head\";\n const h = document.createElement(\"h3\");\n h.textContent = opts.title ?? \"Runtime state\";\n head.append(h);\n\n const saveBtn = document.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.className = \"sl-save\";\n saveBtn.textContent = \"Save state\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = \"save.storyletsave\";\n a.click();\n URL.revokeObjectURL(url);\n });\n head.append(saveBtn);\n\n const filePicker = document.createElement(\"input\");\n filePicker.type = \"file\";\n filePicker.accept = \".storyletsave,application/json\";\n filePicker.hidden = true;\n filePicker.addEventListener(\"change\", () => {\n const file = filePicker.files?.[0];\n if (!file) return;\n const reader = new FileReader();\n reader.onload = () => {\n // A foreign or malformed blob is refused by deserializeState, never applied.\n try {\n deserializeState(engine, String(reader.result));\n liveFlow = engine.getFlow(flow.id) ?? engine.openFlow(flow.id);\n refresh();\n } catch (e) {\n console.error(\"storylets inspector: load failed:\", e instanceof Error ? e.message : e);\n }\n filePicker.value = \"\";\n };\n reader.readAsText(file);\n });\n\n const loadBtn = document.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.className = \"sl-load\";\n loadBtn.textContent = \"Load state\";\n loadBtn.addEventListener(\"click\", () => filePicker.click());\n head.append(loadBtn, filePicker);\n el.append(head);\n\n // The property filter (name/path substring, case-blind - the parity\n // member Unreal renders as an SSearchBox).\n const filter = document.createElement(\"input\");\n filter.type = \"text\";\n filter.className = \"sl-filter\";\n filter.placeholder = \"Filter properties\";\n el.append(filter);\n\n // Rows are built once: the declared surface is fixed for a bundle. The\n // filter only toggles visibility (a group hides with its last row).\n const editors: { row: PropertyRow; read: () => void }[] = [];\n const groups: { el: HTMLElement; rows: { el: HTMLElement; text: string }[] }[] = [];\n let lastGroup = \"\";\n for (const row of live().listProperties()) {\n const group = groupOf(row.path);\n if (group !== lastGroup) {\n const g = document.createElement(\"div\");\n g.className = \"sl-group\";\n g.textContent = group;\n el.append(g);\n groups.push({ el: g, rows: [] });\n lastGroup = group;\n }\n const rowEl = buildRow(live, row, editors);\n groups[groups.length - 1]!.rows.push({ el: rowEl, text: `${row.name} ${row.path}`.toLowerCase() });\n el.append(rowEl);\n }\n\n filter.addEventListener(\"input\", () => {\n const q = filter.value.trim().toLowerCase();\n for (const group of groups) {\n let any = false;\n for (const row of group.rows) {\n const show = q === \"\" || row.text.includes(q);\n row.el.style.display = show ? \"\" : \"none\";\n any = any || show;\n }\n group.el.style.display = any ? \"\" : \"none\";\n }\n });\n\n // Read-only: each box's clock, then the board's current hands (title or\n // gameId, never internal ids) - the same sections as the engine examiners.\n const turnsHead = document.createElement(\"div\");\n turnsHead.className = \"sl-section\";\n turnsHead.textContent = \"Turns (per box)\";\n const turnsBody = document.createElement(\"div\");\n turnsBody.className = \"sl-turns\";\n const boardHead = document.createElement(\"div\");\n boardHead.className = \"sl-section\";\n boardHead.textContent = \"Board\";\n const boardBody = document.createElement(\"div\");\n boardBody.className = \"sl-board\";\n el.append(turnsHead, turnsBody, boardHead, boardBody);\n\n // A retained log (design 2.3), behind per-kind filters, with Autoscroll,\n // Copy and Clear - the JS rendering of the engine examiners' log panel.\n // Built twice: once for the RUN (every flow's events in one order, each line\n // naming its flow) and once for this flow's own. Both exist because a flow's\n // log cannot show a story action in another flow moving shared state\n // (design/shared-scarcity.md 8.2). Empty until the engine had the log option.\n const check = (text: string, onChange: (on: boolean) => void, cls: string): HTMLLabelElement => {\n const label = document.createElement(\"label\");\n label.className = cls;\n const box = document.createElement(\"input\");\n box.type = \"checkbox\";\n box.checked = true;\n box.addEventListener(\"change\", () => onChange(box.checked));\n label.append(box, text);\n return label;\n };\n\n interface LogPanel { render: (force?: boolean) => void }\n const buildLogPanel = (\n which: \"flow\" | \"run\",\n caption: string,\n entriesOf: () => readonly (LogEntry | EngineLogEntry)[],\n clear: () => void,\n empty: string,\n ): LogPanel => {\n // Both panels carry the same controls, so each element takes a `sl-flow` /\n // `sl-run` modifier: without one, a selector for \"the Clear button\" is\n // ambiguous, which is exactly what the inspector test caught.\n const head = document.createElement(\"div\");\n head.className = `sl-section sl-${which}`;\n head.textContent = caption;\n const bar = document.createElement(\"div\");\n bar.className = `sl-logbar sl-${which}`;\n const body = document.createElement(\"div\");\n body.className = `sl-log sl-${which}`;\n\n const kindOn = new Map<string, boolean>();\n const visibleLines = (): string[] =>\n entriesOf().filter((e) => kindOn.get(logKindOf(e)) !== false).map(formatLogEntry);\n let autoscroll = true;\n let stampSeen = \"\";\n const render = (force = false): void => {\n const entries = entriesOf();\n const stamp = `${entries.length}:${entries.length > 0 ? entries[entries.length - 1]!.seq : -1}`;\n if (!force && stamp === stampSeen) return;\n stampSeen = stamp;\n const lines = visibleLines();\n body.textContent = lines.length > 0 ? lines.join(\"\\n\") : empty;\n if (autoscroll) body.scrollTop = body.scrollHeight;\n };\n\n for (const kind of LOG_KINDS) {\n kindOn.set(kind, true);\n bar.append(check(LOG_KIND_LABELS[kind], (on) => {\n kindOn.set(kind, on);\n render(true);\n }, \"sl-logkind\"));\n }\n bar.append(check(\"Autoscroll\", (on) => { autoscroll = on; }, \"sl-logscroll\"));\n const copyBtn = document.createElement(\"button\");\n copyBtn.type = \"button\";\n copyBtn.className = \"sl-logcopy\";\n copyBtn.textContent = \"Copy\";\n copyBtn.title = \"Copy the visible (filtered) log to the clipboard\";\n copyBtn.addEventListener(\"click\", () => { void navigator.clipboard?.writeText(visibleLines().join(\"\\n\")); });\n const clearBtn = document.createElement(\"button\");\n clearBtn.type = \"button\";\n clearBtn.className = \"sl-logclear\";\n clearBtn.textContent = \"Clear\";\n clearBtn.title = \"Drop the retained log entries (cosmetic - no game state changes)\";\n clearBtn.addEventListener(\"click\", () => { clear(); render(true); });\n bar.append(copyBtn, clearBtn);\n el.append(head, bar, body);\n return { render };\n };\n\n // This flow's own log first: it is what the panel was mounted on. The run's\n // log follows, because it is the wider view and it only earns its space once\n // a second flow exists.\n const flowLog = buildLogPanel(\"flow\", \"Log\", () => live().log(), () => live().clearLog(),\n \"(empty - new Engine(bundle, { log: true }) retains the flow log)\");\n const runLog = buildLogPanel(\"run\", \"Run log (every flow)\", () => engine.log(), () => engine.clearLog(),\n \"(empty - new Engine(bundle, { log: true }) retains the run log)\");\n const renderLog = (force = false): void => { runLog.render(force); flowLog.render(force); };\n\n const line = (parent: HTMLElement, text: string): void => {\n const div = document.createElement(\"div\");\n div.className = \"sl-line\";\n div.textContent = text;\n parent.append(div);\n };\n const readLive = (): void => {\n turnsBody.textContent = \"\";\n for (const box of live().listBoxes()) {\n line(turnsBody, `${box.title ?? box.gameId}: turn ${box.turn}`);\n }\n boardBody.textContent = \"\";\n for (const [hand, cards] of Object.entries(live().board())) {\n const names = cards.map((c) => c.title ?? c.gameId);\n line(boardBody, `${hand}: ${names.length > 0 ? names.join(\", \") : \"(empty)\"}`);\n }\n };\n\n const refresh = (): void => {\n for (const e of editors) e.read();\n readLive();\n renderLog();\n };\n readLive();\n renderLog(true);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n const pollMs = opts.pollMs ?? 250;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n (opts.container ?? document.body).append(el);\n return {\n el,\n refresh,\n destroy(): void {\n if (timer !== undefined) clearInterval(timer);\n el.remove();\n },\n };\n}\n\nfunction buildRow(\n live: () => Flow,\n row: PropertyRow,\n editors: { row: PropertyRow; read: () => void }[],\n): HTMLElement {\n const div = document.createElement(\"div\");\n div.className = \"sl-row\";\n const name = document.createElement(\"span\");\n name.className = \"sl-name\";\n name.textContent = row.name;\n name.title = row.path;\n\n const current = (): ScalarValue | undefined => {\n try { return live().getProperty(row.path); } catch { return undefined; }\n };\n // Forward-declared so commit can refresh the whole row (widget + reset\n // state) once everything below is built.\n let sync: () => void = () => {};\n const commit = (value: ScalarValue): void => { live().setProperty(row.path, value); sync(); };\n\n const reset = document.createElement(\"button\");\n reset.type = \"button\";\n reset.className = \"sl-reset\";\n reset.textContent = \"↺\";\n reset.title = \"Reset to default\";\n reset.addEventListener(\"click\", () => commit(row.default));\n\n let widget: HTMLElement;\n let read: () => void;\n const focused = (w: HTMLElement): boolean => document.activeElement === w;\n\n switch (row.type) {\n case \"boolean\": {\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.addEventListener(\"change\", () => commit(input.checked));\n widget = input;\n read = () => { if (!focused(input)) input.checked = current() === true; };\n break;\n }\n case \"number\": {\n const input = document.createElement(\"input\");\n input.type = \"number\";\n input.addEventListener(\"change\", () => commit(Number(input.value)));\n widget = input;\n read = () => { if (!focused(input)) input.value = String(current() ?? 0); };\n break;\n }\n case \"quality\": {\n // A quality shows as its LADDER, the current stage marked, as in both\n // editors: the family's one strip (@wildwinter/app-shell/value-control).\n // Closed like an enum: an unknown stage is not a harmless typo, since the\n // evaluator refuses it, so there is no free text to slip in.\n const holder = document.createElement(\"span\");\n const draw = (): void => {\n const v = current();\n holder.replaceChildren(rungStrip({ stages: row.stages ?? [], value: typeof v === \"string\" ? v : undefined, decl: row,\n onPick: (stage) => { commit(stage); draw(); }, doc: document }));\n };\n widget = holder;\n read = draw;\n break;\n }\n case \"enum\": {\n const select = document.createElement(\"select\");\n for (const v of row.values ?? []) {\n const o = document.createElement(\"option\");\n o.value = v;\n o.textContent = v;\n select.append(o);\n }\n select.addEventListener(\"change\", () => commit(select.value));\n widget = select;\n read = () => { if (!focused(select)) select.value = String(current() ?? \"\"); };\n break;\n }\n case \"flags\": {\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.placeholder = \"comma, separated, flags\";\n input.addEventListener(\"change\", () =>\n commit(input.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n widget = input;\n read = () => { if (!focused(input)) input.value = ((current() as string[] | undefined) ?? []).join(\", \"); };\n break;\n }\n default: { // string\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.addEventListener(\"change\", () => commit(input.value));\n widget = input;\n read = () => { if (!focused(input)) input.value = String(current() ?? \"\"); };\n }\n }\n\n const readAll = (): void => { read(); reset.disabled = eq(current(), row.default); };\n sync = readAll;\n readAll();\n editors.push({ row, read: readAll });\n div.append(name, widget, reset);\n return div;\n}\n","// ---------------------------------------------------------------------------\n// The bundle inspector, JS idiom (design/engine-runtimes.md 2, piece 6).\n//\n// describeBundle() IS the JS half of the parity member - JS has no engine\n// asset pipeline to hang an editor view off - so this is the optional DOM\n// rendering of it: a read-only panel over a compiled bundle, in the property\n// examiner's CSS grammar (inspector.ts), with NO session anywhere. Identity\n// first, then collapsible sections: hands (the deal() surface), tags by box\n// (the peek() criteria surface), declared properties, counts.\n//\n// Read-only by construction: there is no state to edit here, only the shape\n// that shipped.\n// ---------------------------------------------------------------------------\n/// <reference lib=\"dom\" />\n\nimport { describeBundle } from \"@storylet-studio/runtime\";\nimport type {\n BundleDescription, PropertyScopeSummary, PropertySummary,\n} from \"@storylet-studio/runtime\";\nimport type { Bundle, ScalarValue } from \"@storylet-studio/model\";\nimport { ensureInspectorStyle } from \"./inspector.js\";\n\nexport interface BundleInspectorOptions {\n /** Mount point; defaults to document.body. */\n container?: HTMLElement;\n title?: string;\n /** Start the collapsible sections open (default true). */\n open?: boolean;\n}\n\nexport interface BundleInspector {\n el: HTMLElement;\n /** The description this panel rendered (the API is the parity member). */\n description: BundleDescription;\n destroy(): void;\n}\n\nconst showVal = (v: ScalarValue | undefined): string =>\n v === undefined ? \"<unset>\" : JSON.stringify(v);\n\n/** \"name: type = default\", plus enum/flags options where declared, plus\n * \"(durable)\" where the declaration says the value outlives a run\n * (design/engine-server.md 4.2). Nothing is added for the ordinary\n * run-scoped property: that is what a property is. */\nexport function formatPropertySummary(p: PropertySummary): string {\n const options = p.values !== undefined && p.values.length > 0 ? ` [${p.values.join(\", \")}]` : \"\";\n const durable = p.durable === true ? \" (durable)\" : \"\";\n return `${p.name}: ${p.type} = ${showVal(p.default)}${options}${durable}`;\n}\n\n/** The scope label a declaration block files under (\"world\", \"player\", \"box box\",\n * \"tag docks (zone)\"). A host scope is named by its token, as @world is. */\nexport function formatScopeLabel(scope: PropertyScopeSummary): string {\n if (scope.scope === \"world\" || scope.scope === \"story\") return scope.scope;\n if (scope.scope === \"host\") return scope.owner;\n const group = scope.group !== undefined ? ` (${scope.group})` : \"\";\n return `${scope.scope} ${scope.owner}${group}`;\n}\n\nconst line = (parent: HTMLElement, text: string, cls = \"sl-line\"): HTMLElement => {\n const div = document.createElement(\"div\");\n div.className = cls;\n div.textContent = text;\n parent.append(div);\n return div;\n};\n\n/** A collapsible section in the examiner's grammar. */\nconst fold = (parent: HTMLElement, label: string, open: boolean): HTMLElement => {\n const details = document.createElement(\"details\");\n details.className = \"sl-fold\";\n details.open = open;\n const summary = document.createElement(\"summary\");\n summary.textContent = label;\n details.append(summary);\n const body = document.createElement(\"div\");\n details.append(body);\n parent.append(details);\n return body;\n};\n\n/** Render a read-only summary of a compiled bundle: what an integrator may\n * call, with no session and no game running. */\nexport function createBundleInspector(\n bundle: Bundle,\n opts: BundleInspectorOptions = {},\n): BundleInspector {\n ensureInspectorStyle();\n const description = describeBundle(bundle);\n const open = opts.open ?? true;\n\n const el = document.createElement(\"div\");\n el.className = \"sl-insp\";\n\n const head = document.createElement(\"div\");\n head.className = \"sl-head\";\n const h = document.createElement(\"h3\");\n h.textContent = opts.title ?? \"Bundle\";\n head.append(h);\n el.append(head);\n\n // --- identity (always visible: which bundle is this?) --------------------\n const { identity, totals } = description;\n const ident = document.createElement(\"div\");\n ident.className = \"sl-ident\";\n el.append(ident);\n line(ident, `${identity.project} ${identity.version}`, \"sl-line\");\n line(ident, `schema ${identity.schema}`, \"sl-line sl-note\");\n line(ident, `hash ${identity.hash === \"\" ? \"(none)\" : identity.hash} - metadata ${identity.metadata}`,\n \"sl-line sl-note\");\n\n // --- hands: the deal() surface -------------------------------------------\n const handsBody = fold(el, \"Hands (deal)\", open);\n handsBody.className = \"sl-hands\";\n if (description.hands.length === 0) {\n line(handsBody, \"(no hands - this bundle is peek-only)\", \"sl-line sl-note\");\n }\n for (const hand of description.hands) {\n const template = hand.template !== undefined ? `, template ${hand.template}` : \"\";\n // A movable hole is the one thing about a hand its name cannot say: write\n // that property and the hand moves (4.6).\n const moves = hand.movable === undefined ? \"\"\n : `, moves ${hand.movable.map((m) => `${m.group} from ${m.from}`).join(\" and \")}`;\n line(handsBody, `${hand.gameId}: box ${hand.box}, slots ${hand.slots}${template}${moves}`\n + (hand.title !== undefined ? ` - ${hand.title}` : \"\"));\n }\n\n // --- tag groups by box: the peek() criteria surface ----------------------\n const tagsBody = fold(el, \"Tags by box (peek criteria)\", open);\n tagsBody.className = \"sl-tags\";\n for (const box of description.boxes) {\n line(tagsBody, `${box.title ?? box.gameId}`, \"sl-group\");\n if (box.tagGroups.length === 0) {\n line(tagsBody, \" (no tag groups)\", \"sl-line sl-note\");\n }\n for (const group of box.tagGroups) {\n line(tagsBody, ` ${group.gameId}: ${group.tags.length > 0 ? group.tags.join(\", \") : \"(no tags)\"}`);\n }\n }\n\n // --- declared properties: what expressions read, what a host may set ----\n const propsBody = fold(el, \"Properties (declared)\", open);\n propsBody.className = \"sl-props\";\n for (const scope of description.properties) {\n line(propsBody, formatScopeLabel(scope), \"sl-group\");\n if (scope.properties.length === 0) {\n line(propsBody, \" (none declared)\", \"sl-line sl-note\");\n }\n for (const p of scope.properties) {\n line(propsBody, ` ${formatPropertySummary(p)}`);\n }\n }\n\n // --- the project map: which boxes are on it, and any geometry carried ---\n //\n // Only when there IS one. An empty section on every ordinary bundle would\n // teach the reader to skip a section that only ever matters when it is not\n // empty. The zones themselves are already under Properties and the opted-in\n // boxes' peek criteria; this says where they live and what the build carried\n // besides, since the geometry is inert and would otherwise go unmentioned.\n const map = description.map;\n if (map !== undefined) {\n const mapBody = fold(el, \"Project map\", open);\n mapBody.className = \"sl-maps\";\n line(mapBody, `${map.group}: ${map.tags.join(\", \")}`);\n line(mapBody, `boxes on the map: ${map.boxes.length > 0 ? map.boxes.join(\", \") : \"none\"}`);\n const sites = Object.entries(map.sites).map(([box, n]) => `${box} ${n}`).join(\", \");\n line(mapBody, `geometry carried (the engine ignores it): zones ${map.zones}, pictures ${map.backgrounds}`\n + `, hands ${sites === \"\" ? \"0\" : sites}`, \"sl-line sl-note\");\n }\n\n // --- counts: orientation, not inventory ---------------------------------\n const countsBody = fold(el, \"Counts\", open);\n countsBody.className = \"sl-counts\";\n line(countsBody, `boxes ${totals.boxes} - decks ${totals.decks} - cards ${totals.cards}`);\n line(countsBody, `hands ${totals.hands} - templates ${totals.templates} - tag groups ${totals.tagGroups}`);\n for (const box of description.boxes) {\n // A timed box says its unit here (design/engine-server.md 4.8), because\n // this is the line an integrator reads to find out what their host has to\n // tick. Nothing is added for an ordinary box: the answer \"a turn is a\n // play\" belongs in the docs, not on every line of every bundle.\n line(countsBody, `${box.gameId}: decks ${box.counts.decks}, cards ${box.counts.cards}, `\n + `hands ${box.counts.hands}, templates ${box.counts.templates}, `\n + `tag groups ${box.counts.tagGroups}, ranking.specificity ${box.ranking.specificity}`\n + (box.turn === undefined ? \"\" : box.turn.seconds !== undefined ? `, turn = ${box.turn.seconds}s` : \", turn moved by the game\")\n // Only when there are any: a box whose cards all come back with the run\n // has nothing for a server to lift, and the zero would be noise.\n + (box.durableCards !== undefined ? `, durable cards ${box.durableCards}` : \"\"));\n }\n\n (opts.container ?? document.body).append(el);\n return {\n el,\n description,\n destroy(): void {\n el.remove();\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live Link (design/live-link.md): the game-side client.\n//\n// Joins a running game to Storyletter over a loopback WebSocket. Two things\n// travel on it: the flow's trace stream and board snapshots go UP, so the\n// editor's Board can show the game's run instead of its own (observe-only: the\n// editor never drives the game); freshly compiled bundles come DOWN after a\n// save, so the run picks up the edit without restarting (applyLiveBundle in\n// refresh.ts does the swap).\n//\n// Wire protocol `storyletengine/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:2, build, project?, boxes?, flows:[id...] }\n// - on open, and again on setBuild\n// flowOpen / flowClose : { t:\"flowOpen\"|\"flowClose\", flow }\n// - a flow appeared or went\n// trace : { t:\"trace\", flow, event } - every TraceEvent any flow emits\n// board : { t:\"board\", flow, hands:{ hand: [card...] }, turns:{ box: n } }\n// - after hello, and after every deal /\n// play / evict / turns event\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: the full .storyletsc\n// JSON as a string\n// Identity in frames is by gameId (hands, boxes, cards), and since 4.4 that\n// holds for the trace event too: it is forwarded verbatim, and the runtime's\n// own ids are gameIds now, so the rule has no exception left.\n//\n// Patterpad's createDebugLink is the template (Patter play-helpers/debug.ts):\n// hello first, frames queue until the first socket opens, a missing editor is\n// a silent no-op, nothing here ever throws into the game, and no WebSocket\n// implementation at all degrades to a no-op handle.\n//\n// It reconnects, by the rule Patter's debug link shares: until the game closes\n// it, a socket that closes or never opens is tried again after 1 s, doubling to\n// 10 s, and back to 1 s once a hello goes. Each new socket says hello again with\n// the build and the open flows, then every flow's board. One line is logged when\n// an open link is lost and one when it is back; a link that never opened logs\n// nothing. Frames queue only while the first socket connects: after any attempt\n// fails, nothing queues until a socket is open. The editor closes a game it\n// replaced (another game connected) with code 4000, and that one does not\n// retry, so two games do not take turns. `observe(...)` became a\n// trace subscription, which is why this one takes an ENGINE: attach(engine)\n// subscribes, detach() stops, and a live refresh replaces the flow\n// (detach the old one, attach the new one, then setBuild).\n//\n// const link = createLiveLink({ build: bundle.content.hash, onBundle: ... });\n// link.attach(engine); // the ENGINE: the link discovers your flows itself\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow, TraceEvent } from \"@storylet-studio/runtime\";\n\n/** A minimal structural type for a WebSocket implementation (browsers and\n * Node 22+ have a global one). */\nexport interface LiveSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"error\", listener: () => void): void;\n /** The close event's `code` says why: 4000 is the editor replacing this\n * game with another, which is not retried. */\n addEventListener(type: \"close\", listener: (ev?: { code?: number }) => void): void;\n /** Incoming editor messages (the pushed bundle). Optional so a bare\n * send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype LiveSocketCtor = new (url: string) => LiveSocketLike;\n\nexport interface LiveLinkOptions {\n /** The running bundle's build identity: pass `bundle.content.hash`. The\n * editor compares it with its own compiled hash (in sync / stale). */\n build: string;\n /** Optional project name, shown in the editor's connect-chip tooltip. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4472`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (tests with a\n * fake socket, or a host without a global WebSocket). */\n WebSocket?: LiveSocketCtor;\n /** Live refresh: the editor pushed a freshly compiled bundle. `data` is\n * the .storyletsc JSON; hand it (with your current Engine) to\n * `applyLiveBundle`, `attach` the engine it returns, then call\n * `link.setBuild(build)`. Never called with a malformed frame. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\n/** Where a link is: the same three words Unity's LiveLinkState, Godot's\n * `link_state()` and Unreal's `LinkState` carry. */\nexport type LiveLinkState = \"connecting\" | \"connected\" | \"closed\";\n\nexport interface LiveLink {\n /** Where the link is, read at the moment of asking: \"connecting\" until a\n * socket opens and while it retries after one closed or failed, \"connected\"\n * while one is open, \"closed\" once `close()` was called, the editor replaced\n * this game with another (close code 4000), or there was no WebSocket to\n * open. An\n * examiner shows it so a host can tell \"the editor is not listening\" from\n * \"I never attached\". */\n readonly state: LiveLinkState;\n /** Start forwarding this ENGINE's trace: every flow's events, each frame\n * naming the flow it came from, so the editor can follow one participant\n * and switch. An earlier engine is detached first. Sends a board snapshot\n * per open flow straight away, queued behind the hello if the socket is\n * not open yet.\n *\n * Flows are discovered rather than declared: the link diffs `engine.flows()`\n * whenever anything happens and emits `flowOpen` / `flowClose` itself. That\n * is a deliberate departure from Patterplay, whose host calls `FlowOpened`\n * by hand - it has no engine-level trace tap to hang the diff on and we do,\n * so the host has nothing to remember and cannot get the editor's flow list\n * wrong. The one cost: a flow that opens and then does nothing at all is not\n * announced until the next event anywhere in the run. */\n attach(engine: Engine): void;\n /** Stop forwarding. A refresh replaces the engine, so attach the new one\n * afterwards. */\n detach(): void;\n /** After applying a pushed bundle: report the build now running (re-hellos\n * with the new build and a fresh board snapshot, so the editor's chip goes\n * back to in sync and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link and stop retrying; every later call is a no-op. */\n close(): void;\n}\n\n/** One game-to-editor frame, as the client serialises it. Exported for the\n * fixture test; hosts never build these by hand. */\nexport type LiveFrame =\n | { t: \"hello\"; v: 2; build: string; project?: string; boxes?: string[]; flows: string[] }\n | { t: \"flowOpen\"; flow: string }\n | { t: \"flowClose\"; flow: string }\n | { t: \"trace\"; flow: string; event: TraceEvent }\n | { t: \"board\"; flow: string; hands: Record<string, string[]>; turns: Record<string, number> };\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/** How many frames may wait for a socket that has not opened yet. Generous:\n * the point of queueing is that a game's first moments are not lost while the\n * editor's socket is still connecting. */\nconst QUEUE_CAP = 512;\nconst DEFAULT_URL = \"ws://127.0.0.1:4472\";\n/** The editor closed this game because another connected: do not retry. */\nconst LIVE_LINK_REPLACED = 4000;\n/** The retry's first wait and its longest, in ms. */\nconst RETRY_FIRST = 1000;\nconst RETRY_MAX = 10000;\n\n/** The trace kinds that move the board, and so are followed by a snapshot. */\n// A discard changes the board as a play does.\nconst BOARD_EVENTS: ReadonlySet<TraceEvent[\"type\"]> = new Set([\"deal\", \"play\", \"discard\", \"evict\", \"turns\"]);\n\n/** The cheap snapshot: hands by gameId holding card gameIds in dealt order,\n * and every box's clock by gameId. */\nexport function boardFrame(flow: Flow): Extract<LiveFrame, { t: \"board\" }> {\n const id = flow.id;\n const hands: Record<string, string[]> = {};\n for (const [hand, cards] of Object.entries(flow.board())) hands[hand] = cards.map((c) => c.gameId);\n const turns: Record<string, number> = {};\n for (const box of flow.listBoxes()) turns[box.gameId] = box.turn;\n return { t: \"board\", flow: id, hands, turns };\n}\n\n/**\n * Open a Live Link to Storyletter. Returns a handle whose calls are no-ops once\n * the editor disconnects or if it was never listening: safe to leave wired into\n * a shipping build behind a flag.\n */\nexport function createLiveLink(opts: LiveLinkOptions): LiveLink {\n const url = opts.url ?? DEFAULT_URL;\n const Ctor: LiveSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: LiveSocketCtor }).WebSocket;\n let queue: string[] = [];\n let sock: LiveSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live refresh lands\n let engine: Engine | null = null;\n let unsubscribe: (() => void) | null = null;\n // The flows the EDITOR believes are open. Diffed against engine.flows() so\n // flowOpen / flowClose are the link's own business, not the host's.\n let announced = new Set<string>();\n let dropped = false; // an attempt has failed or closed: queue nothing until a socket opens\n let wasOpen = false; // a socket has been open, so losing it is worth a line\n let lost = false; // logged as lost, waiting to log it back\n let wait = RETRY_FIRST;\n let retry: ReturnType<typeof setTimeout> | null = null;\n\n if (!Ctor) {\n // No WebSocket available (no global, none passed): a silent no-op link.\n return { state: \"closed\", attach() {}, detach() {}, setBuild() {}, close() { closed = true; } };\n }\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n const post = (frame: LiveFrame): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n // After an attempt has failed, the editor is away: nothing queues until a\n // socket is open again, and the hello and boards stand in for what was\n // missed (the 2026-08-29 fix, kept across retries).\n if (dropped && sock.readyState !== OPEN) return;\n queue.push(JSON.stringify(frame));\n // A cap for the CONNECTING window, where queueing is the point: the hello\n // and the frames a game emits during those first milliseconds have to\n // land. Beyond that many, the editor is not coming: drop the oldest, so\n // what survives is the most recent story rather than the first moments of\n // it. Once a socket has closed or failed the queue is dropped outright\n // (see the close listener); this is only the first connecting window.\n if (queue.length > QUEUE_CAP) queue.splice(0, queue.length - QUEUE_CAP);\n flush();\n };\n\n // The handshake goes straight to the socket, never through the queue: it\n // must be the first thing the editor reads, ahead of anything queued while\n // the socket was still connecting.\n const liveFlows = (): Flow[] => {\n try { return engine ? engine.flows() : []; } catch { return []; }\n };\n const sendHello = (): void => {\n const flows = liveFlows();\n const hello: LiveFrame = { t: \"hello\", v: 2, build, flows: flows.map((f) => f.id) };\n if (opts.project !== undefined) hello.project = opts.project;\n const first = flows[0];\n if (first) {\n try { hello.boxes = first.listBoxes().map((b) => b.gameId); } catch { /* mid-swap: no boxes */ }\n }\n // The editor's list starts from the hello, so the diff starts there too.\n announced = new Set(flows.map((f) => f.id));\n try { sock?.send(JSON.stringify(hello)); wait = RETRY_FIRST; } catch { /* race: closed immediately */ }\n };\n const postBoard = (flow: Flow): void => {\n try { post(boardFrame(flow)); } catch { /* never into the game */ }\n };\n /** Announce anything that opened or closed since the last look. Runs before\n * each forwarded event, so a frame never names a flow the editor has not\n * been told about. */\n const syncFlows = (): void => {\n const now = liveFlows();\n const ids = new Set(now.map((f) => f.id));\n for (const f of now) {\n if (announced.has(f.id)) continue;\n announced.add(f.id);\n post({ t: \"flowOpen\", flow: f.id });\n postBoard(f);\n }\n for (const id of [...announced]) {\n if (ids.has(id)) continue;\n announced.delete(id);\n post({ t: \"flowClose\", flow: id });\n }\n };\n const onTrace = (flowId: string, event: TraceEvent): void => {\n try {\n syncFlows();\n post({ t: \"trace\", flow: flowId, event });\n if (BOARD_EVENTS.has(event.type)) {\n const f = engine?.getFlow(flowId);\n if (f) postBoard(f);\n }\n } catch { /* never into the game */ }\n };\n\n const connect = (): void => {\n retry = null;\n if (closed) return;\n let s: LiveSocketLike;\n try { s = new Ctor(url); } catch { sock = null; closed = true; return; } // malformed URL etc.: never throw into the game\n sock = s;\n s.addEventListener(\"open\", () => {\n if (sock !== s) return;\n wasOpen = true;\n if (lost) { lost = false; console.info(`[storylets] Live Link is back (${url}).`); }\n sendHello();\n // A reconnect: nothing was queued while the editor was away, so every\n // flow's board follows the hello and the editor's Board comes back as\n // the game has it. (The first socket's boards are already queued.)\n if (dropped) for (const f of liveFlows()) postBoard(f);\n flush();\n });\n // Live refresh: the editor pushed a new bundle. The shape is checked here\n // so the host's handler never sees a malformed frame; anything else the\n // editor might send is ignored.\n s.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n s.addEventListener(\"error\", () => { /* editor not listening: the close that follows retries */ });\n // The socket is gone: drop the queue (nothing queues until the next socket\n // opens, so a game that shipped with the link wired in does no work per\n // event while the editor is away), then retry unless the game closed the\n // link or the editor replaced this game with another.\n s.addEventListener(\"close\", (ev?: { code?: number }) => {\n if (sock !== s) return;\n sock = null; queue = []; dropped = true;\n if (closed) return;\n if (ev?.code === LIVE_LINK_REPLACED) {\n closed = true;\n console.info(`[storylets] Live Link: another game took the editor's link (${url}).`);\n return;\n }\n if (wasOpen && !lost) { lost = true; console.info(`[storylets] Live Link lost (${url}); retrying.`); }\n retry = setTimeout(connect, wait);\n wait = Math.min(wait * 2, RETRY_MAX);\n });\n };\n connect();\n\n const detach = (): void => {\n unsubscribe?.();\n unsubscribe = null;\n engine = null;\n announced = new Set();\n };\n\n return {\n get state(): LiveLinkState {\n if (closed) return \"closed\";\n // Between attempts there is no socket, but the link is still trying.\n return sock !== null && sock.readyState === OPEN ? \"connected\" : \"connecting\";\n },\n attach(next: Engine): void {\n if (closed) return;\n detach();\n engine = next;\n try { unsubscribe = next.subscribeTrace(onTrace); } catch { engine = null; return; }\n // Every open flow's board up front: the editor can show any of them the\n // moment it connects, without waiting for that participant to move.\n announced = new Set(liveFlows().map((f) => f.id));\n for (const f of liveFlows()) postBoard(f);\n },\n detach,\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n // Re-handshake: the editor re-reads the build, then gets every flow's\n // table as the new engine has it.\n if (sock && sock.readyState === OPEN) {\n sendHello();\n for (const f of liveFlows()) postBoard(f);\n }\n },\n close(): void {\n closed = true;\n detach();\n queue = [];\n if (retry) { clearTimeout(retry); retry = null; }\n try { sock?.close(); } catch { /* already gone */ }\n sock = null;\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live refresh (design/live-link.md): the game-side applier. The editor pushes\n// a freshly compiled bundle over the Live Link (createLiveLink's `onBundle`);\n// this swaps it in under the running engine through engine.hotSwap: a new\n// Engine over the new bundle, carrying the old one's run, on the same registry. The runtime's loadGame() already\n// tolerates edited content (a deleted card leaves the table, orphaned\n// cooldowns and hand contents drop, a new property takes its default), so the\n// run carries across - every flow of it; it refuses only a save from another\n// project.\n//\n// Patter's applyLiveBundle has two tiers (strings-only vs hot swap) because it\n// has string tables and a cursor to re-find; we have neither, so this is the\n// one tier. Wire-up:\n//\n// let engine = new Engine(bundle, { seed: 7, log: true });\n// let flow = engine.openFlow(\"main\");\n// const link = createLiveLink({\n// build: bundle.content.hash,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, data, { log: true });\n// if (!r.ok) return console.warn(r.error);\n// engine = r.engine; // re-bind your handles: loadGame\n// flow = engine.getFlow(\"main\") // rebuilt every flow, so the old\n// ?? engine.openFlow(\"main\"); // Flow objects are inert\n// link.attach(engine); // re-attach the ENGINE: loadGame rebuilt every flow\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport type { Bundle, LoadReport } from \"@storylet-studio/model\";\nimport { Engine } from \"@storylet-studio/runtime\";\nimport type { EngineOptions } from \"@storylet-studio/runtime\";\n\nexport type LiveBundleResult =\n /** The new engine, carrying the old one's run (all flows), the bundle it\n * runs, and the report its load produced: what the edit cost the run\n * (cards evicted, properties dropped, defaulted or retyped). */\n | { ok: true; engine: Engine; bundle: Bundle; report: LoadReport }\n /** Nothing changed: keep the engine you have. */\n | { ok: false; error: string };\n\n/**\n * Apply a bundle the editor pushed over the Live Link through the engine's own\n * `hotSwap`, returning the replacement. Never throws; a failure (unparseable\n * JSON, a bundle the runtime rejects, a different project) comes back as\n * `{ ok: false, error }` and the old engine is left as it was.\n *\n * Works whether the engine made its own registry or was given the game's: on\n * the game's registry the old engine hands its keys to the replacement, which a\n * plain save and load into a second engine cannot do (the two would clash).\n *\n * The engine remembers the options it was built with (seed, log, hostScopes,\n * registry), so `opts` is only for overriding one of them for the replacement.\n */\nexport function applyLiveBundle(engine: Engine, bundleJson: string, opts: EngineOptions = {}): LiveBundleResult {\n let bundle: Bundle;\n try {\n bundle = JSON.parse(bundleJson) as Bundle;\n } catch {\n return { ok: false, error: \"pushed bundle is not valid JSON\" };\n }\n try {\n // The engine's own hotSwap: on the game's registry the old engine has to hand\n // its keys over, which a plain save and load into a new engine cannot do.\n const { engine: next, report } = engine.hotSwap(bundle, opts);\n return { ok: true, engine: next, bundle, report };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n","// ---------------------------------------------------------------------------\n// The host's container for a host scope, @world or any other the bundle\n// declares (design/flows.md; engine-runtimes.md 3.1).\n//\n// A host scope is the game's own state: the engine resolves it through a\n// resolver and NEVER saves it - \"host saves its container once, each engine saves its\n// own envelope\". A real game binds its own state here; a host that has no\n// state of its own (the demos, the playable page, the Board) uses this\n// ready-made container so @world still persists across its save/load.\n//\n// This is also what keeps a mixed Patter + Storylet Engine game honest: ONE\n// container, both engines mounting it foreign, neither writing it into its\n// envelope.\n// ---------------------------------------------------------------------------\n\nimport { PropertyBag as StateBag } from \"@wildwinter/scoperegistry\";\nimport type { ScalarValue } from \"@wildwinter/expr\";\nimport type { ScopeResolver } from \"@wildwinter/expr\";\nimport { hostScopeDecls } from \"@storylet-studio/model\";\nimport type { Bundle, PropertyBag } from \"@storylet-studio/model\";\n\nexport interface WorldContainer {\n /** Pass as `new Engine(bundle, { hostScopes: { world: container.resolver } })`. */\n resolver: ScopeResolver;\n /** The kernel bag itself (subscribe, audit, rows live there) - mount it\n * into a state logger or examiner beside the engine's own bags. Writing it\n * DIRECTLY is writing the kernel, so a `writable: false` declaration asks\n * the kernel's question: pass `{ host: true }` to say the game is speaking\n * (`bag.set(name, value, { host: true })`). Through `resolver` or an\n * engine's setProperty that is already answered. */\n bag: StateBag;\n /** The current values, for saving beside the engine's envelope. */\n values(): PropertyBag;\n /** Restore saved values over fresh defaults: orphaned keys drop, new\n * declarations keep their defaults - the same drift rule as loadGame. */\n load(values: PropertyBag): void;\n}\n\n/** A container seeded from one host scope's declarations in the bundle:\n * `@world`'s unless `token` names another (`\"player\"`).\n *\n * The container is the GAME's state, so it WRITES - even a declaration\n * carrying `writable: false`. That flag is the STORY's promise not to write\n * the value (Reboot.md 10), and the engine keeps it where the story writes: an\n * outcome is refused against the engine's read-only table before it ever\n * reaches this resolver. Enforcing it here as well refused the HOST too - the\n * clock the game must move, the harness driving the value it is testing\n * against - which is the opposite of what the flag says.\n *\n * So the declarations are seeded AS DECLARED, which is what an examiner over\n * this container should read, and the writes go through as HOST writes\n * (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the\n * engine's own doorway and passes the flag too: the engine has already sorted\n * story from host by then - a story write was refused earlier, a host write is\n * the only kind that arrives - and a resolver takes a name and a value with no\n * room to say which. A game wanting a rule of its own binds its own resolver\n * rather than this one; the ports' container (Unreal's UStoryletWorld) draws\n * the same line, with HostSet never refused and StorySet asking the game's own\n * read-only list. */\nexport function createWorldContainer(bundle: Bundle, token = \"world\"): WorldContainer {\n const bag = new StateBag(hostScopeDecls(bundle, token), { normalise: (n) => n });\n return {\n resolver: {\n get: (n) => bag.get(n),\n set: (n: string, v: ScalarValue) => { bag.set(n, v, { host: true }); },\n },\n bag,\n values: () => bag.values,\n load: (values) => bag.load(values),\n };\n}\n"],"mappings":"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","names":["browser_exports","__export","DURABLE_SCHEMA","Engine","Flow","STORYLET_ENGINE_VERSION","StoryletError","applyLiveBundle","boardFrame","compareStages","createBundleInspector","createStateLogger","createLiveLink","createPropertyInspector","createWorldContainer","describeBundle","deserializeState","diffState","effectiveFields","ensureInspectorStyle","extensionDecls","extensionsOf","fieldDecls","fieldValue","formatLogEntry","formatPropertySummary","formatScopeLabel","loadState","makePrng","nextStage","outcomeFieldDecls","saveState","serializeState","shuffleInPlace","snapshotState","stageIndex","versionInRange","own","record","key","deserialiseAst","node","args","EvalError","message","evaluate","node","ctx","dialect","missingPolicy","s","rec","n","scope","own","val","arg","ladder","ladderOf","current","stageIndex","def","l","r","left","right","lLadder","rLadder","sameLadder","valueEquals","assertNumbers","JOIN_TEXT","a","b","x","y","i","op","value","toUint32","seed","modded","makePrng","s","shuffleInPlace","arr","prng","i","j","ladderOf","ladder","typeWord","v","stageIndex","value","positionOf","stages","EvalError","i","compareStages","a","b","d","nextStage","valueFits","decl","value","opts","f","CHECK_FLAGS_COUNTING_CALL","node","DEFAULT_COUNTING_CALLS","matchedSpecificity","evalTruthy","opts","countingCalls","walk","want","l","r","rule","c","operands","holds","ENGINE_SCOPES","NEVER_PLAYED","host","h","stringArg","fn","args","i","v","EvalError","flagsArg","OWN_SCOPES","EXTERNAL_SCOPES","ENGINE_SCOPES","s","t","storyletsDialect","token","nextRandom","a","b","lo","hi","flags","arg","result","idx","card","dimension","value","gameIdify","text","bundleHostScopes","of","legacy","hostScopeSpec","token","s","hostScopeDecls","effectiveGameId","entity","pinned","gameIdify","valueAddresses","bundle","tags","box","boxGameId","group","tag","gameId","forms","list","print","accept","repeated","zoneQualified","candidates","ambiguous","zoneQualifiedValueAddressMessage","segment","zone","name","ambiguousValueAddressMessage","q","PLACE_GROUP","HOLE_REF","isHoleRef","value","parseHoleRef","m","BUNDLE_SCHEMA","BUNDLE_SCHEMA_V1","BUNDLE_SCHEMA_V0","BUNDLE_SCHEMAS","groupsOfBox","bundle","box","allTagGroups","bundle","groups","box","SAVE_SCHEMA","SAVE_SCHEMA_V2","SAVE_SCHEMA_V1","SAVEFILE_SCHEMA","SHARD_EXTENSIONS","MANIFEST_EXTENSION","SOURCE_EXTENSIONS","SHARD_EXTENSIONS","MANIFEST_EXTENSION","diffState","prev","next","changes","paths","path","from","to","show","v","prefixOf","m","createStateLogger","adapter","opts","sink","line","label","emit","c","full","out","prefix","name","value","baseline","pushed","mounted","hook","bag","change","mount","mounts","i","diffed","PropertyBag","_PropertyBag","declarations","opts","n","d","name","defaultFor","value","change","audit","fn","rowFor","c","k","values","saved","v","misfits","valueFits","writable","pathPrefix","rowName","lowerCase","SAVE_FRAGMENT_VERSION","ScopeRegistry","token","o","bag","waiting","e","prefix","key","only","taken","m","scope","found","resolver","norm","decls","out","owner","row","host","view","scopes","qualities","normOf","aliases","declsOf","properties","vals","blob","refit","fragment","by","wants","tagKey","boxId","groupId","tagId","StoryletError","message","ENGINE_OPTIONS","DURABLE_SCHEMA","cardIsShared","card","deckShared","cardIsDurable","deck","sharedCap","acrossLimit","orderRun","run","held","prng","first","s","rest","weight","drawn","left","total","r","k","shuffleInPlace","inOneHandOf","OWNER","identity","propertyPath","path","esc","id","unesc","sharedKey","kind","flowPrefix","flowId","flowKey","emptyPartitionValues","partitionsFromSections","sections","flowIds","shared","flows","flowOf","escaped","p","key","values","m","sectionsOf","keyOf","out","OWNED_SCOPES","bagFromDecls","decls","pathPrefix","PropertyBag","ownValue","bag","name","EMPTY_SCOPE","conditionPasses","v","SCOPE_DEFAULT_SHARED","isShared","scope","d","sharedHalf","flowHalf","isOwnedScope","emptyOwnerIndex","emptyOwnerIndexes","indexOwner","index","entity","gameId","effectiveGameId","indexValueOwners","bundle","addresses","valueAddresses","segment","candidates","zone","durableProps","internals","seen","push","decl","owner","list","addressOf","durableBag","prop","durableValues","props","entries","value","byKey","address","planDurable","cards","save","flow","draft","at","byAddress","carried","valueFits","eligible","e","spend","entry","assertDurable","schema","project","putDurable","hostReadOnly","spec","toForeignDecl","selfBackedDecls","writable","handDeclsOf","hand","resolveOwner","byGameId","ownerOrThrow","ambiguousValueAddressMessage","zoneQualifiedValueAddressMessage","legacyAddressMessage","buildPartition","bags","partitionValues","loadPartition","emptyDraft","SORT_SEP","items","item","a","b","propKey","walkScope","saved","byName","clean","walkPartition","n","savedKind","ids","finishReport","drift","evicted","droppedCooldowns","c","droppedSpent","droppedProperties","defaultedProperties","retypedProperties","isRecord","describeInput","bundleShapeProblems","problems","scopes","BUNDLE_SCHEMA","part","registry","refuseUnreadableSave","envelope","SAVE_SCHEMA","SAVE_SCHEMA_V2","SAVE_SCHEMA_V1","takes","quoted","xs","x","savedCard","savedHand","savedBox","cardKey","checkHostWrite","parts","host","hostRows","defaultFor","refuseUnreadableBundle","BUNDLE_SCHEMAS","missing","map","mapName","PLACE_GROUP","boxTags","box","group","tag","clash","boxName","g","names","where","template","Engine","_Engine","opts","setup","ScopeRegistry","misfits","view","event","turn","h","viewRevision","viewCache","reg","ctx","declSet","half","allTagGroups","node","bound","declared","bundleHostScopes","registered","mount","register","story","token","resolver","near","isBound","state","sharedDecls","grab","any","claim","durable","flowCards","otherClaims","existing","dealt","Flow","content","keepFlows","cardId","except","counts","found","perFlow","add","_prefix","row","mounts","handler","options","snapshot","next","mine","registeredKeys","f","waiting","report","stillWaiting","all","take","memory","plan","seed","hostScopes","scratch","dropped","moved","spent","withProps","cooldowns","cardName","board","restored","handKey","keys","known","kept","turns","engine","makePrng","put","pair","own","keep","boxRef","expr","deserialiseAst","ref","groups","groupsOfBox","record","tagIds","cached","made","memo","t","since","NEVER_PLAYED","last","handEnv","others","source","evaluate","storyletsDialect","boundTags","askNames","bindAll","bindings","isHoleRef","condition","criteria","groupRef","tagRef","groupName","parsed","parseHoleRef","wanted","ask","sources","contents","world","home","tags","claimed","trace","verdict","gateOk","scored","deckCtx","refused","priority","matchedSpecificity","i","j","taken","claimCounts","worldClaims","ordered","listed","handRef","conditionOk","evict","reason","survivors","handId","handRefs","want","room","outranked","from","o","outcomeGameId","bare","outcome","perPlay","newTurn","writes","target","landed","w","l","prev","outcomeId","match","boxKey","STORYLET_ENGINE_VERSION","SEMVER","parse","v","m","compareDigits","x","y","compare","a","b","i","c","xn","yn","versionInRange","version","min","maxExclusive","lo","hi","summarise","decls","d","defaultFor","durableCardCount","box","n","deck","card","handDecls","hand","movableHoles","bundle","filled","out","groupId","value","isHoleRef","group","groupsOfBox","g","effectiveGameId","handSlots","declared","t","describeBundle","boxes","hands","hosts","bundleHostScopes","properties","h","totals","boxGameId","cards","tag","template","movable","push","scope","owner","map","sites","boxOf","bundle","box","b","own","record","name","fieldDecls","outcomeFieldDecls","fieldValue","decls","values","fallback","f","effectiveFields","out","v","k","extensionDecls","extension","kind","ext","extensionsOf","address","node","first","second","named","items","x","cards","d","id","g","snapshotState","engine","flow","out","bag","row","extraState","flowBlob","id","saved","boxId","turn","cardId","at","handId","cards","createStateLogger","opts","live","serializeState","engine","world","saveState","SAVEFILE_SCHEMA","loadState","file","opts","SAVE_SCHEMA","SAVE_SCHEMA_V2","SAVE_SCHEMA_V1","container","before","report","e","deserializeState","json","parsed","renderTip","host","text","re","last","m","strong","inited","checked","suppressed","SHOW_DELAY","EDGE","GAP","tip","timer","active","activeKey","ensureEl","el","rectOf","anchor","place","t","r","tr","top","left","show","paint","hide","schedule","ensureTooltipHost","initTooltips","checkTooltipHost","checked","inited","tipAncestor","opts","suppressed","el","to","hide","active","tag","classNameOrProps","children","node","props","child","fallbackText","decl","d","defaultLabel","rungStrip","opts","make","el","tag","props","node","strip","decl","unset","fallback","readOnly","defaultLabel","stage","current","falls","rung","STYLE_ID","CSS","ensureInspectorStyle","style","eq","a","b","LOG_KINDS","LOG_KIND_LABELS","logKindOf","e","showVal","v","formatLogEntry","formatLogBody","flow","stamp","dealt","c","crit","g","t","listed","groupOf","path","parts","createPropertyInspector","engine","opts","liveFlow","live","el","head","h","saveBtn","blob","serializeState","url","filePicker","file","reader","deserializeState","refresh","loadBtn","filter","editors","groups","lastGroup","row","group","rowEl","buildRow","q","any","show","turnsHead","turnsBody","boardHead","boardBody","check","text","onChange","cls","label","box","buildLogPanel","which","caption","entriesOf","clear","empty","bar","body","kindOn","visibleLines","autoscroll","stampSeen","render","force","entries","lines","kind","on","copyBtn","clearBtn","flowLog","runLog","renderLog","line","parent","div","readLive","hand","cards","names","timer","pollMs","name","current","sync","commit","value","reset","widget","read","focused","w","input","holder","draw","rungStrip","stage","select","o","s","readAll","showVal","v","formatPropertySummary","p","options","durable","formatScopeLabel","scope","group","line","parent","text","cls","div","fold","label","open","details","summary","body","createBundleInspector","bundle","opts","ensureInspectorStyle","description","describeBundle","el","head","h","identity","totals","ident","handsBody","hand","template","moves","m","tagsBody","box","propsBody","map","mapBody","sites","n","countsBody","DEFAULT_URL","BOARD_EVENTS","boardFrame","flow","id","hands","hand","cards","c","turns","box","createLiveLink","opts","url","DEFAULT_URL","Ctor","queue","sock","closed","build","engine","unsubscribe","announced","dropped","wasOpen","lost","wait","retry","flush","m","post","frame","liveFlows","sendHello","flows","hello","f","first","b","postBoard","syncFlows","now","ids","onTrace","flowId","event","connect","s","ev","msg","detach","next","applyLiveBundle","engine","bundleJson","opts","bundle","next","report","e","createWorldContainer","bundle","token","bag","PropertyBag","hostScopeDecls","n","v","values"]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../../../../expr/packages/expr/src/own.ts","../../../../expr/packages/expr/src/ast.ts","../../../../expr/packages/expr/src/evaluate.ts","../../../../expr/packages/expr/src/prng.ts","../../../../expr/packages/expr/src/stages.ts","../../../../expr/packages/expr/src/values.ts","../../../../expr/packages/expr-specificity/src/index.ts","../../dialect/src/engine-scopes.ts","../../dialect/src/index.ts","../../../node_modules/@wildwinter/toolkit/src/ids.ts","../../../node_modules/@wildwinter/toolkit/src/gameid.ts","../../../node_modules/@wildwinter/toolkit/src/hash.ts","../../../node_modules/@wildwinter/toolkit/src/find.ts","../../model/src/index.ts","../../../../expr/packages/scoperegistry/src/state-logger.ts","../../../../expr/packages/scoperegistry/src/index.ts","../../runtime/src/engine.ts","../../runtime/src/version.ts","../../runtime/src/describe.ts","../../runtime/src/fields.ts","../src/logger.ts","../src/save.ts","../../../node_modules/@wildwinter/app-shell/src/tooltip.ts","../../../node_modules/@wildwinter/app-shell/src/dom.ts","../../../node_modules/@wildwinter/app-shell/src/field-help.ts","../../../node_modules/@wildwinter/app-shell/src/value-control.ts","../src/inspector.ts","../src/bundle-inspector.ts","../src/live-link.ts","../src/refresh.ts","../src/world.ts"],"sourcesContent":["// The browser drop-in: ONE classic script, no build step, defining the\n// `StoryletEngine` global with the runtime AND these helpers on it, so a plain\n// page can build an engine, deal, play, and save and load the family's\n// .storyletsave text, all from two script tags (this and Patterplay's).\n//\n// Built by this package rather than the runtime because this is the one\n// package that depends on both: the runtime cannot bundle the helpers without\n// bundling itself twice. The export names of the two do not overlap (checked\n// 2026-09-04), so `export *` from each is exact.\nexport * from \"@storylet-studio/runtime\";\nexport * from \"./index.js\";\n","// ---------------------------------------------------------------------------\n// Own-property reads, for every lookup by a name an author wrote.\n//\n// The language's names come from authors (`@world.constructor`, a function\n// called `toString`), and a plain JavaScript object answers the names it\n// inherits from Object.prototype. Looked up with `record[name]`, the keyword\n// table turned `constructor` into a token that was not an identifier, a static\n// scope bag read a built-in function as a property's value, and a dialect's\n// function table offered `Object` as a function to call (October 2026 review).\n// Every such lookup goes through `own`, which sees a record's own entries only.\n// ---------------------------------------------------------------------------\n\n/** `record[key]` when the record has it as its OWN property, else undefined. */\nexport function own<T>(record: Readonly<Record<string, T>> | undefined, key: string): T | undefined {\n return record !== undefined && Object.prototype.hasOwnProperty.call(record, key) ? record[key] : undefined;\n}\n","// ---------------------------------------------------------------------------\n// AST - the in-memory expression tree and its serialised tagged-tuple form.\n//\n// The in-memory `ExprNode` is a discriminated union (kind field). The published\n// `AstNode` is the compact tagged-tuple form that goes into a compiled bundle's\n// { src, ast } envelope - what a runtime walks, never parses.\n//\n// This module is dialect-agnostic: scope tokens and function names are plain\n// strings here; meaning is supplied by a Dialect (see dialect.ts).\n// ---------------------------------------------------------------------------\n\nexport type ScalarValue = boolean | number | string | string[];\n\nexport type BinaryOp =\n | \"==\" | \"!=\" | \">\" | \">=\" | \"<\" | \"<=\"\n | \"+\" | \"-\" | \"*\" | \"/\"\n | \"and\" | \"or\";\n\nexport type UnaryOp = \"not\" | \"neg\";\n\nexport type ExprNode =\n | { kind: \"bool\"; value: boolean }\n | { kind: \"number\"; value: number }\n | { kind: \"string\"; value: string }\n // All property references are scoped: bare `@name` is canonicalised to\n // `@<defaultScope>.name` at parse time. Names are lowercased at parse time.\n | { kind: \"scopedvar\"; scope: string; name: string }\n | { kind: \"call\"; name: string; args: ExprNode[] }\n | { kind: \"unary\"; op: UnaryOp; operand: ExprNode }\n | { kind: \"binary\"; op: BinaryOp; left: ExprNode; right: ExprNode }\n // Produced only by flag-delta function argument parsing (see Dialect\n // `flagDeltaArgs`) - not valid elsewhere.\n | { kind: \"flagdelta\"; sign: \"+\" | \"-\"; name: string };\n\n/**\n * Path into an ExprNode tree. Each segment names the field on the parent node,\n * with numeric indices for array elements (call args).\n * binary.left -> [\"left\"]\n * binary.right.args[0] -> [\"right\", \"args\", 0]\n * top-level node -> []\n */\nexport type AstPath = readonly (string | number)[];\n\n// ---------------------------------------------------------------------------\n// Published tagged-tuple form (JSON arrays, opcode at index 0).\n// ---------------------------------------------------------------------------\n\nexport type AstNode =\n | [\"b\", boolean]\n | [\"n\", number]\n | [\"s\", string]\n | [\"sv\", string, string]\n | [\"u\", UnaryOp, AstNode]\n | [\"bin\", BinaryOp, AstNode, AstNode]\n | [\"call\", string, ...AstNode[]]\n | [\"fd\", \"+\" | \"-\", string];\n\n/** In-memory ExprNode -> published tagged-tuple AstNode. */\nexport function serialiseAst(node: ExprNode): AstNode {\n switch (node.kind) {\n case \"bool\": return [\"b\", node.value];\n case \"number\": return [\"n\", node.value];\n case \"string\": return [\"s\", node.value];\n case \"scopedvar\": return [\"sv\", node.scope, node.name];\n case \"unary\": return [\"u\", node.op, serialiseAst(node.operand)];\n case \"binary\": return [\"bin\", node.op, serialiseAst(node.left), serialiseAst(node.right)];\n case \"call\": return [\"call\", node.name, ...node.args.map(serialiseAst)];\n case \"flagdelta\": return [\"fd\", node.sign, node.name];\n }\n}\n\n/** Published tagged-tuple AstNode -> in-memory ExprNode. */\nexport function deserialiseAst(node: AstNode): ExprNode {\n switch (node[0]) {\n case \"b\": return { kind: \"bool\", value: node[1] };\n case \"n\": return { kind: \"number\", value: node[1] };\n case \"s\": return { kind: \"string\", value: node[1] };\n case \"sv\": return { kind: \"scopedvar\", scope: node[1], name: node[2] };\n case \"u\": return { kind: \"unary\", op: node[1], operand: deserialiseAst(node[2]) };\n case \"bin\": return { kind: \"binary\", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };\n case \"call\": {\n const args = (node.slice(2) as AstNode[]).map(deserialiseAst);\n return { kind: \"call\", name: node[1], args };\n }\n case \"fd\": return { kind: \"flagdelta\", sign: node[1], name: node[2] };\n }\n}\n","// ---------------------------------------------------------------------------\n// Evaluator - walk an ExprNode against an EvalContext, parameterised by Dialect.\n//\n// Operators (binary/unary), short-circuiting, and type-checking are generic.\n// Scope resolution uses the context's scope maps + the Dialect's per-scope\n// missing-property policy. Function calls dispatch to the Dialect's functions.\n//\n// Ported from @storylets/engine (storylets/packages/engine/src/expression.ts),\n// generalised by injecting scopes + functions from the Dialect.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode, ScalarValue } from \"./ast.js\";\nimport type { Dialect, EvalContext, ScopeResolver } from \"./dialect.js\";\nimport { own } from \"./own.js\";\n\nexport class EvalError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"EvalError\";\n }\n}\n\nexport function evaluate(node: ExprNode, ctx: EvalContext, dialect: Dialect): ScalarValue {\n // Per-scope missing-property policy, precomputed once per top-level evaluate.\n const missingPolicy = new Map<string, \"false\" | \"throw\">(\n dialect.scopes.map((s) => [s.token, s.missing ?? \"false\"])\n );\n\n const rec = (n: ExprNode): ScalarValue => {\n switch (n.kind) {\n case \"bool\": return n.value;\n case \"number\": return n.value;\n case \"string\": return n.value;\n\n case \"scopedvar\": {\n const scope = own(ctx.scopes, n.scope);\n if (scope === undefined) {\n // Scope context absent -> graceful false. (A scope the dialect knows\n // about but the context didn't populate, or an unknown scope.)\n return false;\n }\n // A scope is either a static bag or a host resolver ({ get }). Bag values\n // are always ScalarValue (never functions), so a `get` function reliably\n // distinguishes a resolver.\n const val = typeof (scope as ScopeResolver).get === \"function\"\n ? (scope as ScopeResolver).get(n.name)\n : own(scope as Record<string, ScalarValue>, n.name);\n if (val === undefined) {\n // Property not declared on the present scope. Policy decides: \"false\"\n // for back-compat scopes, \"throw\" for scopes where a missing key is a\n // bug publish-time validation should have caught.\n if (missingPolicy.get(n.scope) === \"throw\") {\n throw new EvalError(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);\n }\n return false;\n }\n return val;\n }\n\n case \"call\": {\n // `advance` is the language's own, the first core built-in: the next\n // stage in the argument's ladder, saturating at the last. Core rather\n // than dialect because it IS the quality design's insertion mechanism\n // (an outcome that never names its destination routes through an\n // inserted stage automatically), and every dialect should say it the\n // same way. A dialect that defines its own `advance` wins, for\n // back-compat with any dialect that already had one.\n if (n.name === \"advance\" && !own(dialect.functions, n.name)) {\n const arg = n.args[0];\n if (n.args.length !== 1 || arg === undefined) {\n throw new EvalError(`advance() takes exactly 1 argument, got ${n.args.length}`);\n }\n const ladder = ladderOf(arg, ctx);\n if (ladder === undefined) {\n throw new EvalError(\"advance() needs a quality reference (@scope.name of a quality property)\");\n }\n const current = stageIndex(rec(arg), ladder, \"advance\");\n return ladder[Math.min(current + 1, ladder.length - 1)]!;\n }\n const def = own(dialect.functions, n.name);\n if (!def) throw new EvalError(`unknown function '${n.name}'`);\n return def.eval(n.args, { evaluate: rec, ctx });\n }\n\n case \"flagdelta\":\n throw new EvalError(\"flagdelta node is only valid as an argument to a flag-delta function\");\n\n case \"unary\": {\n if (n.op === \"not\") {\n const val = rec(n.operand);\n if (typeof val !== \"boolean\") throw new EvalError(`'not' requires a boolean operand, got ${typeof val}`);\n return !val;\n }\n // neg\n const val = rec(n.operand);\n if (typeof val !== \"number\") throw new EvalError(`unary '-' requires a numeric operand, got ${typeof val}`);\n return -val;\n }\n\n case \"binary\": {\n // Short-circuit operators first\n if (n.op === \"and\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'and' requires boolean operands, left is ${typeof l}`);\n if (!l) return false;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'and' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n if (n.op === \"or\") {\n const l = rec(n.left);\n if (typeof l !== \"boolean\") throw new EvalError(`'or' requires boolean operands, left is ${typeof l}`);\n if (l) return true;\n const r = rec(n.right);\n if (typeof r !== \"boolean\") throw new EvalError(`'or' requires boolean operands, right is ${typeof r}`);\n return r;\n }\n\n const left = rec(n.left);\n const right = rec(n.right);\n\n // Quality: when either operand REFERENCES a quality (the node carries\n // the scope+name the channel resolves), ordering compares by ladder\n // position and arithmetic is refused. Everything else is untouched,\n // so a context with no channel behaves exactly as before.\n const lLadder = ladderOf(n.left, ctx);\n const rLadder = ladderOf(n.right, ctx);\n const ladder = lLadder ?? rLadder;\n if (ladder !== undefined) {\n if (lLadder && rLadder && !sameLadder(lLadder, rLadder)) {\n if (n.op === \">\" || n.op === \">=\" || n.op === \"<\" || n.op === \"<=\") {\n throw new EvalError(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);\n }\n }\n switch (n.op) {\n case \">\": return stageIndex(left, ladder, \">\") > stageIndex(right, ladder, \">\");\n case \">=\": return stageIndex(left, ladder, \">=\") >= stageIndex(right, ladder, \">=\");\n case \"<\": return stageIndex(left, ladder, \"<\") < stageIndex(right, ladder, \"<\");\n case \"<=\": return stageIndex(left, ladder, \"<=\") <= stageIndex(right, ladder, \"<=\");\n case \"+\": case \"-\": case \"*\": case \"/\":\n throw new EvalError(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);\n default: break; // == and != fall through to plain value equality\n }\n }\n\n switch (n.op) {\n case \"==\": return valueEquals(left, right);\n case \"!=\": return !valueEquals(left, right);\n case \">\": assertNumbers(left, right, \">\"); return (left as number) > (right as number);\n case \">=\": assertNumbers(left, right, \">=\"); return (left as number) >= (right as number);\n case \"<\": assertNumbers(left, right, \"<\"); return (left as number) < (right as number);\n case \"<=\": assertNumbers(left, right, \"<=\"); return (left as number) <= (right as number);\n case \"+\":\n // Numbers only. Text joined by '+' fixes its word order in the\n // expression, so it cannot be translated: put the value in the text.\n if (typeof left === \"string\" || typeof right === \"string\") throw new EvalError(JOIN_TEXT);\n 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 assertNumbers(left, right, \"/\");\n if ((right as number) === 0) throw new EvalError(\"division by zero\");\n return (left as number) / (right as number);\n }\n }\n }\n };\n\n return rec(node);\n}\n\n/**\n * Equality for `==` / `!=`. Primitives compare by value (JS `===`); arrays\n * (the flags value type) compare as SETS: same members, ORDER IRRELEVANT.\n *\n * Plain `===` on arrays would be reference equality - two distinct arrays with\n * the same contents would never be equal, and a fresh array (from a scope read\n * or a function result) would never equal another. Mixed array/non-array\n * operands are unequal, and never an error.\n *\n * Order was significant until 2026-09-01, and that was wrong: a flags value IS\n * a set, and its stored order is an artefact of the order somebody happened to\n * add things in. `set_flags(@f, +a)` then `+b` compared UNEQUAL to the same two\n * flags added the other way round, which is a difference no author can see and\n * none intends. The Storylet Engine papered over it by sorting in `set_flags`,\n * which only holds while every producer sorts: a bundle's declared default, or\n * a host handing a list in, does not.\n *\n * Compared as MULTISETS (sorted copies), so a duplicated flag still counts. A\n * well-formed flags value has no duplicates, but equality should not be the\n * thing that decides what happens if one appears.\n */\nfunction valueEquals(a: ScalarValue, b: ScalarValue): boolean {\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b)) return false;\n if (a.length !== b.length) return false;\n const x = [...a].sort();\n const y = [...b].sort();\n for (let i = 0; i < x.length; i++) if (x[i] !== y[i]) return false;\n return true;\n }\n return a === b;\n}\n\n/** Why '+' refuses a string. Word for word the same in every port and the checker. */\nexport const JOIN_TEXT = \"'+' cannot join text: put the value into the text itself, so it can be translated\";\n\nfunction assertNumbers(l: ScalarValue, r: ScalarValue, op: string): void {\n if (typeof l !== \"number\" || typeof r !== \"number\") {\n throw new EvalError(`'${op}' requires numeric operands, got ${typeof l} and ${typeof r}`);\n }\n}\n\n// --- quality (design: storylets-new/design/quality.md) -----------------------\n\n/** The ladder behind an operand NODE, when the context's quality channel says\n * it references a quality. Values are plain strings; the node is what carries\n * the (scope, name) the channel needs. */\nfunction ladderOf(node: ExprNode, ctx: EvalContext): readonly string[] | undefined {\n if (node.kind !== \"scopedvar\" || ctx.qualities === undefined) return undefined;\n return ctx.qualities(node.scope, node.name);\n}\n\n/** Index of a stage in a ladder; an unknown stage is an error naming the\n * value, never a silent pass (a drifted save is exactly what lands here). */\nfunction stageIndex(value: ScalarValue, ladder: readonly string[], op: string): number {\n if (typeof value !== \"string\") {\n throw new EvalError(`'${op}' on a quality compares stages, got ${typeof value}`);\n }\n const i = ladder.indexOf(value);\n if (i < 0) throw new EvalError(`\"${value}\" is not a stage of this quality (stages: ${ladder.join(\", \")})`);\n return i;\n}\n\nconst sameLadder = (a: readonly string[], b: readonly string[]): boolean =>\n a.length === b.length && a.every((x, i) => x === b[i]);\n","// ---------------------------------------------------------------------------\n// mulberry32 - the contractual PRNG.\n//\n// A fixed, published algorithm that both product families need and neither\n// owns. It lived in `@storylet-studio/runtime` as prng.ts and inline in\n// `@patterkit/runtime`'s engine.ts, plus separately in three of Patterplay's\n// harnesses, plus a hand port in each of six native runtimes: thirteen copies\n// of forty lines. It belongs here, in the package both families already depend\n// on, for the same reason the evaluator does.\n//\n// All arithmetic is unsigned 32-bit, matching JavaScript's `>>> 0` and\n// Math.imul.\n// ---------------------------------------------------------------------------\n\nexport interface Prng {\n /** One draw in [0, 1); advances the state. */\n next(): number;\n /** The persisted state (a uint32; feed back into makePrng to restore). */\n state(): number;\n}\n\n/**\n * ECMA-262 7.1.6 ToUint32, which is what `seed >>> 0` means.\n *\n * Written out rather than spelled `seed >>> 0` because THIS is the obligation a\n * port in a language without JavaScript's shift semantics has to reproduce, and\n * the shorthand hides it. Patterplay's C++ port cast the seed straight to an\n * integer type, which is undefined behaviour outside that type's range and gave\n * two wrong answers before the parity corpus pinned them.\n */\nexport function toUint32(seed: number): number {\n if (Number.isNaN(seed) || !Number.isFinite(seed)) return 0;\n const modded = Math.trunc(seed) % 4294967296;\n return modded < 0 ? modded + 4294967296 : modded;\n}\n\n/**\n * Seed a generator. The state is UNSIGNED throughout (`>>> 0`, never `| 0`):\n * the two produce identical draws and differ only in the sign of the number a\n * save persists, and Patterplay shipped `| 0` for long enough that over half of\n * all its saves carried a negative state its own native ports could not read\n * back.\n */\nexport function makePrng(seed: number): Prng {\n let s = toUint32(seed);\n return {\n next(): number {\n s = (s + 0x6d2b79f5) >>> 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n },\n state(): number {\n return s;\n },\n };\n}\n\n/** The contractual shuffle: Fisher-Yates, descending. Runs of one element\n * consume no draws (the loop never executes for length < 2). */\nexport function shuffleInPlace<T>(arr: T[], prng: Prng): void {\n for (let i = arr.length - 1; i > 0; i--) {\n const j = Math.floor(prng.next() * (i + 1));\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n }\n}\n","// ---------------------------------------------------------------------------\n// Quality ladders, outside an expression.\n//\n// The evaluator knows how to order a quality's stages; nothing else did. Every\n// validator that took a quality declaration wrote its own idea of a valid\n// ladder, and the two product families disagreed (Patter wanted two stages,\n// the Storylet compiler one). The rule is a fact about what a ladder is, so it\n// lives here, beside the evaluator that gives a ladder its meaning.\n//\n// Design: patterkit design/proposals/patter-qualities.md, sections 3 and 6.\n// ---------------------------------------------------------------------------\n\nimport type { ExprNode } from \"./ast.js\";\nimport type { Dialect } from \"./dialect.js\";\nimport { EvalError } from \"./evaluate.js\";\nimport { KEYWORD_NAMES, literalSpans, parse } from \"./parser.js\";\nimport { unparse } from \"./unparse.js\";\n\n/** One way a quality declaration breaks the rule. A caller words it. */\nexport type QualityDeclarationProblem =\n /** Fewer than two stages: a ladder with one rung has nothing to order. */\n | { kind: \"too-few-stages\"; count: number }\n /** A stage whose name is empty or only spaces, by its position. */\n | { kind: \"empty-stage\"; index: number }\n /** A stage listed again, once per repeat: \"which position?\" has no answer. */\n | { kind: \"duplicate-stage\"; stage: string }\n /** A default that is given but is not one of the stages. */\n | { kind: \"default-not-a-stage\"; value: unknown };\n\n/**\n * Check a quality declaration against the family's one rule: at least two\n * stages, none empty, none twice; a default is optional (pass `undefined` for\n * none), and when given it must be a stage. Returns the problems in ladder\n * order, then the default; an empty list means the declaration is sound.\n *\n * A host that requires a default checks that it is present itself: this rule\n * only says what a given one must be.\n */\nexport function qualityDeclarationProblems(\n stages: readonly string[] | undefined, defaultValue?: unknown,\n): QualityDeclarationProblem[] {\n const problems: QualityDeclarationProblem[] = [];\n const ladder = stages ?? [];\n if (ladder.length < 2) problems.push({ kind: \"too-few-stages\", count: ladder.length });\n const seen = new Set<string>();\n ladder.forEach((stage, index) => {\n if (typeof stage !== \"string\" || stage.trim() === \"\") {\n problems.push({ kind: \"empty-stage\", index });\n } else if (seen.has(stage)) {\n problems.push({ kind: \"duplicate-stage\", stage });\n } else {\n seen.add(stage);\n }\n });\n if (ladder.length > 0 && defaultValue !== undefined && (typeof defaultValue !== \"string\" || !seen.has(defaultValue))) {\n problems.push({ kind: \"default-not-a-stage\", value: defaultValue });\n }\n return problems;\n}\n\n// --- the ladder outside an expression -----------------------------------------\n//\n// A host holding a quality's value (a field, a game-data value, a property it\n// read) needs the order the evaluator uses, and each game was re-implementing\n// it. These three are that order, stated once (patter-qualities.md section 3).\n// Each takes a bare ladder or the declaration carrying one, so a host writes\n// `compareStages(fieldDecl, card.fields.act, current)`.\n\n/** A quality's ladder, or a declaration with `stages` (a field's, a\n * property's, a game-data field's). A declaration without them has none. */\nexport type StageLadder = readonly string[] | { readonly stages?: readonly string[] };\n\nconst ladderOf = (ladder: StageLadder): readonly string[] =>\n (Array.isArray(ladder) ? ladder : (ladder as { readonly stages?: readonly string[] }).stages ?? []);\n\nconst typeWord = (v: unknown): string =>\n (Array.isArray(v) ? \"flags\" : v === null || v === undefined ? \"nothing\" : typeof v);\n\n/** The position of a stage, or -1 when the value is not one of the stages. */\nexport function stageIndex(ladder: StageLadder, value: unknown): number {\n return typeof value === \"string\" ? ladderOf(ladder).indexOf(value) : -1;\n}\n\n/** The position of a stage, or the evaluator's own error when it is not one. */\nfunction positionOf(stages: readonly string[], value: unknown): number {\n if (typeof value !== \"string\") throw new EvalError(`a stage is a name, got ${typeWord(value)}`);\n const i = stages.indexOf(value);\n if (i < 0) throw new EvalError(`\"${value}\" is not a stage of this quality (stages: ${stages.join(\", \")})`);\n return i;\n}\n\n/** -1, 0 or 1 as `a` is below, at or above `b` on the ladder. An error when\n * either is not a stage: an unknown stage has no position to compare. */\nexport function compareStages(ladder: StageLadder, a: unknown, b: unknown): -1 | 0 | 1 {\n const stages = ladderOf(ladder);\n const d = positionOf(stages, a) - positionOf(stages, b);\n return d < 0 ? -1 : d > 0 ? 1 : 0;\n}\n\n/** One rung up, staying on the top rung, as `advance()` does. An error when\n * the value is not a stage (a drifted save, a deleted stage). */\nexport function nextStage(ladder: StageLadder, value: unknown): string {\n const stages = ladderOf(ladder);\n return stages[Math.min(positionOf(stages, value) + 1, stages.length - 1)]!;\n}\n\n/**\n * The stages either side of `stage`, with it, in ladder order: the one below,\n * the stage, the one above, each where the ladder has it. A coverage driver\n * proposes these for a gate such as `@q >= \"x\"`, so both sides of the gate are\n * played; an editor offers them as the stages nearest a value.\n *\n * A name that is not a stage has no position to be beside (a deleted stage\n * took its place with it), so the answer is then the whole ladder, in order:\n * only the author knows where such a value belongs now.\n */\nexport function stagesEitherSide(ladder: StageLadder, stage: unknown): string[] {\n const stages = ladderOf(ladder);\n const i = stageIndex(stages, stage);\n if (i < 0) return [...stages];\n return stages.slice(Math.max(0, i - 1), i + 2);\n}\n\n// --- a stage literal in an expression (editors) --------------------------------\n//\n// A quality's values are stored by stage NAME, so a rename is the one edit that\n// has to reach every literal naming the stage (patter-qualities.md section 2),\n// and a reorder the one that changes what a gate means without changing any\n// text. Both editors walk their own formats for the expressions; these say what\n// happens inside one.\n\n/** Does `(scope, name)`, read in an expression, name the quality in question? */\nexport type StageTargetRef = (scope: string, name: string) => boolean;\n\nconst COMPARISONS = new Set([\"==\", \"!=\", \"<\", \"<=\", \">\", \">=\"]);\nconst ORDERING = new Set([\"<\", \"<=\", \">\", \">=\"]);\nconst BARE_WORD = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Rewrite the stage literal `from` to `to` wherever `src` compares it with the\n * quality `isTarget` names, or, with `assigned`, where the whole expression is\n * that literal (an effect's value assigned to the quality). Undefined when\n * nothing changed or the source does not parse.\n *\n * The author's own text is kept where that says exactly the rewritten\n * expression: of the occurrences of `from` (quoted either way, or a bare word),\n * the set whose replacement parses to the rewritten tree. Every one first, as\n * that is the usual case, then each smaller choice, while there are few enough\n * to try; so a condition that also compares another quality with the same word\n * keeps that one as it was. Otherwise the parser's form of the rewritten tree.\n */\nexport function renameStageInExpression(\n src: string, dialect: Dialect, isTarget: StageTargetRef, from: string, to: string, assigned = false,\n): string | undefined {\n let ast: ExprNode;\n try { ast = parse(src, dialect); } catch { return undefined; }\n let hits = 0;\n const isRef = (n: ExprNode): boolean => n.kind === \"scopedvar\" && isTarget(n.scope, n.name);\n const walk = (n: ExprNode): ExprNode => {\n switch (n.kind) {\n case \"binary\": {\n let left = walk(n.left);\n let right = walk(n.right);\n if (COMPARISONS.has(n.op)) {\n if (isRef(left) && right.kind === \"string\" && right.value === from) { right = { kind: \"string\", value: to }; hits++; }\n if (isRef(right) && left.kind === \"string\" && left.value === from) { left = { kind: \"string\", value: to }; hits++; }\n }\n return { ...n, left, right };\n }\n case \"unary\": return { ...n, operand: walk(n.operand) };\n case \"call\": return { ...n, args: n.args.map(walk) };\n default: return n;\n }\n };\n let next = walk(ast);\n if (assigned && next.kind === \"string\" && next.value === from) { next = { kind: \"string\", value: to }; hits++; }\n if (hits === 0) return undefined;\n\n const wanted = JSON.stringify(next);\n const quotedTo = (q: string): string => `${q}${to.replace(/\\\\/g, \"\\\\\\\\\").replace(new RegExp(q, \"g\"), `\\\\${q}`)}${q}`;\n const bareTo = BARE_WORD.test(to) && !KEYWORD_NAMES.includes(to) ? to : quotedTo('\"');\n // The candidates are the tokeniser's: a string literal or a bare word that IS `from`, never text inside\n // another literal, so a long note mentioning the stage neither counts nor changes.\n const found = literalSpans(src).filter((t) => t.value === from)\n .map((t) => ({ at: t.at, length: t.end - t.at, text: t.quote !== undefined ? quotedTo(t.quote) : bareTo }));\n const says = (text: string): boolean => {\n try { return JSON.stringify(parse(text, dialect)) === wanted; } catch { return false; }\n };\n const replaced = (mask: number): string => {\n let text = \"\";\n let at = 0;\n found.forEach((f, i) => {\n if (!(mask & (1 << i))) return;\n text += src.slice(at, f.at) + f.text;\n at = f.at + f.length;\n });\n return text + src.slice(at);\n };\n if (found.length > 0 && found.length <= 8) {\n for (let mask = (1 << found.length) - 1; mask > 0; mask--) if (says(replaced(mask))) return replaced(mask);\n }\n return unparse(next, { defaultScope: dialect.defaultScope });\n}\n\n/**\n * How many gates in `src` order the quality `isTarget` names against something\n * (`<`, `<=`, `>`, `>=` with the quality on one side), counting each one: the\n * gates whose meaning a reorder of the ladder changes, though no value does.\n * Zero for a source that does not parse, which gates nothing.\n */\nexport function stageGateCount(src: string, dialect: Dialect, isTarget: StageTargetRef): number {\n let ast: ExprNode;\n try { ast = parse(src, dialect); } catch { return 0; }\n let gates = 0;\n const count = (n: ExprNode): void => {\n switch (n.kind) {\n case \"binary\":\n if (ORDERING.has(n.op)\n && [n.left, n.right].some((s) => s.kind === \"scopedvar\" && isTarget(s.scope, s.name))) gates++;\n count(n.left); count(n.right);\n return;\n case \"unary\": count(n.operand); return;\n case \"call\": n.args.forEach(count); return;\n default: return;\n }\n };\n count(ast);\n return gates;\n}\n","// ---------------------------------------------------------------------------\n// Does a value fit its declaration?\n//\n// Every place a typed value is held outside an expression asks it: a compiler\n// checking a field's value or a given default, a load checking a save written\n// against an older build (a stage, an enum value or a flag since deleted). Both\n// product families had written it, Storylets twice in its compiler and once in\n// its engine, with the vocabulary rule disagreeing between the copies. It is a\n// fact about the family's types, so it lives here, beside the ladder helpers.\n//\n// Design: patterkit design/proposals/patter-qualities.md, sections 1 and 7.\n// ---------------------------------------------------------------------------\n\n/** A declaration as this check reads it: a property's, a field's or a game-data\n * field's. `type` is the family's word (`text`, `multiline`, `number`,\n * `boolean`, `enum`, `flags`, `quality`, and a property's `string`). */\nexport interface ValueDecl {\n readonly type: string;\n /** An enum's values, or the flags a flags value may hold. */\n readonly values?: readonly string[];\n /** A quality's ladder. */\n readonly stages?: readonly string[];\n}\n\nexport interface ValueFitsOptions {\n /** An enum may hold `\"\"`, none chosen: an editor writes that for \"(none)\".\n * Only an enum; a quality is always on a stage. */\n readonly noneChosen?: boolean;\n}\n\n/**\n * Whether `value` fits `decl`: the type first, then the declaration's own\n * vocabulary. An enum value or a stage that is no longer listed is still a\n * string, and still no longer a legal value. A declaration that lists no\n * vocabulary (`values` or `stages` absent, or not a list: a declaration read\n * from JSON as it came) constrains nothing past the type, as in every port.\n *\n * A type this build does not know constrains nothing: a newer build's\n * declaration is the compiler's to report, not a reason to throw a value away.\n */\nexport function valueFits(decl: ValueDecl, value: unknown, opts?: ValueFitsOptions): boolean {\n switch (decl.type) {\n case \"boolean\": return typeof value === \"boolean\";\n case \"number\": return typeof value === \"number\";\n case \"string\":\n case \"text\":\n case \"multiline\": return typeof value === \"string\";\n case \"enum\":\n if (typeof value !== \"string\") return false;\n if (value === \"\" && opts?.noneChosen) return true;\n return !Array.isArray(decl.values) || decl.values.includes(value);\n case \"flags\":\n return Array.isArray(value)\n && value.every((f) => typeof f === \"string\" && (!Array.isArray(decl.values) || decl.values.includes(f)));\n case \"quality\":\n return typeof value === \"string\" && (!Array.isArray(decl.stages) || decl.stages.includes(value));\n default: return true;\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","// GENERATED - vendored from expr/family/engine-scopes.json by scripts/sync-conformance.mjs.\n// Do not edit here; edit the shared list and re-run the script.\n\n/** Every engine's game-wide scope token across the family. A compiler accepts every token here\n * that is not its own engine's, unchecked: the other engine owns those names and types. */\nexport interface EngineScope {\n token: string;\n engine: string;\n means: string;\n}\n\nexport const ENGINE_SCOPES: readonly EngineScope[] = [\n { token: \"patter\", engine: \"Patterplay\", means: \"Patter's shared globals\" },\n { token: \"story\", engine: \"Storylet Engine\", means: \"the Storylet Engine's shared @story properties\" },\n];\n","// ---------------------------------------------------------------------------\n// @storylet-studio/dialect - the storylets expression dialect.\n//\n// One dialect drives runtime eval AND publish-time validation (schema 6.1).\n// Five scopes, fixed (Reboot 3); bare `@name` is `@story.name` (schema 6.2,\n// resolved 2026-07-19). Function eval semantics carry from the old engine's\n// storylets-dialect.ts where the function survives (random, check_flags,\n// set_flags); the play-history functions are the new set pinned in schema 6.3.\n// ---------------------------------------------------------------------------\n\nimport { EvalError } from \"@wildwinter/expr\";\nimport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport { ENGINE_SCOPES } from \"./engine-scopes.js\";\nexport type { EngineScope } from \"./engine-scopes.js\";\nimport type { Dialect, EvalHelpers, ExprNode, ScalarValue } from \"@wildwinter/expr\";\n\n/** turns_since_played / _in when the card / value has never been played. */\nexport const NEVER_PLAYED = 9999;\n\n/**\n * Host callbacks the dialect's functions read from `EvalContext.host`. The\n * runtime supplies these (its PRNG, its play log); the compiler validates\n * without them.\n */\nexport interface StoryletsHost {\n /** One PRNG draw in [0, 1) - the session mulberry32 (schema 3.3). */\n nextRandom(): number;\n /** Plays of the card (gameId) from the play log. */\n countPlayed(card: string): number;\n /** Turns since the card (gameId) last played; NEVER_PLAYED when never. */\n turnsSincePlayed(card: string): number;\n /** Plays of cards belonging to the dimension value (both by gameId). */\n countPlayedIn(dimension: string, value: string): number;\n /** As above; NEVER_PLAYED when never. */\n turnsSincePlayedIn(dimension: string, value: string): number;\n}\n\nconst host = (h: EvalHelpers): Partial<StoryletsHost> =>\n (h.ctx.host ?? {}) as Partial<StoryletsHost>;\n\nconst stringArg = (fn: string, args: ExprNode[], h: EvalHelpers, i: number): string => {\n const v = h.evaluate(args[i]!);\n if (typeof v !== \"string\" || v === \"\") {\n throw new EvalError(`${fn}() argument ${i + 1} must be a non-empty string`);\n }\n return v;\n};\n\n/** Resolve the first argument of check_flags / set_flags to a flag set. */\nconst flagsArg = (fn: string, args: ExprNode[], h: EvalHelpers): string[] => {\n if (args.length === 0) {\n throw new EvalError(`${fn}() requires at least one argument (the flags property)`);\n }\n const v = h.evaluate(args[0]!);\n if (Array.isArray(v)) return v as string[];\n // An unset flags property may surface as false; treat as the empty set\n // (carried from the old dialect). Anything else is a type error.\n if (v === false) return [];\n throw new EvalError(`${fn}() first argument must be a flags property`);\n};\n\n/** The Storylet Engine's own five scope tokens. */\nexport const OWN_SCOPES = [\"story\", \"world\", \"box\", \"deck\", \"hand\"] as const;\n\n/**\n * Other engines' game-wide scopes a card may name, from the family's shared list\n * (expr/family/engine-scopes.json): `@patter.gold` reads Patterplay's shared\n * globals in a game that runs both. On by default, with no project setting, so\n * combining the engines needs no wiring. The compiler lets these through\n * unchecked (the other engine owns its names and types), and the engine reports\n * at run time when the game has not registered one.\n */\nexport const EXTERNAL_SCOPES: readonly string[] = ENGINE_SCOPES\n .map((s) => s.token)\n .filter((t) => !(OWN_SCOPES as readonly string[]).includes(t));\n\nexport const storyletsDialect: Dialect = {\n // A missing property in a PRESENT scope is always an error: every property\n // is declared with a default, so absence means a publish bug, a drifted\n // save, or a foreign scope the host never fed (schema 6.2). The same holds\n // for another engine's scope: `@patter.glod` is a typo, and it says so the\n // first time the card is evaluated rather than quietly reading false.\n scopes: [\n ...OWN_SCOPES.map((token) => ({ token, missing: \"throw\" as const })),\n ...EXTERNAL_SCOPES.map((token) => ({ token, missing: \"throw\" as const })),\n ],\n defaultScope: \"story\",\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 nextRandom = host(h).nextRandom;\n if (!nextRandom) throw new EvalError(\"random() called without a PRNG in context\");\n const a = h.evaluate(args[0]!);\n const b = h.evaluate(args[1]!);\n if (typeof a !== \"number\" || typeof b !== \"number\") {\n throw new EvalError(\"random(a, b) arguments must be numbers\");\n }\n if (!Number.isInteger(a) || !Number.isInteger(b)) {\n throw new EvalError(\"random(a, b) arguments must be integers\");\n }\n const lo = Math.min(a, b);\n const hi = Math.max(a, b);\n return Math.floor(nextRandom() * (hi - lo + 1)) + lo;\n },\n },\n check_flags: {\n minArgs: 1, returnType: \"boolean\", flagDeltaArgs: true,\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const flags = flagsArg(\"check_flags\", args, h);\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") {\n throw new EvalError(\"check_flags() flag args must be +flagName or -flagName\");\n }\n if (arg.sign === \"+\" ? !flags.includes(arg.name) : flags.includes(arg.name)) {\n return false;\n }\n }\n return true;\n },\n },\n set_flags: {\n minArgs: 1, returnType: \"flags\", flagDeltaArgs: true,\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const result = [...flagsArg(\"set_flags\", args, h)];\n for (let i = 1; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.kind !== \"flagdelta\") {\n throw new EvalError(\"set_flags() flag args must be +flagName or -flagName\");\n }\n if (arg.sign === \"+\") {\n if (!result.includes(arg.name)) result.push(arg.name);\n } else {\n const idx = result.indexOf(arg.name);\n if (idx >= 0) result.splice(idx, 1);\n }\n }\n // Sorted so a SAVE is deterministic: the same flags reached by different routes\n // serialise to the same bytes, which keeps save diffs and cross-runtime byte\n // comparisons stable. It is no longer what makes equality work - flags compare\n // as a SET since 2026-09-01 - so this is now about the stored form only, and\n // Patterplay not sorting is a difference that costs nothing.\n return result.sort();\n },\n },\n count_played: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const card = stringArg(\"count_played\", args, h, 0);\n const fn = host(h).countPlayed;\n if (!fn) throw new EvalError(\"count_played() called without a play log in context\");\n return fn(card);\n },\n },\n turns_since_played: {\n minArgs: 1, maxArgs: 1, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const card = stringArg(\"turns_since_played\", args, h, 0);\n const fn = host(h).turnsSincePlayed;\n if (!fn) throw new EvalError(\"turns_since_played() called without a play log in context\");\n return fn(card);\n },\n },\n count_played_in: {\n minArgs: 2, maxArgs: 2, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const dimension = stringArg(\"count_played_in\", args, h, 0);\n const value = stringArg(\"count_played_in\", args, h, 1);\n const fn = host(h).countPlayedIn;\n if (!fn) throw new EvalError(\"count_played_in() called without a play log in context\");\n return fn(dimension, value);\n },\n },\n turns_since_played_in: {\n minArgs: 2, maxArgs: 2, returnType: \"number\",\n eval(args: ExprNode[], h: EvalHelpers): ScalarValue {\n const dimension = stringArg(\"turns_since_played_in\", args, h, 0);\n const value = stringArg(\"turns_since_played_in\", args, h, 1);\n const fn = host(h).turnsSincePlayedIn;\n if (!fn) throw new EvalError(\"turns_since_played_in() called without a play log in context\");\n return fn(dimension, value);\n },\n },\n },\n};\n\n/**\n * The dialect, also accepting the game-wide scopes `tokens` names: every token a game's\n * shared scopes folder declares (`game-scopes/`, patterkit design/shared-scopes.md), so a\n * game's own scope such as `@player` parses where the shared vocabulary alone would not. A\n * token the dialect already has keeps its place and its policy; each new one gets the policy\n * another engine's scope has, since whoever declared it owns its names. With nothing new to\n * add this is `storyletsDialect` itself, so a project with no shared folder compiles exactly\n * as before. For editing and publishing only: a runtime evaluates a compiled bundle, which\n * names its scopes already.\n */\nexport function storyletsDialectWith(tokens: readonly string[]): Dialect {\n const known = new Set(storyletsDialect.scopes.map((s) => s.token));\n const added = [...new Set(tokens)].filter((t) => !known.has(t));\n if (added.length === 0) return storyletsDialect;\n return {\n ...storyletsDialect,\n scopes: [...storyletsDialect.scopes, ...added.map((token) => ({ token, missing: \"throw\" as const }))],\n };\n}\n","// ---------------------------------------------------------------------------\n// Stable, immutable, opaque ids.\n//\n// Generated at creation, never derived from content or position, so an id\n// survives moving, renaming and reordering the thing it names. A short,\n// collision-resistant base-36 token with an optional type prefix that aids\n// debugging but carries no meaning.\n// ---------------------------------------------------------------------------\n\nconst ALPHABET = \"0123456789abcdefghijklmnopqrstuvwxyz\";\n\n/** Generate a new opaque id, e.g. `newId(\"scn\") -> \"scn_8f3kq2z1\"`. */\nexport function newId(prefix = \"\", length = 8): string {\n // Rejection-sample so every alphabet character is equally likely: a plain\n // `byte % 36` over-weights the first four characters.\n const limit = 256 - (256 % ALPHABET.length); // 252\n let token = \"\";\n while (token.length < length) {\n const bytes = new Uint8Array(length * 2);\n globalThis.crypto.getRandomValues(bytes);\n for (const b of bytes) {\n if (b >= limit) continue;\n token += ALPHABET[b % ALPHABET.length];\n if (token.length === length) break;\n }\n }\n return prefix ? `${prefix}_${token}` : token;\n}\n\n/** A url-ish slug from a human label; `\"project\"` when nothing usable is left. */\nexport function slug(name: string, fallback = \"project\"): string {\n const s = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n return s || fallback;\n}\n","// ---------------------------------------------------------------------------\n// Game IDs: the author-editable, host-facing address a game uses for a thing\n// (a Patter scene or block, a Storylet box, deck, card or outcome), distinct\n// from the opaque immutable id. A hyphen-slug, derived from the thing's name\n// until an author pins one.\n//\n// Both families had this rule word for word (Patter's model, and Storylets',\n// adopted from Patter's on 2026-07-20), and Storyletter matches a card to its\n// Patter scene by it, so the two copies must never drift. What each family\n// derives an address FROM (a name; a title, else the id) stays with the family.\n// ---------------------------------------------------------------------------\n\n/**\n * Turn a name into a Game ID: lower case, apostrophes (straight and curly) dropped,\n * every run of anything but a-z, 0-9 and hyphens made one hyphen, repeated hyphens\n * collapsed, and none at either end. Can be empty (`\"!!!\"`, or a name with no ASCII\n * letters or digits); a caller falls back as it sees fit.\n */\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: lower-case letters, digits and hyphens, with no hyphen at either end. */\nexport function isValidGameId(gameId: string): boolean {\n return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(gameId);\n}\n","// ---------------------------------------------------------------------------\n// FNV-1a, 32-bit.\n//\n// A published, fixed algorithm. Not cryptographic: it is for short stable\n// handles and change detection, where the properties that matter are that it is\n// the same everywhere and does not move between runs.\n// ---------------------------------------------------------------------------\n\n/** FNV-1a over a string's UTF-16 code units, as an unsigned 32-bit integer. */\nexport function fnv32(input: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < input.length; i++) {\n h ^= input.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n","// ---------------------------------------------------------------------------\n// Find and replace over authored text.\n// ---------------------------------------------------------------------------\n\nexport interface FindOptions {\n query: string;\n caseSensitive?: boolean;\n wholeWord?: boolean;\n}\n\n/**\n * Build the find matcher, or null for an empty query. Global, so every\n * occurrence in a string is replaced rather than only the first.\n *\n * The query is escaped: a person typing `a.b` means those three characters, not\n * \"a, anything, b\".\n */\nexport function findMatcher(opts: FindOptions): RegExp | null {\n if (!opts.query) return null;\n const esc = opts.query.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const body = opts.wholeWord ? `\\\\b${esc}\\\\b` : esc;\n return new RegExp(body, opts.caseSensitive ? \"g\" : \"gi\");\n}\n\n// ---------------------------------------------------------------------------\n// Find by field value: the Field tab of both families' Find windows\n// (patterkit design/family-parity-2026-10.md item 16). Each app walks its own\n// project for the values (Storylets' card and outcome fields, Patter's\n// gameData) and asks these two what a query means, so `mood = tense` reads the\n// same in both.\n// ---------------------------------------------------------------------------\n\n/** A parsed Field query: a field, and optionally a value it must (`=`) or must\n * not (`!=`) hold. With no `op` the query lists every value of the field. */\nexport interface FieldQuery {\n /** As typed, folded to lower case: `mood`, or `extension.field`. */\n field: string;\n op?: \"=\" | \"!=\";\n /** As typed, trimmed, one pair of surrounding quotes taken off. May be `\"\"`\n * (`mood =` asks for the field left empty). */\n value?: string;\n}\n\n/**\n * `mood`, `mood = tense`, `mood != tense`, `ext.field = \"two words\"`. Null when\n * the text before the operator is not a field name (a letter or `_`, then\n * letters, digits, `_` or `-`; up to three of those joined by dots).\n */\nexport function parseFieldQuery(query: string): FieldQuery | null {\n const m = /^\\s*([a-z_][\\w-]*(?:\\.[a-z_][\\w-]*){0,2})\\s*(?:(!=|=)\\s*(.*?))?\\s*$/i.exec(query);\n if (!m) return null;\n const field = m[1]!.toLowerCase();\n if (m[2] === undefined) return { field };\n let value = m[3]!;\n const q = /^([\"'])(.*)\\1$/.exec(value);\n if (q) value = q[2]!;\n return { field, op: m[2] as \"=\" | \"!=\", value };\n}\n\n/** Does this field name answer the query's? Names compare without case. */\nexport const fieldNameMatches = (name: string, query: FieldQuery): boolean => name.toLowerCase() === query.field;\n\n/**\n * Does a field's value satisfy the query, read by its type (the family's list:\n * text, multiline, number, boolean, enum, flags, quality; `string` is text)?\n * Pass the value that HOLDS: the default when the node sets none, so\n * `mood = calm` finds the cards that leave a calm mood alone.\n *\n * - text, multiline, enum, quality, and any type this does not know: equal,\n * ignoring case and the ends' whitespace\n * - flags: the list includes that flag (`= \"\"` is no flags set)\n * - number: numerically equal (`= 1.0` finds 1)\n * - boolean: `true` or `false`, ignoring case\n *\n * `!=` is the opposite of `=`. A query with no operator matches any value.\n */\nexport function fieldValueMatches(type: string, value: unknown, query: FieldQuery): boolean {\n if (query.op === undefined || query.value === undefined) return true;\n const holds = valueIs(type, value, query.value);\n return query.op === \"=\" ? holds : !holds;\n}\n\nfunction valueIs(type: string, value: unknown, wanted: string): boolean {\n const want = wanted.trim().toLowerCase();\n if (type === \"flags\") {\n const flags = Array.isArray(value) ? value.map((f) => String(f).toLowerCase()) : [];\n return want === \"\" ? flags.length === 0 : flags.includes(want);\n }\n if (type === \"number\") {\n if (want === \"\") return value === undefined || value === null || value === \"\";\n const n = Number(want);\n return !Number.isNaN(n) && typeof value === \"number\" && value === n;\n }\n if (type === \"boolean\") return (value === true && want === \"true\") || (value === false && want === \"false\");\n return fieldValueText(type, value).trim().toLowerCase() === want;\n}\n\n/** A field's value as a row shows it: flags joined with commas, everything\n * else as written, nothing for no value. */\nexport function fieldValueText(type: string, value: unknown): string {\n if (value === undefined || value === null) return \"\";\n if (type === \"flags\" && Array.isArray(value)) return value.join(\", \");\n return typeof value === \"string\" ? value : JSON.stringify(value);\n}\n","// ---------------------------------------------------------------------------\n// @storylet-studio/model - the shape source-of-truth.\n//\n// Transcribes design/storylets-schema.md (bundle, save) and\n// design/storylets-source.md (shards). Entity shapes are generic over their\n// expression representation E: source shards use plain `src` strings\n// (Card<string>), the compiled bundle uses { src, ast } envelopes\n// (Card<Expression>). No behaviour lives here.\n// ---------------------------------------------------------------------------\n\nimport type { Expression, ScalarValue } from \"@wildwinter/expr\";\nimport { gameIdify, isValidGameId } from \"@wildwinter/toolkit\";\n\nexport type { Expression, ScalarValue, AstNode } from \"@wildwinter/expr\";\n\n// --- shared declarations -----------------------------------------------------\n\nexport type PropertyType = \"boolean\" | \"number\" | \"string\" | \"enum\" | \"flags\" | \"quality\";\n\n/** A property declaration: @world / @story / @box / @deck / tag / hand\n * state. A declared property always has a value; referencing an undeclared\n * property is a publish-time error. */\nexport interface PropertyDecl {\n name: string;\n type: PropertyType;\n /** Optional in source: left out, the type's default holds (false, 0, \"\",\n * the first enum value, no flags, the first stage), by the kernel's\n * `defaultFor`. The compiler writes the resolved default onto every\n * declaration in the bundle, so a bundle's declaration always has one. */\n default?: ScalarValue;\n values?: string[];\n /**\n * A quality's ordered ladder of stage names (design/quality.md). Order IS\n * the meaning: `>=` compares by position here, and `advance()` steps along\n * it. The one order-semantic list in the format, accepted as such: it is a\n * declaration, and inserting a stage mid-ladder is the design's whole point.\n */\n stages?: string[];\n /**\n * Host scopes only (`@world`, `@player`, ...; see `HostScopeDecl`). `false`\n * makes the property read-only TO THE STORY: a condition may read it, an\n * outcome that writes it is a compile error. The game still moves it through\n * its resolver; this is the story's statement of intent, not the game's\n * policy. Mirrors Patter's `HostScopeDecl.writable` name for name (Reboot.md\n * 10, ruled 2026-09-03). Ignored on every other scope. Absent = writable.\n */\n writable?: boolean;\n /**\n * The sharing axis (design/flows.md, Patter's flag adopted): is this\n * property's value one world value across all flows, or a copy per flow?\n * It does NOT change reference syntax - sharing is set here, on the\n * declaration, not by a different scope token. Absent = the scope\n * default: `@story` shared; box, deck, hand and tag properties per-flow.\n * On a `@world` declaration the flag is a validation error - `@world` is\n * the game's own state, always engine-level, never per-flow.\n */\n shared?: boolean;\n /**\n * The durability axis (design/engine-server.md 4.2), valid wherever `shared`\n * is valid and orthogonal to it: `shared` says whose value this is WITHIN a\n * run, `durable` says whether the value survives the run at all. A durable\n * shared property is the installation's memory (\"trolls defeated since we\n * opened\"); a durable per-flow one is the player's pocket (visits,\n * allegiance, what they earned).\n *\n * The engine partitions by `shared` alone; this flag only decides what the\n * durable halves carry across a run boundary: `engine.saveDurable()` and\n * `loadDurable` for a shared one, `flow.saveDurable()` and `openFlow`'s\n * `durable` for a per-flow one.\n *\n * On a `@world` declaration the flag is a validation error, for the reason\n * `shared` is: @world is the game's own state, and how long the game keeps it\n * is the game's business.\n */\n durable?: boolean;\n purpose?: string;\n}\n\n/**\n * A property of a host scope (`@world`, `@player`, ...): the game's own state,\n * declared by the project. The same shape as Patter's `HostScopeDecl` and the\n * `@wildwinter/scoperegistry` `ScopeDeclaration`. `default` seeds the\n * standalone engine's self-backed bag; `writable: false` makes the property\n * read-only to the story (an outcome may not write it), never to the game.\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). */\n purpose?: string;\n}\n\n/** One scope (`token`) in a project's host-scope registry. Patter's shape. */\nexport interface HostScopeSpec {\n /** The scope token after `@` (`\"world\"`, `\"player\"`). Never one of the\n * Storylet Engine's own (`story`, `box`, `deck`, `hand`). */\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/** A project's host-scope registry: every scope the game provides itself.\n * Structurally scoperegistry's `ScopeRegistrySpec`, and Patter's. */\nexport interface HostScopeRegistry {\n version: number;\n scopes: HostScopeSpec[];\n}\n\n/** The `world` block of a bundle @1 or @0, and of a project shard before\n * host scopes: @world's declarations, the one game scope there was. */\nexport interface LegacyWorldBlock {\n properties: PropertyDecl[];\n /** Never read. */\n registry?: unknown;\n}\n\n/**\n * The host scopes a bundle or a project shard declares, in order: its\n * `scopeRegistry`'s, or, for a bundle @1 or @0 and a project not yet\n * formatted, its `world` block read as the scope `world` (none when that block\n * declares nothing, which is what an empty `world` always meant).\n */\nexport function bundleHostScopes(of: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }): HostScopeSpec[] {\n if (of.scopeRegistry !== undefined) return of.scopeRegistry.scopes;\n const legacy = of.world?.properties;\n return legacy !== undefined && legacy.length > 0 ? [{ token: \"world\", declarations: legacy }] : [];\n}\n\n/** One host scope by token, case-sensitive, from `bundleHostScopes`. */\nexport function hostScopeSpec(of: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }, token: string): HostScopeSpec | undefined {\n return bundleHostScopes(of).find((s) => s.token === token);\n}\n\n/** A host scope's declarations by token (empty when it declares none, is\n * opaque, or isn't there). `hostScopeDecls(bundle, \"world\")` is what\n * `bundle.world.properties` was. */\nexport function hostScopeDecls(of: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }, token: string): HostScopeDecl[] {\n return hostScopeSpec(of, token)?.declarations ?? [];\n}\n\n/**\n * A project shard with one host scope's declarations replaced (the scope\n * added, last, when it is new), in place: the legacy `world` block moves into\n * `scopeRegistry` first, so a shard is never written with both. The scope's\n * own `writable` is kept.\n */\nexport function setHostScopeDecls(shard: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }, token: string, declarations: HostScopeDecl[]): void {\n migrateLegacyWorld(shard);\n const registry = shard.scopeRegistry ?? { version: 1, scopes: [] };\n const at = registry.scopes.findIndex((s) => s.token === token);\n if (at >= 0) registry.scopes[at] = { ...registry.scopes[at]!, declarations };\n else registry.scopes.push({ token, declarations });\n shard.scopeRegistry = registry;\n}\n\n/**\n * Move a project shard's legacy `world` block into `scopeRegistry`, in place\n * (what `storyletengine format` does): `world.properties` becomes the scope\n * `world`, its `shared` and `durable` flags dropped, since neither means\n * anything on a game's scope. An empty block just goes. Returns whether\n * anything moved.\n */\nexport function migrateLegacyWorld(shard: { scopeRegistry?: HostScopeRegistry; world?: LegacyWorldBlock }): boolean {\n if (shard.world === undefined) return false;\n const properties = shard.world.properties ?? [];\n delete shard.world;\n if (properties.length === 0 || shard.scopeRegistry?.scopes.some((s) => s.token === \"world\")) return true;\n const declarations: HostScopeDecl[] = properties.map((d) => {\n const { shared: _shared, durable: _durable, ...rest } = d;\n return rest;\n });\n shard.scopeRegistry = {\n version: shard.scopeRegistry?.version ?? 1,\n scopes: [{ token: \"world\", declarations }, ...(shard.scopeRegistry?.scopes ?? [])],\n };\n return true;\n}\n\n/**\n * A field's type: the family's one list, shared with Patter's game data\n * (patterkit design/family-parity-2026-10.md item 9). `text` is the spelling;\n * `string` is read as a synonym, the spelling fields had before 2026-10, and\n * means exactly what `text` does. `multiline` is text a writer expects to run\n * to more than one line. A `quality` field is a stage of its own ladder\n * (`stages`), compared by position by a host, never by the engine.\n */\nexport type FieldType = \"text\" | \"multiline\" | \"string\" | \"number\" | \"boolean\" | \"enum\" | \"flags\" | \"quality\";\n\n/** Every field type, in the order an editor offers them (`string` left out:\n * it is read, never offered). */\nexport const FIELD_TYPES: readonly FieldType[] = [\"text\", \"multiline\", \"number\", \"boolean\", \"enum\", \"flags\", \"quality\"];\n\n/** Is this field type text of either shape (`text`, `multiline`, `string`)? */\nexport const isTextFieldType = (type: string): boolean => type === \"text\" || type === \"multiline\" || type === \"string\";\n\n/** A template field (box-defined), of the card template or of the outcome\n * fields. Data for the host; the engine never interprets fields and they are\n * not addressable from expressions. The bundle carries the declaration as\n * written, `string` included. */\nexport interface FieldDecl {\n name: string;\n type: FieldType;\n /** Optional in source: left out, the type's default holds, as for a\n * property, and the compiler writes it into the bundle. When given: for an\n * enum, one of `values` or `\"\"` (none chosen); for flags, a subset of\n * `values`; for a quality, one of `stages`. */\n default?: ScalarValue;\n values?: string[];\n /** A quality's ordered ladder, by the family's one rule (at least two\n * stages, none empty, none twice; `@wildwinter/expr`). */\n stages?: string[];\n purpose?: string;\n}\n\n/** Cooldown policy, in turns (schema 3.4). */\nexport type RedrawPolicy = \"always\" | \"never\" | number;\n\n// --- gameId derivation (Patter's effectiveGameId, adopted 2026-07-20) --------\n//\n// gameId is the renameable host-facing address; it is OPTIONAL in source and\n// derived from the entity's title until the author pins one, so a rename of\n// the title carries the address with it (no \"new-deck\" stuck placeholder).\n// The compiler fills a concrete gameId into every bundle entity.\n\n// `gameIdify` (a human label slugified into a filename- and address-safe gameId) and\n// `isValidGameId` are the family's one rule, shared with Patter from `@wildwinter/toolkit`\n// (step 6, Q19), and re-exported here so this package's API is unchanged.\nexport { gameIdify, isValidGameId };\n\n// --- property names (adopted 2026-08-18, with Patter, from one rule) ---------\n//\n// Design and argument: `@wildwinter/app-shell` src/property-names.ts. Not house\n// style: the rule is what `@wildwinter/expr` can parse. Its lexer takes an\n// identifier as /[a-zA-Z_][a-zA-Z0-9_]*/ and folds it to lower case, so\n// `@story.isNight` reaches a property called `isnight`, `@story.9lives` and\n// `@story.not` are parse errors, and `@story.is-night` is not an error at all: it\n// compiles to `@story.is` MINUS the string \"night\". That last one is why the rule\n// is enforced rather than trusted - it is the only violation that silently means\n// something else.\n//\n// Here rather than behind an import of the UI kit because the compiler, the CLI\n// and the embedded runtime resolve state by these. `property-name-parity.test.ts`\n// holds them to the shell's, and `property-name-grammar.test.ts` holds them to the\n// parser they came from.\n\n/** The words `@wildwinter/expr` lexes as keywords, so no property may be called one. */\n// A legal property NAME is a fact about the expression language, not about this\n// model: `not` is reserved because the tokeniser reads it as an operator. Both\n// families kept their own copy of the rule AND of the keyword list, a list\n// neither owned. @wildwinter/expr derives the list from its own tokeniser, so a\n// keyword added there cannot leave a stale copy here.\n//\n// Re-exported so nothing that imports them has to move.\nexport {\n propertyNameify, isValidPropertyName, isCaseOnlyPropertyName, RESERVED_PROPERTY_NAMES,\n} from \"@wildwinter/expr\";\n/** The effective address: a pinned gameId, else derived from the title, else\n * the immutable id (so there is always something addressable). */\nexport function effectiveGameId(entity: { gameId?: string; title?: string; id: string }): string {\n const pinned = entity.gameId?.trim();\n if (pinned) return pinned;\n const fromTitle = entity.title ? gameIdify(entity.title) : \"\";\n return fromTitle || entity.id;\n}\n\n// --- the value scope's owner segment (design/engine-server.md 4.4) -----------\n//\n// Every other owned scope names its owner with a gameId that is unique across\n// the bundle: box, deck, hand and card. A TAG's gameId is unique only within\n// its group, and a group's only within its box, so two boxes may each name a\n// tag \"docks\" - as ordinary as two boxes each having a \"zone\" group - and\n// `value.docks.danger` then names two stores.\n//\n// So the value scope's owner segment is box-qualified where it has to be:\n// `value.<boxGameId>/<tagGameId>.<name>`. The slash sits INSIDE the owner\n// segment, so the address still splits into three on the dot and no parser\n// changes shape. The qualified form is always accepted; the short form is\n// accepted while exactly one tag in the bundle carries that gameId, and\n// refused when more do, naming the qualified candidates. What a runtime\n// PRINTS - `listProperties`, a write on the trace, a load report, an\n// examiner - is the short form except where the gameId repeats.\n//\n// One definition, because the Board draws these addresses from the bundle\n// while the engine builds them from its own index, and an address the editor\n// shows that the engine will not take is the fault 4.4 was fixing.\n\n// A ZONE of the project map (design/project-map-contract.md 3.4) is the one\n// tag with no qualified form: it belongs to no box, so it prints and is\n// accepted as `value.<zoneGameId>.<name>` whichever boxes use it. Nothing can\n// make that ambiguous, because no box tag may share a zone's gameId (refused\n// by the compiler and again by the engine at load). A box-qualified form that\n// names a zone is REFUSED rather than accepted for old times' sake: there is\n// no old project to be kind to, and the qualified form would assert a box the\n// zone does not have.\n\n/** The answers a value address needs, all derived from the bundle. */\nexport interface ValueAddresses {\n /** Tag internal id -> the owner segment an address PRINTS for it. */\n print: Map<string, string>;\n /** Every owner segment a value address ACCEPTS -> the tag's internal id.\n * Holds the qualified form for every box tag, the short form only for a\n * gameId no other box tag shares, and the short form of every zone. */\n accept: Map<string, string>;\n /** A tag gameId more than one box uses -> its qualified forms, in bundle\n * order. Empty for the overwhelming majority of projects, and what a\n * refusal lists. Never holds a zone. */\n repeated: Map<string, string[]>;\n /** A box-qualified segment that names a project-map zone\n * (`\"box/quay\"`) -> the zone's own segment (`\"quay\"`), the one an\n * address must use instead. One entry per box and zone, so a refusal can\n * name the address that works without working it out again. Empty when\n * the bundle has no map. */\n zoneQualified: Map<string, string>;\n}\n\n/** The owner segment of every tag in the bundle, both ways round: the boxes'\n * tags, then the project map's zones. */\nexport function valueAddresses(bundle: {\n boxes: readonly { id: string; gameId?: string; title?: string; tagGroups: readonly TagGroup[] }[];\n map?: { group: TagGroup };\n}): ValueAddresses {\n const tags: { id: string; gameId: string; qualified: string }[] = [];\n for (const box of bundle.boxes) {\n const boxGameId = effectiveGameId(box);\n for (const group of box.tagGroups) {\n for (const tag of group.tags) {\n const gameId = effectiveGameId(tag);\n tags.push({ id: tag.id, gameId, qualified: `${boxGameId}/${gameId}` });\n }\n }\n }\n // Distinct qualified forms per gameId. Distinct rather than a count: two\n // groups in ONE box may also name a tag the same way, and a refusal that\n // offered the same address twice would be no help at all. Those two share\n // the qualified segment, and the first in bundle order answers to it, which\n // is what the short form did for everything before this rule.\n //\n // That last case is closing at the source rather than here (question 16,\n // ruled 2026-09-06): the compiler WARNS that a tag gameId must be unique\n // within its box, across all of that box's groups, and refuses it from the\n // next release. So this stays as the reading rule for a bundle built before\n // that, and a bundle built after it has no repeated qualified form to read.\n const forms = new Map<string, string[]>();\n for (const tag of tags) {\n const list = forms.get(tag.gameId) ?? [];\n if (!list.includes(tag.qualified)) list.push(tag.qualified);\n forms.set(tag.gameId, list);\n }\n const print = new Map<string, string>();\n const accept = new Map<string, string>();\n const repeated = new Map<string, string[]>();\n const zoneQualified = new Map<string, string>();\n for (const tag of tags) {\n const candidates = forms.get(tag.gameId) ?? [tag.qualified];\n const ambiguous = candidates.length > 1;\n print.set(tag.id, ambiguous ? tag.qualified : tag.gameId);\n if (!accept.has(tag.qualified)) accept.set(tag.qualified, tag.id);\n if (!ambiguous && !accept.has(tag.gameId)) accept.set(tag.gameId, tag.id);\n if (ambiguous) repeated.set(tag.gameId, candidates);\n }\n // The zones, after the box tags and never in `repeated`. A zone whose\n // gameId a box tag also uses is a bundle the engine refuses at load; should\n // one reach here anyway (the Board reads bundles the engine never loaded),\n // the box tag keeps the short form it already had and the zone does not\n // take it over silently.\n for (const tag of bundle.map?.group.tags ?? []) {\n const gameId = effectiveGameId(tag);\n print.set(tag.id, gameId);\n if (!accept.has(gameId)) accept.set(gameId, tag.id);\n for (const box of bundle.boxes) zoneQualified.set(`${effectiveGameId(box)}/${gameId}`, gameId);\n }\n return { print, accept, repeated, zoneQualified };\n}\n\n/** What a box-qualified address naming a project-map zone is told: why the\n * form is wrong, and the address that works (design/project-map-contract.md\n * 3.4). */\nexport function zoneQualifiedValueAddressMessage(segment: string, zone: string, name: string): string {\n return `\"value.${segment}.${name}\": \"${zone}\" is a zone of the project map, which belongs to no box; `\n + `write \"value.${zone}.${name}\"`;\n}\n\n/** What an ambiguous short-form value address is told: the candidates, in\n * full, because \"that names two tags\" without them leaves a host reading a\n * bundle it did not write to find out which boxes. */\nexport function ambiguousValueAddressMessage(\n segment: string, name: string, candidates: readonly string[],\n): string {\n const forms = candidates.map((q) => `\"value.${q}.${name}\"`);\n const list = forms.length <= 1 ? (forms[0] ?? \"\")\n : `${forms.slice(0, -1).join(\", \")} or ${forms[forms.length - 1]}`;\n return `\"value.${segment}.${name}\" names a tag in ${candidates.length} boxes; write ${list}`;\n}\n\n/**\n * The first free gameId of the form `base`, `base-2`, `base-3`, ... not already\n * in `taken`.\n *\n * A gameId is API - `deal()` and the play log speak it - so a name minted for a\n * new or duplicated entity must not collide with an existing one. This lived in\n * two copies, one in the editor and one in the CLI's kit scaffolder, character\n * for character the same; two copies of an addressing rule can drift, and a\n * drift here means the same act produces different addresses depending on which\n * program did it. It is here, beside `gameIdify`, because both programs need it\n * and no UI touches it.\n */\nexport function freeGameId(base: string, taken: ReadonlySet<string>): string {\n let gameId = base;\n for (let n = 2; taken.has(gameId); n++) gameId = `${base}-${n}`;\n return gameId;\n}\n\n/**\n * The first free TITLE of the form `base`, `base 2`, `base 3`, ... whose\n * derived gameId is not already in `taken`.\n *\n * The sibling of `freeGameId` for the \"New box\", \"New deck\" case, where the\n * author is given a title and the address follows from it. The dedupe is on the\n * DERIVED gameId rather than the title, because two titles that slug to one\n * address are the collision that matters.\n */\n/**\n * An id-sorted collection in the order a person should SEE it.\n *\n * Storage is sorted by immutable id (source rule 5) so that two authors adding\n * one item each never touch the same line. That makes array position useless as\n * display order, so the order the author arranged rides in a sparse `order`\n * field, with position as the fallback and id to break a tie.\n *\n * One definition, because this rule has to give the same answer in four places\n * that a reader compares side by side: the compiler (what the bundle carries),\n * the editor (what the card document lists), the exports, and Find. When they\n * disagree, the editor shows one order and the game plays another.\n */\nexport function byDisplayOrder<T extends { id?: string; order?: number }>(items: readonly T[]): T[] {\n return items\n .map((x, i) => ({ x, key: x.order ?? i, i }))\n .sort((a, b) => a.key - b.key || ((a.x.id ?? \"\") < (b.x.id ?? \"\") ? -1 : (a.x.id ?? \"\") > (b.x.id ?? \"\") ? 1 : a.i - b.i))\n .map((e) => e.x);\n}\n\nexport function freeTitle(base: string, taken: ReadonlySet<string>): string {\n let title = base;\n for (let n = 2; taken.has(gameIdify(title)); n++) title = `${base} ${n}`;\n return title;\n}\n\n/**\n * A count of a TIMED box's turns, said as time (design/engine-server.md 4.8):\n * `turnSpan(30, 60)` is \"30 min\", and `turnSpan(30, 60, true)` is \"30 minutes\".\n *\n * One definition, because the conversion appears wherever a designer might\n * otherwise have to do it in their head: the card editor's Redraw field, the\n * box page, the Board's advance buttons, and the coverage report's turn\n * budget. Two of those want the unit spelled out and two want it short, which\n * is the whole of `long`.\n */\nexport function turnSpan(turns: number, seconds: number, long = false): string {\n const total = Math.max(0, Math.round(turns * seconds));\n const say = (n: number, short: string, one: string, many: string): string =>\n long ? `${n} ${n === 1 ? one : many}` : `${n} ${short}`;\n if (total < 60) return long ? say(total, \"s\", \"second\", \"seconds\") : `${total}s`;\n if (total < 3600) {\n const minutes = total % 60 === 0 ? total / 60 : Math.round(total / 6) / 10;\n return say(minutes, \"min\", \"minute\", \"minutes\");\n }\n let hours = Math.floor(total / 3600);\n let rest = Math.round((total % 3600) / 60);\n if (rest === 60) { hours += 1; rest = 0; }\n const said = say(hours, \"hr\", \"hour\", \"hours\");\n return rest === 0 ? said : `${said} ${say(rest, \"min\", \"minute\", \"minutes\")}`;\n}\n\n// --- entities (generic over expression representation E) ---------------------\n\nexport interface Outcome<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Unlike `Card.order` this one IS compiled into the bundle:\n * which option is offered first is authorial, and a host reading a dealt\n * card's outcomes is building the player's menu. */\n order?: number;\n /** Gating; availability is always evaluated against current state. */\n condition?: E;\n /** Target (\"@scope.name\") -> expression; all right-hand sides evaluate\n * against pre-play state (schema 3.7). */\n changes: Record<string, E>;\n /** Template data, as `Card.fields` is: field name -> value, declared by\n * the box's `outcomeFields`, validated at publish, and handed to the host\n * with the outcome. The engine never reads it: a press can say one line\n * (\"The notice is in your pocket\") without spending a card on it. */\n fields?: Record<string, ScalarValue>;\n}\n\n/** A box's defaults for its cards' dealing rules (its Card template). */\nexport interface CardDefaults {\n redraw?: RedrawPolicy;\n weight?: number;\n inOneHand?: number;\n acrossHands?: \"one\" | \"any\";\n}\n\nexport interface CardFields<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order within the deck (sparse; a card without one falls\n * back to its id position). Merges as a per-card value, so id-sorted storage\n * stays merge-clean (Reboot 7.4); dropped from the compiled bundle. */\n order?: number;\n condition?: E;\n /** Default 0; an expression must evaluate to a number. */\n priority: number | E;\n /** Always present in a bundle. A source card may leave it out, and then\n * takes its box's Card template default (or \"always\"). */\n redraw?: RedrawPolicy;\n /** Tags: tag group id -> tag ids. An absent group is a wildcard (matches\n * any binding of it), except the reserved home group, whose default\n * inverts (schema 2.4). Editors and fixtures speak gameIds; stored\n * references are ids. */\n tags?: Record<string, string[]>;\n /** How often this card comes up beside cards of equal rank: a positive\n * number, default 1 (the hands-on review, 2026-10: weights in place of\n * Copies). Priority, then specificity, still decide between ranks; within\n * a run of equal cards a card of weight 2 is drawn ahead twice as often as\n * one of weight 1. A run whose weights are all equal shuffles exactly as it\n * always has. */\n weight?: number;\n /** How many slots of ONE hand this card may fill: integer >= 1, default 1\n * (an encounter comes up once; a pickup or a piece of equipment can be up\n * to 3). */\n inOneHand?: number;\n /** Whether several hands may hold this card at once, within a flow:\n * \"one\" (the default, a one-off can't be offered in two places) or \"any\"\n * (interchangeable filler). Replaces `copies`, which a shard from before it\n * may still hold: the compiler refuses it, and `storyletengine format`\n * rewrites it as this (1 is \"one\", more is \"any\"). */\n acrossHands?: \"one\" | \"any\";\n /** Scarcity across flows (design/shared-scarcity.md). Absent takes the\n * deck's flag; set here it overrides the deck, so a single unique card can\n * stay in the content it belongs to. A shared card's claims count every\n * flow's board, and a shared `redraw: \"never\"` is spent for everyone the\n * first time anyone plays it.\n *\n * A finite `redraw` stays PER FLOW even when shared: a cooldown is an\n * absolute turn of the card's box clock and clocks are per flow, so\n * \"3 turns of whose clock?\" has no answer. A world-wide timer is a @world\n * question, not an engine one (shared-scarcity 9.3.3). */\n shared?: boolean;\n /** The world cap: how many hands ACROSS EVERY FLOW may hold this at once.\n * Read only when the card is shared, and defaults to the per-flow limit\n * (one hand, or no limit under `acrossHands: \"any\"`), so \"five in the\n * world, one to a customer\" is `sharedCopies: 5`. */\n sharedCopies?: number;\n /** Does this card's `redraw: \"never\"` spend survive the run\n * (design/engine-server.md 4.2)? Absent takes the deck's flag, set here it\n * overrides the deck, exactly as `shared` does. `shared` decides who a\n * spend counts for WITHIN a run; this decides whether it outlives one.\n *\n * Only `\"never\"` crosses the run boundary, for the reason only `\"never\"`\n * crosses the flow boundary (shared-scarcity 9.3.2): a finite cooldown is\n * an absolute turn of a box clock, and the clock resets with the run. On\n * any other redraw the flag is a compile warning.\n *\n * Like the declaration flag, it only decides what the durable halves carry:\n * a per-flow spend rides the flow's half (its `never` cooldown), a shared\n * one the engine's half (its spent set), each by card gameId. */\n durable?: boolean;\n /** Card-template data: field name -> value, validated at publish. */\n fields?: Record<string, ScalarValue>;\n outcomes: Outcome<E>[];\n}\n\n/** A bundle card from before `acrossHands`: the one field a runtime still\n * reads for it. How many hands of a flow may hold the card at once (schema\n * 3.5), integer >= 1, default 1, read only when `acrossHands` is absent. Never\n * written any more, and not part of the source model: a shard that still says\n * `copies` is refused by the compiler and rewritten by `storyletengine format`. */\nexport interface LegacyBundleCard {\n /** @deprecated Read from an old bundle only; see `LegacyBundleCard`. */\n copies?: number;\n}\n\n/** A card. Source cards hold their expressions as strings (`Card<string>`);\n * bundle cards as `{ src, ast }` envelopes (`Card<Expression>`), which alone\n * may carry an old bundle's `copies`. */\nexport type Card<E> = CardFields<E> & (E extends string ? unknown : LegacyBundleCard);\n\nexport interface Deck<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** The deck gate, evaluated once per draw in the draw's environment. */\n condition?: E;\n /** This pile is scarce across flows (design/shared-scarcity.md): every card\n * in it is shared unless the card says otherwise. The container is where\n * Patter puts its own shared-memory flag, and the deck is our container. */\n shared?: boolean;\n /** Every `redraw: \"never\"` card in this pile is spent for good, past the end\n * of the run, unless the card says otherwise (design/engine-server.md 4.2).\n * The container carries the flag for the reason `shared` is carried here:\n * a pile is what an author reaches for when a rule is true of all of it. */\n durable?: boolean;\n properties: PropertyDecl[];\n cards: Card<E>[];\n}\n\nexport interface Tag {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n /**\n * This tag's own starting values for properties its GROUP declares\n * (design/hand-typing.md). The group says what the property IS; a tag says\n * only where it starts, so \"every zone has a haunting level\" is written once\n * and \"the cave starts at 2\" is written where it belongs.\n *\n * A name here that the group does not declare is an error: it would be a\n * value for nothing.\n */\n values?: Record<string, ScalarValue>;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Merges as a per-item value, so id-sorted storage stays\n * merge-clean (Reboot 7.4). */\n order?: number;\n properties?: PropertyDecl[];\n /** Template-of-play extras (e.g. spatial geometry). Source only: preserved\n * in shards, never compiled into the bundle. */\n templates?: Record<string, unknown>;\n}\n\n/** A named axis for cross-cutting cards (schema 2.4, renamed from\n * Dimension). Tags are declared, not freeform. */\nexport interface TagGroup {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n purpose?: string;\n /**\n * A property reference (`\"@story.act\"`) whose value names a tag in this group\n * by gameId. The engine reads it at every ask and binds the group, exactly as\n * if the asking hand had chosen that tag; a hand's own binding wins.\n *\n * For an axis driven by STATE rather than by place: acts, chapters, a\n * difficulty band. Without it, only a hand can bind a group, so such an axis\n * had nowhere to gate and every card needed its own condition.\n *\n * A reference rather than an expression on purpose (design/where-and-\n * selectors.md Part B): a computed binding belongs in a property the outcomes\n * maintain, and an expression here would make this type generic for no gain.\n */\n boundBy?: string;\n /**\n * What omitting this group means for a card. Default false: omission is a\n * wildcard, so the card matches whatever the group is bound to. True inverts\n * it, so a card that names no tag here is unavailable wherever the group IS\n * bound (and unaffected where it is not).\n *\n * `place` is the built-in instance of this pair: bound to the asking hand,\n * and inverted per card rather than per group.\n */\n required?: boolean;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Merges as a per-item value, so id-sorted storage stays\n * merge-clean (Reboot 7.4). */\n order?: number;\n /**\n * Properties EVERY tag in this group has (design/hand-typing.md). The\n * declaration lives here and each tag carries only its own starting value in\n * `Tag.values`, which is the separation the format was missing: a tag's own\n * `properties` entry has to restate the type on every tag purely in order to\n * say the value, and a tag added later silently arrives without it.\n *\n * Compiled by FLATTENING onto each tag, so the bundle keeps its per-tag\n * shape and no runtime, port or bundle schema changes: source is where the\n * author works and where merges happen, the bundle is a compiled artefact\n * that can afford to be explicit.\n *\n * A tag may still declare its own `properties` for a group whose tags\n * genuinely differ. Declaring the same NAME both ways is an error.\n */\n properties?: PropertyDecl[];\n tags: Tag[];\n /** Template-of-play extras for the GROUP, the same bag its tags carry: this is\n * where a group is marked spatial and where that template keeps its own\n * group-level configuration. Source only, preserved but never compiled.\n *\n * A bag rather than a `spatial: true` flag because the marker and the\n * configuration are one thing (see model/spatial.ts), and because core is not\n * meant to grow a field per template of play. */\n templates?: Record<string, unknown>;\n}\n\n/** The reserved tag group (schema 2.4): present in every box without\n * declaration, its tags the box's hand ids. Every hand implicitly binds it to\n * itself; a card that names a place is available only at that place.\n *\n * Called `place` rather than `home` since 2026-08-21: one word for one thing\n * across the format, the editor and the exports. \"Where\" is the QUESTION a\n * card answers (at a place, or anywhere in a region); \"place\" is the direct\n * half of that answer. `home` was a metaphor an author had to learn, and it\n * leaked into hand-edited shards and the docs. */\nexport const PLACE_GROUP = \"place\";\n\n/** The scopes a movable hole may be filled from (design/engine-server.md 4.6):\n * the two `boundBy` already allows, plus `@hand` - the asking hand's OWN\n * declared property, resolved before tag composition so a movable hole can\n * never depend on the tags it is choosing. */\nexport type HoleRefScope = \"hand\" | \"story\" | \"world\";\n\n/** A parsed hole reference: `@hand.zone` -> `{ scope: \"hand\", name: \"zone\" }`. */\nexport interface HoleRef {\n scope: HoleRefScope;\n name: string;\n}\n\nconst HOLE_REF = /^@(hand|world|story)\\.([a-z][a-z0-9_-]*)$/;\n\n/**\n * Is this `chosen` / binding value MEANT as a property reference rather than a\n * tag id?\n *\n * The test is the leading `@` alone, deliberately: a value that starts with\n * one and does not parse is a mistyped reference, which the compiler should\n * name as such, not a tag id that happens to look odd. Tag ids never begin\n * with `@`.\n */\nexport const isHoleRef = (value: string): boolean => value.startsWith(\"@\");\n\n/** Parse a hole reference, or undefined when it is not one. The on-disk form\n * stays a plain string, so the canonical serialiser and the shard merge need\n * no change at all: a hole is still one group name against one value. */\nexport const parseHoleRef = (value: string): HoleRef | undefined => {\n const m = HOLE_REF.exec(value);\n return m === null ? undefined : { scope: m[1] as HoleRefScope, name: m[2]! };\n};\n\n/** A declared kind of hand (schema 2.6): live-inherited, author-side only,\n * never called from game code. One condition governs every instance. */\nexport interface HandTemplate<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order (sparse; one without it falls back to its id\n * position). Merges as a per-item value, so id-sorted storage stays\n * merge-clean (Reboot 7.4). */\n order?: number;\n\n /** Fixed tag bindings: tag group id -> tag id. Literal tags only: what a\n * template FIXES is the same for every instance, and a hole that moves is\n * the instance's own business (`Hand.chosen`, 4.6). */\n bindings?: Record<string, string>;\n /** The holes: tag group ids each instance fills (one tag each, or one\n * property reference: 4.6). */\n chooses?: string[];\n /** Shared availability condition, ANDed in (schema 3.1); evaluated per\n * instance against that instance's composed @hand. */\n condition?: E;\n /** Default slot cap. */\n slots: number | \"unbounded\";\n /** Declared @hand state every instance carries. */\n properties: PropertyDecl[];\n}\n\n/** A standalone hand's inline rule (schema 2.6): owned by the hand. */\nexport interface HandRule<E> {\n /**\n * Tag group id -> tag id, or a PROPERTY REFERENCE (`\"@hand.zone\"`,\n * `\"@story.where\"`, `\"@world.place\"`) the runtime resolves at ask time\n * (design/engine-server.md 4.6, the hand that moves). Still a plain string\n * on disk, so the canonical serialiser and the merge are untouched; what\n * widened is the meaning, and `parseHoleRef` is where it is read.\n *\n * `place` is never fillable this way: it is the hand's own name, not an axis.\n */\n bindings?: Record<string, string>;\n condition?: E;\n slots: number | \"unbounded\";\n}\n\n/** A hand (schema 2.6): a template instance (template + chosen) or a\n * standalone hand (rule). Exactly one of template / rule. Fully concrete:\n * deal is name-only. */\nexport interface Hand<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n /** The name deal() is called with; a rename is a breaking change\n * (Reboot 7.4). */\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Hand template id (not gameId). */\n template?: string;\n /**\n * Template instances: tag group id -> tag id, one per `chooses` hole.\n *\n * A value may instead be a PROPERTY REFERENCE (`\"@hand.zone\"`,\n * `\"@story.where\"`, `\"@world.place\"`), which makes the hole MOVABLE: the\n * runtime resolves the reference at ask time and binds the hole to the tag\n * the value names, so moving the Elder to the forest is `setProperty` and\n * nothing else (design/engine-server.md 4.6). Still a plain string on disk,\n * so the canonical serialiser and the shard merge need no change; read it\n * with `parseHoleRef`.\n */\n chosen?: Record<string, string>;\n /** Standalone hands: the inline rule. */\n rule?: HandRule<E>;\n /** Override; defaults to the template's / rule's slots. The ONLY template\n * field an instance may override (schema 2.6). */\n slots?: number;\n /** Standalone hands' own @hand state (template instances inherit the\n * template's declarations). */\n properties?: PropertyDecl[];\n /** Authored display order within the box (sparse; authoring-only, never\n * compiled into the bundle - the compiler's explicit field list drops it). */\n order?: number;\n /** Template-of-play extras (e.g. a spatial pin). Source only. */\n templates?: Record<string, unknown>;\n}\n\nexport interface Box<E> {\n /** Bundle only: this node's extension values, by extension id, raw as\n * authored (editor-only fields stripped). Source keeps them in extension\n * shards, never here. Absent when the node has none. */\n extensions?: NodeExtensions;\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** The only per-box ranking policy (Reboot 2.2). */\n ranking: { specificity: boolean };\n /**\n * Who moves the box's clock. Absent is the ordinary box, whose clock a\n * play moves. Present, the GAME moves it: with `seconds` it is a TIMED box,\n * counting real time, one turn every `seconds` of the run\n * (design/engine-server.md 4.8); without, it is a calendar or any clock the\n * game ticks on its own beat (the hands-on review, 2026-10: the three-way\n * box clock).\n *\n * Two things follow, and only two. In the ENGINE, a play in this box\n * defaults to advancing nothing: `settings.playAdvancesTurns` does not\n * apply, so a designer cannot declare the convention and then forget to\n * switch play-advance off. Everywhere else it is what the tools SAY: the\n * host ticks the box (the runtime has no clock and gains none here), and a\n * card's `redraw: N` reads as N x `seconds`, which the editors, the bundle\n * inspectors and the coverage report spell out rather than leaving a\n * designer to know that 30 meant minutes.\n *\n * The number itself is inert to the runtime, which never reads it.\n */\n turn?: { seconds?: number };\n /** The card template: what every card in this box carries. */\n fields: FieldDecl[];\n /** What every outcome in this box may carry, declared the same way.\n * Absent when the box declares none, so a bundle without them is byte for\n * byte what it was. */\n outcomeFields?: FieldDecl[];\n properties: PropertyDecl[];\n /**\n * The box has opted in to the PROJECT MAP (design/project-map-contract.md\n * 1.2, 2.2): its hands may bind the map's zone group and its cards may be\n * tagged with it, and it sees the group's name beside its own groups' names.\n * Absent is \"not on the map\", and a box that is not may not reference the\n * group at all: the engine refuses such a bundle at load.\n *\n * Opting in changes nothing about what the box deals FROM: a hand still\n * deals only from its own box's decks. What the boxes on the map share is\n * the zones' values (one bag per zone), never their cards or their history.\n */\n usesMap?: true;\n /** The box's OWN tag groups. The project map's group is never here, even in\n * a box that uses it: it is the bundle's, in `Bundle.map` (`groupsOfBox`\n * gives the two together). */\n tagGroups: TagGroup[];\n decks: Deck<E>[];\n handTemplates: HandTemplate<E>[];\n hands: Hand<E>[];\n}\n\n// --- the compiled bundle (.storyletsc) ---------------------------------------\n\n/**\n * The bundle schema this build of the format writes: @2 since host scopes\n * (2026-10-28), the bundle whose game scopes are `scopeRegistry` and which has\n * no `world` block. @1 was the project map's (design/project-map-contract.md\n * 2.4), the bundle that can carry `map` and `Box.usesMap`.\n *\n * @2 is a new number because the `world` block is GONE, not added to: a runtime\n * that reads only @1 would reach for `world.properties` and fail as a raw\n * error, where a schema it does not know is refused by name.\n *\n * Every runtime READS @0, @1 and @2 and refuses anything else, by name. Reading @0\n * is not a compatibility branch: an @0 bundle cannot carry a map, so it is the\n * same shape with less in it. The check exists so that from this release on a\n * runtime meeting a schema it does not know says so, rather than half-working\n * the way an @0 runtime does on a map bundle (tag matching is by id, so its\n * deals look right while every zone value is missing).\n */\nexport const BUNDLE_SCHEMA = \"storylets/bundle@2\";\n/** The project map's schema, before host scopes: the game's scopes were one\n * `world` block. Still read by every runtime, its `world.properties` as the\n * host scope `world`. */\nexport const BUNDLE_SCHEMA_V1 = \"storylets/bundle@1\";\n/** The schema before the project map, still read by every runtime. */\nexport const BUNDLE_SCHEMA_V0 = \"storylets/bundle@0\";\n/** Every bundle schema a runtime accepts. */\nexport const BUNDLE_SCHEMAS: readonly string[] = [BUNDLE_SCHEMA_V0, BUNDLE_SCHEMA_V1, BUNDLE_SCHEMA];\n\n/** Binds bundles to shards (staleness gate) and saves to bundles. */\nexport interface BundleContent {\n project: string;\n version: string;\n /** hash32 over the canonical source shards (schema 2.8). */\n hash: string;\n}\n\nexport interface BundleSettings {\n playAdvancesTurns: number;\n}\n\n/**\n * The play ladder (design/engine-server.md 4.10): how much of itself\n * Storyletter shows this project, in one setting with three rungs rather than\n * a set of toggles, because the features nest.\n *\n * solo one player, one flow: no sharing, no durability, no venue features\n * shared several players over one world: sharing appears\n * venue a production: nothing is hidden\n *\n * EDITOR-SIDE ONLY. It stays in the project shard beside `coverage` and\n * `export` and is never compiled: a solo project plays on the same Engine as a\n * venue one. Hidden is hidden rather than disabled, so going DOWN a rung is\n * refused when the project already contains what the rung would hide, and a\n * hand-edited shard above its rung is a compile warning.\n */\nexport type PlayRung = \"solo\" | \"shared\" | \"venue\";\n\n/** The default rung: a project shard that says nothing is a solo game. */\nexport const DEFAULT_PLAY_RUNG: PlayRung = \"solo\";\n\n/** The project shard's settings block: what the bundle carries, plus the\n * authoring-side play rung that it does not. */\nexport interface ProjectSettings extends BundleSettings {\n /** The play ladder rung (see `PlayRung`). Absent = \"solo\". */\n play?: PlayRung;\n}\n\n/**\n * The PROJECT MAP (design/project-map-contract.md 2.1): at most one per project,\n * above the boxes. Two halves, on purpose.\n *\n * `group` is SEMANTIC and always ships when the project declares a map: the zone\n * group, compiled exactly as a box's group is (the group's properties flattened\n * onto each tag), which opted-in boxes' hands bind and cards are tagged with BY\n * ID, as with any group. It is in no box's `tagGroups`. Each zone is one tag and\n * so one value bag, whichever boxes' hands are dealt to it.\n *\n * `geometry` is INERT PAYLOAD, as the per-box map block before it was: nothing\n * in the engine reads it, and it is absent unless the project asked for it\n * (`export.map`), so a shipping build carries no shapes. GAME IDS throughout,\n * because a host matches these against the names it passes to `peek`.\n *\n * SITES ARE CONTENT. Until 2026-09-05 they were deliberately left out: a site\n * was where an author parked a hand while working. That held for a game, where a\n * hand's zone is its only real-world meaning; it does not hold for a physical\n * experience (design/engine-server.md 4.3), where the position is where the kiosk\n * stands. They stay per box, because a hand belongs to one box.\n */\nexport interface ProjectMap {\n /** The zone group. The engine reads this. */\n group: TagGroup;\n /** Drawing data, only under `export.map`. The engine never reads it. */\n geometry?: {\n /** Drawn zones only, by tag gameId, in group order. A tag with no polygon\n * is not a place yet and is left out rather than shipped as an empty\n * shape. */\n zones: { tag: string; polygon: ViewPoint[] }[];\n /** Visible background pictures, back to front, as bundle-relative paths. */\n backgrounds?: BundleBackground[];\n /** Box gameId -> where that box's placed hands stand, sorted by hand\n * gameId so the bytes do not depend on authoring order. Only opted-in\n * boxes; a box with no site has no key, and no key at all when nothing\n * is placed. The zone a site sits in is NOT repeated: the hand's own\n * binding is what the runtime deals from. */\n sites?: Record<string, { hand: string; x: number; y: number }[]>;\n };\n}\n\n/** One shipped picture. `locked` and `hidden` are authoring state and do not\n * travel; the draw order is the array order. */\nexport interface BundleBackground {\n /** Where the file sits relative to the bundle (\"assets/<file>\"). */\n file: string;\n x: number;\n y: number;\n width: number;\n height: number;\n opacity?: number;\n}\n\nexport interface Bundle {\n schema: typeof BUNDLE_SCHEMA | typeof BUNDLE_SCHEMA_V1 | typeof BUNDLE_SCHEMA_V0;\n content: BundleContent;\n metadata: \"full\" | \"stripped\";\n settings: BundleSettings;\n /** The game's own scopes the project declares (`@world`, `@player`, ...),\n * Patter's shape: each one's token, its scope-level `writable` and its\n * declarations, every default resolved (absent declarations: an opaque\n * scope). A standalone engine self-backs each from its defaults; a game\n * binds its own store for any of them (`hostScopes`). Absent when the\n * project declares none. */\n scopeRegistry?: HostScopeRegistry;\n /** Bundle @1 and @0 only: the one game scope there was, read as the host\n * scope `world` (see `bundleHostScopes`). A bundle @2 has none. */\n world?: LegacyWorldBlock;\n story: {\n properties: PropertyDecl[];\n };\n boxes: Box<Expression>[];\n /** The project map, when the project declares one (see `ProjectMap`). The\n * per-box `maps` array it replaces is gone, not kept as a one-element list:\n * zones are no longer a box's. */\n map?: ProjectMap;\n /** Other engines' game-wide scopes the content names (`patter`), sorted: the\n * family's shared vocabulary, let through unchecked by the compiler. The\n * engine reports when the game has not registered one. Absent when none.\n * A token the project declares (`scopeRegistry`) is the game's, never here. */\n externalScopes?: string[];\n /**\n * The extensions the project takes, by id: each one's version and its\n * compiled declarations by node kind, every default resolved (what a\n * defaults helper reads, `extensionDecls`), and the project node's own values.\n * A node's values sit on the node, under `extensions`. Absent when the project\n * takes none, so a bundle without them is byte for byte what it was.\n */\n extensions?: Record<string, BundleExtension>;\n}\n\n/** One extension as the bundle carries it. */\nexport interface BundleExtension {\n version: string;\n /** Compiled declarations by node kind: no editor-only fields, no editor words. */\n fields: Record<string, ExtensionFieldDecl[]>;\n /** The project node's values, raw, when it has any. */\n project?: Record<string, unknown>;\n}\n\n/**\n * A compiled extension field declaration (`@wildwinter/toolkit/extensions`' FieldDecl,\n * less the editor's words). Its `default` is always present. Structured types\n * carry their parts: `fields` for a record, `key` and `of` for a map, `of` and\n * `id` for a list, `to` for a reference.\n */\nexport interface ExtensionFieldDecl {\n name: string;\n type: string;\n default: unknown;\n values?: string[];\n stages?: string[];\n to?: string;\n fields?: ExtensionFieldDecl[];\n key?: { type: string; to?: string };\n of?: Omit<ExtensionFieldDecl, \"name\" | \"default\"> & { default?: unknown };\n id?: string;\n min?: number;\n max?: number;\n required?: boolean;\n}\n\n/** A node's extension values in a bundle: extension id -> field -> value, raw. */\nexport type NodeExtensions = Record<string, Record<string, unknown>>;\n\n// --- reading the project map (design/project-map-contract.md 5, Q12) ---------\n//\n// The questions every reader of a bundle asks once a group may live outside the\n// box that uses it, answered once here beside `valueAddresses`, because the\n// engine, `describeBundle`, the Board and the server each looked a bound group up\n// in the hand's own box and silently lost the project one. Pure field access.\n\n/** The tag groups a box sees, by NAME as well as by id: its own groups, then the\n * project map's group when the box has opted in. The engine resolves a group\n * name in a box against exactly this list, own groups first (a bundle the\n * engine loads never has a box group sharing the project group's name, so the\n * order decides nothing; it is stated for determinism). */\nexport function groupsOfBox<E>(\n bundle: { map?: { group: TagGroup } },\n box: Pick<Box<E>, \"tagGroups\" | \"usesMap\">,\n): TagGroup[] {\n return box.usesMap === true && bundle.map !== undefined\n ? [...box.tagGroups, bundle.map.group]\n : box.tagGroups;\n}\n\n/** A tag group by internal id, wherever it lives: in a box (`box` set) or on the\n * project map (`box` absent). Ids are project-unique, so this is bundle-wide;\n * whether the box asking may USE the group is `groupsOfBox`'s question. */\nexport function groupById<E>(\n bundle: { boxes: readonly Box<E>[]; map?: { group: TagGroup } },\n id: string,\n): { group: TagGroup; box?: Box<E> } | undefined {\n for (const box of bundle.boxes) {\n const group = box.tagGroups.find((g) => g.id === id);\n if (group !== undefined) return { group, box };\n }\n return bundle.map?.group.id === id ? { group: bundle.map.group } : undefined;\n}\n\n/** Every tag group in the bundle, each once: the boxes' groups in bundle order,\n * then the project map's. What a walk over every value bag wants. */\nexport function allTagGroups<E>(bundle: { boxes: readonly Box<E>[]; map?: { group: TagGroup } }): TagGroup[] {\n const groups = bundle.boxes.flatMap((box) => box.tagGroups);\n return bundle.map !== undefined ? [...groups, bundle.map.group] : groups;\n}\n\n/** One declared owned property, as an address prints it. */\nexport interface PropertyAddress {\n /** The engine address, `<scope>.<owner segment>.<name>`, exactly as\n * `listProperties` prints it and `setProperty` accepts it. */\n path: string;\n scope: \"box\" | \"deck\" | \"hand\" | \"value\";\n /** The owner's internal id: what the stores and the save are keyed by. */\n owner: string;\n decl: PropertyDecl;\n}\n\n/**\n * Every declared box, deck, hand and tag property in the bundle, by the address\n * a runtime prints for it: boxes in bundle order (the box, its decks, its hands,\n * its tags), then the project map's zones, ONCE, whichever boxes use them. A hand\n * instance carries its template's declarations, as the engine's bags do.\n *\n * The owner segments are `valueAddresses`' for tags, so a zone prints\n * `value.<zone>.<name>` and a repeated box tag its qualified form.\n */\nexport function propertyAddresses(bundle: {\n boxes: readonly Box<unknown>[];\n map?: { group: TagGroup };\n}): PropertyAddress[] {\n const values = valueAddresses(bundle);\n const out: PropertyAddress[] = [];\n const push = (scope: PropertyAddress[\"scope\"], owner: string, segment: string, decls: readonly PropertyDecl[]): void => {\n for (const decl of decls) out.push({ path: `${scope}.${segment}.${decl.name}`, scope, owner, decl });\n };\n const tagsOf = (group: TagGroup): void => {\n for (const tag of group.tags) {\n push(\"value\", tag.id, values.print.get(tag.id) ?? effectiveGameId(tag), tag.properties ?? []);\n }\n };\n for (const box of bundle.boxes) {\n push(\"box\", box.id, effectiveGameId(box), box.properties);\n for (const deck of box.decks) push(\"deck\", deck.id, effectiveGameId(deck), deck.properties);\n for (const hand of box.hands) {\n const decls = hand.template !== undefined\n ? box.handTemplates.find((t) => t.id === hand.template)?.properties ?? []\n : hand.properties ?? [];\n push(\"hand\", hand.id, effectiveGameId(hand), decls);\n }\n for (const group of box.tagGroups) tagsOf(group);\n }\n if (bundle.map !== undefined) tagsOf(bundle.map.group);\n return out;\n}\n\n// --- the save envelope --------------------------------------------------------\n//\n// Version 2 (the one-registry model): property values are the game's\n// ScopeRegistry's, not the engine's. The envelope holds what is NOT a property\n// (boards, clocks, cooldowns, PRNGs, play logs, spent cards), plus, when the\n// engine made its own registry (a standalone game), that registry's values\n// under `registry`. A game that passed a registry saves it once itself.\n// Version 1 envelopes still load on every runtime: their property partitions\n// move into the registry under the keys below.\n//\n// Registry keys (identical on every runtime, since they are in the save):\n// `story` for the shared @story; `storylets/<kind>/<id>` for a shared box,\n// deck, hand, or value bag (kind is `box`, `deck`, `hand`, or `value`, id the\n// internal id); `storylets/flow/<flowId>/story` and\n// `storylets/flow/<flowId>/<kind>/<id>` for a flow's own. Ids escape `%` as\n// `%25` and `/` as `%2F`. A bag with no declared properties is not registered.\n// A self-backed @world (no resolver bound) is a stored property too, under\n// `world`.\n\n/** Version 3 (the hands-on review, 2026-10, foundation 7): a flow's turns,\n * cooldowns and board, and the shared spent cards, are keyed by GAME ID, as\n * the play log always was, so a card deleted and made again under the same\n * Game ID keeps its cooldown and its place as well as its play count. */\nexport const SAVE_SCHEMA = \"storylets/save@3\";\n/** Version 2, keyed by internal id: still read, every key resolved as a Game\n * ID first and an internal id second. */\nexport const SAVE_SCHEMA_V2 = \"storylets/save@2\";\n/** The version 1 envelope's schema tag, still read. */\nexport const SAVE_SCHEMA_V1 = \"storylets/save@1\";\n\nexport interface PlayRecord {\n /** Card and outcome by gameId (feeds the play-history functions). */\n card: string;\n /** \"\" for a card with no outcomes, played with none: the key is always\n * there, so a save's shape does not depend on the card. */\n outcome: string;\n turn: number;\n}\n\n/** A property bag: name -> value. */\nexport type PropertyBag = Record<string, ScalarValue>;\n\n/** The per-scope property partitions one side of the sharing flag holds:\n * a save carries one of these for the shared values and one per flow\n * (design/flows.md). NO world key, in either: @world is the game's own\n * state, resolved through the world resolver and saved by whoever owns\n * it - \"host saves its container once, each engine saves its own\n * envelope\" (engine-runtimes.md 3.1). */\nexport interface PropsPartition {\n story: PropertyBag;\n box: Record<string, PropertyBag>;\n deck: Record<string, PropertyBag>;\n hand: Record<string, PropertyBag>;\n /** Tag state, keyed by tag id. */\n value: Record<string, PropertyBag>;\n}\n\n/** One flow's snapshot inside the envelope (schema 4), and the blob\n * `saveFlow` parks. */\nexport interface FlowSave {\n /** The per-flow property partitions. Carried by `saveFlow`, which parks one\n * flow whole; absent from a version 2 envelope's flows, whose properties\n * are the registry's. Present in every flow of a version 1 envelope. */\n props?: PropsPartition;\n /** Per-box turn counters, keyed by box Game ID (schema 3.4; by box id\n * before version 3) - per flow: there is deliberately no global turn. */\n turns: Record<string, number>;\n /** mulberry32 state, uint32 (schema 3.3), per flow. */\n prng: number;\n /** Absolute next-eligible turn (of the card's box's clock) per card Game ID\n * (card id before version 3); MAX_SAFE_INTEGER = never (deliberately not\n * Infinity, which JSON-serialises to null). */\n cooldowns: Record<string, number>;\n /** Hand contents (card Game IDs, in dealt order), keyed by hand Game ID\n * (internal ids before version 3). The claims ledger is derived from this\n * (schema 3.5). */\n board: Record<string, string[]>;\n playLog: PlayRecord[];\n}\n\n/** The whole engine, one envelope: the shared partitions once, then every\n * live flow keyed by its id - Patter's shape (one shared blob + N flow\n * blobs; multi-flow and save/load are the same feature). */\n/** The engine's half of a save: what every flow shares. Properties, and the\n * cards a shared `redraw: \"never\"` has taken out of the world for good\n * (design/shared-scarcity.md). Claims are NOT here: they are derived from the\n * live boards, and each flow's board rides its own blob. */\nexport interface SharedSave {\n /** The shared property partitions: version 1 only. */\n props?: PropsPartition;\n /** Card Game IDs (ids before version 3), sorted, so a save is byte-stable\n * for a diff. */\n spent: string[];\n}\n\nexport interface SaveEnvelope {\n /** Written as version 3; a version 2 envelope has the same shape, keyed by\n * internal id. */\n schema: typeof SAVE_SCHEMA | typeof SAVE_SCHEMA_V2;\n content: BundleContent;\n /** The engine's own registry's values, keyed by registry key: present only\n * when the engine made the registry itself (a standalone game). A game that\n * passed a registry saves it once, beside this envelope. */\n registry?: Record<string, PropertyBag>;\n shared: SharedSave;\n flows: Record<string, FlowSave>;\n}\n\n/** A version 1 envelope, from before the registry held the properties. Every\n * runtime still reads it: its partitions move into the registry as it loads. */\nexport interface SaveEnvelopeV1 {\n schema: typeof SAVE_SCHEMA_V1;\n content: BundleContent;\n shared: SharedSave & { props: PropsPartition };\n flows: Record<string, FlowSave & { props: PropsPartition }>;\n}\n\n// --- the load report (design/engine-server.md 4.9) ----------------------------\n//\n// `loadGame` is forgiving by design: a card the bundle no longer has drops off\n// the board, a property the save does not carry keeps its default, and a\n// version two builds newer loads without a word. That forgiveness is what makes\n// a save survive an edit, and it is also what hides the cost of a content\n// update from whoever is about to apply one. The report is the same walk,\n// itemised: `previewLoad` computes it and changes nothing, `loadGame` computes\n// it and applies it, and `previewFlowRestore` answers the same questions for\n// one flow (4.1's `openFlow(id, { restore })`).\n//\n// Card, hand and flow identities are GAME IDS: a report is host-facing and\n// internal ids mean nothing outside the project. An entity the edit DELETED -\n// a vanished card, a vanished hand - is named by the key the save itself\n// carries, which is its Game ID from save version 3 (an internal id in an\n// older save). There is nothing else to name it by.\n//\n// A property is named differently, and deliberately: by its ENGINE ADDRESS,\n// the string listProperties() prints and getProperty()/setProperty() accept.\n// A report entry is then something a host can act on rather than merely\n// print, and the runtimes have one property grammar instead of two.\n\n/** One card that a restore refused to put back on the board, or a load\n * dropped from it because it no longer qualifies.\n *\n * `vanished` and `hand-vanished` are the edit's doing (the card, or the hand\n * it sat in, is no longer in the bundle). `claimed-elsewhere` is only ever a\n * single-flow restore into a LIVE engine: the card is shared, and the other\n * open flows already hold every copy the world has. */\nexport interface LoadEviction {\n flow: string;\n hand: string;\n card: string;\n /** The rest are a deal's own reasons, met as a load checks what it put\n * back (save version 3 era; the hands-on review, 2026-10, foundation 7):\n * the card, its deck or its hand no longer qualifies with the values the\n * save carries. Checked only when the envelope carries them. */\n reason: \"vanished\" | \"hand-vanished\" | \"claimed-elsewhere\"\n | \"condition\" | \"deck-gate\" | \"hand-condition\" | \"cooldown\" | \"tags\" | \"taken\";\n}\n\n/** One property the restore could not put back as it was. `flow` names the\n * flow whose half it belongs to; absent, it is the shared half.\n *\n * `path` is the engine's property address, spelled exactly as\n * `Flow.listProperties()` / `Engine.listProperties()` print it and exactly as\n * `getProperty` and `setProperty` accept it: `story.<name>` for the story\n * scope, `<scope>.<ownerGameId>.<name>` for the box, deck, hand and tag\n * scopes. No `@`, which belongs to the expression language and not to an\n * address.\n *\n * The owner segment is its GAMEID (design/engine-server.md 4.4), the name it\n * is called by everywhere else, so an operator reading a hot-swap report can\n * paste the address straight into `setProperty`. An owner the build no longer\n * has keeps the id the save carried: there is no gameId left to give it,\n * which is the rule the eviction list above has always used. */\nexport interface LoadProperty {\n flow?: string;\n path: string;\n}\n\n/** What a load or a flow restore would do that is not a plain restore\n * (design/engine-server.md 4.9). Arrays are sorted, so two runtimes given the\n * same save and bundle produce the same bytes; `flows` alone keeps the\n * envelope's own order, because a caller re-takes its handles in it. */\nexport interface LoadReport {\n /** No drift and nothing dropped, defaulted or retyped: the save goes back\n * exactly as it was. `flows` is not a divergence and does not count. */\n exact: boolean;\n project: string;\n /** Drift when the two differ; reported, never refused. */\n version: { saved: string; bundle: string };\n /** Drift when the two differ; reported, never refused. */\n hash: { saved: string; bundle: string };\n /** The flows this restores, in the order it restores them. */\n flows: string[];\n evicted: LoadEviction[];\n /** Cooldowns held for cards the bundle no longer has. */\n droppedCooldowns: { flow: string; card: string }[];\n /** Shared `redraw: \"never\"` entries for cards the bundle no longer has. */\n droppedSpent: string[];\n /** In the save, not declared any more. */\n droppedProperties: LoadProperty[];\n /** Declared, not in the save: it takes the declaration's default. */\n defaultedProperties: LoadProperty[];\n /** In the save, still declared, but the saved value no longer fits the\n * declaration (its type changed, or an enum value / quality stage was\n * edited away). It takes the declaration's default. */\n retypedProperties: LoadProperty[];\n}\n\n/** The .storyletsave FILE: the HOST's file, not the engine's - the engine's\n * envelope plus, when the host keeps one, its @world container. This is\n * \"host saves its container once, each engine saves its own envelope\"\n * folded into one file for the single-host case; the ENGINE never reads or\n * writes `world` (loadGame takes the envelope alone). */\nexport const SAVEFILE_SCHEMA = \"storylets/savefile@1\";\n\nexport interface SaveFile {\n schema: typeof SAVEFILE_SCHEMA;\n /** The engine's envelope: version 2 when written today, version 1 still read. */\n engine: SaveEnvelope | SaveEnvelopeV1;\n /** The host's @world values, saved and restored by the host. */\n world?: PropertyBag;\n}\n\n// --- source shards (design/storylets-source.md) --------------------------------\n\n/** The project folder: a macOS package, a plain folder elsewhere. */\nexport const PROJECT_FOLDER_EXTENSION = \".storylets\";\n/** The compiled bundle (strict JSON; generated, never hand-edited). */\nexport const BUNDLE_EXTENSION = \".storyletsc\";\n\n/**\n * Where a shipped background sits, relative to the bundle file.\n *\n * One function so the compiler (which writes the name into the bundle) and the\n * export op (which writes the bytes) cannot drift apart: a path agreed in two\n * places is a path that eventually disagrees. One folder for the project, as\n * the source has (`<project>/assets/`), since the project map is the only thing\n * with pictures and there is one of it (design/project-map-contract.md 1.4).\n */\nexport const bundleAssetPath = (file: string): string => `assets/${file}`;\n/** Per-type shard extensions, JSON5 inside (source doc section 2). */\nexport const SHARD_EXTENSIONS = {\n project: \".storyletproj\",\n box: \".storyletbox\",\n tags: \".storylettags\",\n hands: \".storylethands\",\n deck: \".storyletdeck\",\n /** The AUTHOR's arrangement layer: the canvases, where cards sit on a deck's\n * node canvas and the furniture drawn round them. Its own shard because\n * positions churn (an afternoon of tidying a canvas touches every card) and\n * content does not, so a designer arranging and a writer editing never\n * collide on one file (design/graphical-views.md section 1.2). */\n view: \".storyletview\",\n /** The DESIGNER's map, in two places told apart by schema tag. At the\n * project root (`map.storyletmap`, `PROJECTMAP_SCHEMA`) it is the PROJECT\n * MAP: the zone group and the map's furniture, once for the project\n * (design/project-map-contract.md 1.1). In a box folder (`MAP_SCHEMA`) it is\n * that box's SITES: where its hands stand on the project map.\n *\n * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1\n * point 5) because the two halves stopped having one owner. A hand's\n * position ships in the bundle's `map.geometry` (4.3) and is where a venue's\n * kiosk stands, so it is SHAPE, which a server's author key may not change;\n * the canvases are the author's own working drawing and never leave the\n * project folder. One file could not be both. */\n map: \".storyletmap\",\n /** Threaded comments: content-ADJACENT, so neither in a content shard (a\n * writer's deck edit must not conflict with a reviewer's comment) nor in the\n * arrangement sidecar (this is not where anything sits). One per box,\n * id-keyed (design/annotation.md). Documentation NOTES used to share this\n * file and were retired: `purpose` already says why a thing exists, and\n * Patterpad's typed routing has no destination here. */\n notes: \".storyletnotes\",\n /** An installation contract: what a VENUE depends on, one file per\n * installation in `contracts/` at the project root\n * (design/engine-server.md 4.11). Its own shard, and its own folder, for the\n * walkthrough's reason (Reboot 7.5, S4): a different owner, a different\n * change rate, and a merge that must never collide with the author's edits,\n * since the server always wins its own file. */\n contract: \".storyletcontract\",\n /** One extension's data (a Storylet extension's, design: patterkit\n * proposals/patter-storylet-extensions.md): one per extension per box, in the\n * box folder (`<extension id>.storyletextension`), and one at the project\n * root for project-level data. Keyed by node kind and id, merged by the\n * extension's manifest, never interpreted by the engine. Its own shard\n * because a tool's pass and a writer's edit must not collide on one file. */\n extension: \".storyletextension\",\n} as const;\n\n/**\n * An extension's manifest, as a project keeps it: `extensions/<id>.extension.json`\n * at the project root (`@wildwinter/toolkit/extensions`' `MANIFEST_FILE_SUFFIX`). Not a\n * shard: JSON the extension ships, never written by the tools nor put in the\n * canonical form, but SOURCE all the same, so a pack carries it and a project\n * compiles from it.\n */\nexport const MANIFEST_EXTENSION = \".extension.json\";\n/** Where a project keeps its extensions' manifests, relative to the root. */\nexport const EXTENSIONS_DIR = \"extensions\";\n\n/** Every file a project's source is made of: its shards and its manifests. */\nexport const SOURCE_EXTENSIONS: readonly string[] = [...Object.values(SHARD_EXTENSIONS), MANIFEST_EXTENSION];\n\n/** Where the installation contracts live, relative to the project root. The\n * directory is the registry, as it is for a box's decks: a contract exists\n * because its file exists. */\nexport const CONTRACTS_DIR = \"contracts\";\n\nexport const PROJECT_SCHEMA = \"storylets/project@0\";\nexport const BOX_SCHEMA = \"storylets/box@0\";\nexport const TAGS_SCHEMA = \"storylets/tags@0\";\nexport const HANDS_SCHEMA = \"storylets/hands@0\";\nexport const DECK_SCHEMA = \"storylets/deck@0\";\nexport const VIEW_SCHEMA = \"storylets/view@0\";\nexport const MAP_SCHEMA = \"storylets/map@0\";\n/** The project map shard's schema: the root `map.storyletmap`, beside the\n * project shard (design/project-map-contract.md 1.1). The same extension as a\n * box's map shard, told apart by this tag and by sitting at depth one. */\nexport const PROJECTMAP_SCHEMA = \"storylets/projectmap@0\";\n/** The comment sidecar's schema. Still called \"notes\" on disk: the file already\n * held both, and renaming it would break every project for no gain. */\nexport const NOTES_SCHEMA = \"storylets/notes@0\";\nexport const CONTRACT_SCHEMA = \"storylets/contract@0\";\n/** An extension data shard's schema. */\nexport const EXTENSION_SCHEMA = \"storylets/extension@0\";\n\n/**\n * One extension's data for one box, or for the project (the root shard).\n *\n * `entries` is the extension's, by node kind (`card`, `outcome`, `box`, `deck`,\n * `tagGroup`, `tag`, `handTemplate`, `hand`, `project`) and then node id, and\n * the canonical form never reorders anything inside it: its lists are the\n * extension's, in the extension's order. The values are raw, as a tool wrote\n * them; the extension's manifest says their types.\n */\nexport interface ExtensionShard {\n schema: typeof EXTENSION_SCHEMA;\n /** The extension's id: the same as the file's stem. */\n extension: string;\n entries: Record<string, Record<string, Record<string, unknown>>>;\n}\n\n/**\n * The extensions a project takes (the project opts in, ruled 2026-10-18), by id:\n * the version it is pinned to. The manifest itself sits in `extensions/`.\n */\nexport interface ProjectExtension {\n version: string;\n}\n\n/**\n * The reserved key a merged shard carries an unresolved merge under, in every\n * shard type (`@wildwinter/toolkit/extensions`' `UNRESOLVED_KEY`): the clashes, each\n * with its path, its kind and the three values, ours being what the shard now\n * holds. Validation refuses a shard carrying it; resolving removes it.\n */\nexport const UNRESOLVED_KEY = \"unresolved\";\n\n/**\n * What one installation depends on, written by the venue's server and read by\n * `validate` (design/engine-server.md 4.11).\n *\n * NOT THE AUTHOR'S FILE. A venue is provisioned against names - the hands its\n * stations deal, the boxes its scheduler ticks, the properties its clocks drive,\n * the fields its crew read - and the server writes them out so the tools that\n * already gate a build can refuse a rename before it reaches the venue. A\n * project playing at two venues has two of these. The author never edits one,\n * and today, with no server built, a project either receives one or has none.\n *\n * NEVER COMPILED. It is project-side config like `coverage` and `export`: the\n * server does not need its own contract handed back, it needs the bundle to\n * still honour it.\n *\n * BY GAMEID throughout, because a gameId is the name that crosses the project's\n * border and an internal id is authoring identity.\n */\nexport interface ContractShard {\n schema: typeof CONTRACT_SCHEMA;\n /** The installation this contract speaks for. One file per installation, and\n * two files naming the same one is an error. */\n installation: string;\n /** Who wrote it, for a human reading the file (\"Storylet Server 0.1.0\"). */\n by?: string;\n /** The server's revision when it wrote this. */\n revision?: number;\n /** Hands a station is bound to, by gameId: they may not be renamed or\n * removed. */\n hands?: string[];\n /** Timed boxes the venue's scheduler ticks, by box gameId, with the turn unit\n * in SECONDS it was provisioned against. A box whose unit changed means every\n * rest on its cards changed meaning. */\n boxes?: Record<string, { turn: number }>;\n /** Property paths the venue reads or drives, in the engine's own address\n * grammar with no `@` (\"world.time_wall\", \"story.visits\"), which is how\n * `listProperties()` prints them. */\n properties?: ContractProperty[];\n /** Card-template field names the crew and the bridges read. */\n fields?: string[];\n /** Outcome field names they read, the same way: the after-line a station\n * shows when a press lands. What `fields` is to the card template, this is\n * to the box's `outcomeFields`. */\n outcomeFields?: string[];\n}\n\n/**\n * One contracted property.\n *\n * A bare path is the common form and the one the spec's example writes. The\n * object form adds the TYPE the venue was provisioned against, which is the only\n * way `validate` can catch the break that costs a producer most: a property that\n * still exists under the same name and now holds something else. A server that\n * knows the type should write the object form; a hand-written contract may say\n * only the path and get the existence check alone.\n */\nexport type ContractProperty = string | { path: string; type?: PropertyType };\n\n/** The path a contracted property names, whichever form it was written in. */\nexport const contractPropertyPath = (p: ContractProperty): string =>\n typeof p === \"string\" ? p : p.path;\n\n/** The type a contracted property was provisioned against, when it says. */\nexport const contractPropertyType = (p: ContractProperty): PropertyType | undefined =>\n typeof p === \"string\" ? undefined : p.type;\n\n/** A point in a canvas's own coordinates. */\nexport interface ViewPoint {\n x: number;\n y: number;\n}\n\n/**\n * Canvas furniture: what an author draws AROUND the content to make sense of it\n * (design/graphical-views.md 3, \"Frames and sites\").\n *\n * Both canvases carry the same thing, which is why they share a type: a node\n * canvas and a map are different views of different material, but \"put a box\n * round this lot and call it act two\" is the same thought on either.\n *\n * It lives in the view sidecar because it is ARRANGEMENT. Nothing here is\n * content: no runtime reads it, no bundle carries it, and deleting the sidecar\n * loses only the drawing. Threaded comments are the\n * other thing entirely - they attach to entities and they travel - but a canvas\n * DRAWS their markers, while owning none of them.\n */\nexport interface CanvasFurniture {\n /** Titled areas behind the content, back to front (see `stacked`).\n *\n * There was a second kind, a `stickies` list, retired on 2026-08-10\n * (design/annotation.md): a dropped comment marker does the same job in a\n * fraction of the space, and an annotation that takes as much room as the\n * thing it is about is a bad trade on a canvas. */\n frames?: Frame[];\n}\n\n/**\n * A titled area behind a group of things: Unreal's comment box.\n *\n * Deliberately dumb about what is inside it. It has no membership list and\n * computes none: a frame is a thing an author DREW, and the cards under it are\n * whatever happens to be under it now. That is what keeps it honest when content\n * moves, and it is the same reasoning that keeps a zone's sites out of the map's\n * sidecar.\n */\nexport interface Frame extends ViewPoint {\n id: string;\n w: number;\n h: number;\n /** Shown in the frame's bar, and the handle it is dragged by. */\n title?: string;\n /** One of the furniture palette's names (see `FURNITURE_COLOURS`); the theme\n * decides what that looks like, so a frame does not carry a hex value that\n * would fight the palette on the day somebody switches theme. */\n colour?: string;\n /** Place in the frame band (sparse, `stacked`). Frames can nest. */\n z?: number;\n}\n\n/** The furniture palette: names, not colours. The theme maps them, so the same\n * shard reads correctly on linen and on baize. */\nexport const FURNITURE_COLOURS = [\"paper\", \"amber\", \"sage\", \"sky\", \"rose\", \"slate\"] as const;\nexport type FurnitureColour = typeof FURNITURE_COLOURS[number];\n\n/** One deck's node canvas: where its cards sit, and the furniture around them.\n * Sparse throughout. A card with no entry lays out by default, and an entry for\n * a card that no longer exists is inert: kept, and reported by `validate` as a\n * warning per entry (the family's orphan rule, 2026-10-18), never dropped. */\nexport interface DeckCanvas extends CanvasFurniture {\n /** Keyed by CARD id. */\n cards?: Record<string, ViewPoint>;\n /** The deck's colour: its stripe on a node canvas and its chip on a card\n * face. A slot of the 12-colour identity palette (0-11), as a box's\n * `ViewShard.colour` is. Absent, the colour is a hash of the deck's title,\n * which is every deck until it is first renamed: the rename stores the\n * colour the old title had, so the deck keeps it (Ian's ruling,\n * 2026-10-28). Editor-only, like everything in this shard: it never\n * reaches the bundle. */\n colour?: number;\n}\n\n/** The box's map: where its hands stand on the PROJECT map, and nothing else.\n * Carried by the MAP shard since 2026-09-06. The view shard's old `map` is\n * not read since 1.0.0.\n *\n * No furniture since the project map (design/project-map-contract.md 1.3): the\n * frames belong to the one map, in `ProjectMapShard`. A box map shard that\n * still carries `frames` is warned about and read for nothing until\n * `storyletengine format` moves them. */\nexport interface BoxMap {\n /** Keyed by HAND id. WHERE a site is, and nothing else.\n *\n * Which zone it is IN is not recorded here, and deliberately (2026-08-06,\n * with the rebinding drag): a hand that binds a zone already says so in its\n * own shard, as `chosen` or as a rule binding, and that is the truth the\n * runtime deals from. A copy here could only ever go on to disagree with it,\n * and a site whose recorded zone contradicts the hand it stands for would be\n * the most misleading thing on the map.\n *\n * Called `pins` until 2026-08-10 (design/annotation.md). No compatibility\n * branch: the only projects that exist are the examples in this repo, and they\n * were edited. */\n sites?: Record<string, ViewPoint>;\n}\n\n/** The AUTHOR's arrangement layer for one box: where cards sit on their decks'\n * canvases, and the furniture drawn round them.\n *\n * Its own shard on purpose (design/graphical-views.md section 1.2). Positions\n * churn, content does not: an afternoon of tidying a canvas touches every card,\n * and if that lived in the deck shard then a designer arranging and a writer\n * editing card text would collide on one file all day, while a content review\n * would be full of coordinates. Keyed by id throughout so the existing merge\n * engine handles two designers rearranging different things without a conflict.\n *\n * Source-only. It never reaches the compiled bundle, exactly as `order` does\n * not: the compiler reads the fields it names and this is not among them. */\nexport interface ViewShard {\n schema: typeof VIEW_SCHEMA;\n /** Keyed by DECK id: one node canvas each. */\n canvases?: Record<string, DeckCanvas>;\n /** The box's colour on the project map: its layer swatch, its pins and its\n * chips. A slot of the theme's 12-colour identity palette (0-11), not a hex\n * value, so it follows light, dark and the colour themes, as a Patter\n * writing status's `colour` does.\n *\n * STORED, not computed (the surfacing review's round-3 ruling): a box takes\n * the first slot no other box on the map uses when it joins, and keeps it\n * however the boxes are reordered, added or removed, until the author picks\n * another. Arrangement like everything else here, so it never reaches the\n * bundle. */\n colour?: number;\n}\n\n/** The DESIGNER's map for one box: where its hands stand on the project map.\n *\n * Split out of the view shard on 2026-09-06 (design/engine-server.md 9.1 point\n * 5). The two halves had stopped sharing an owner: a hand's position ships in\n * the bundle's `map.geometry.sites` (4.3), which makes it the thing a venue\n * provisions its kiosks against, while a deck's canvas is a working drawing\n * that never leaves the folder. A server's author key may change the canvases\n * and not this.\n *\n * The map is NESTED under `map` rather than flattened to the top level, and\n * deliberately: the block's bytes are then exactly what the view shard held, so\n * the migration was a move of a value rather than a reshaping of it, and the\n * merge strategy carried over word for word. The view shard's old `map` is no\n * longer read (1.0.0): a map left there is ignored, and `storyletengine\n * format` drops it.\n *\n * Source-only in the sense the view shard is not: `compileMaps` reads the\n * positions for the bundle's `map.geometry`, under `export.map`, for a box that\n * has opted in to the project map. */\nexport interface MapShard {\n schema: typeof MAP_SCHEMA;\n map: BoxMap;\n}\n\n/**\n * The PROJECT MAP's shard: the root `map.storyletmap` (design/project-map-contract.md\n * 1.1). At most one per project, above the boxes.\n *\n * `group` is the zone group, a `TagGroup` byte for byte the shape a box's tags\n * shard holds, so every reader of a group (the spatial marker, polygons,\n * backgrounds, group-level property declarations) carries over unchanged. It is\n * compiled exactly as a box's group is and ships as the bundle's `map.group`. A\n * box opts in with `usesMap` on its box shard; only an opted-in box's cards and\n * hands may reference it.\n *\n * `frames` is the map's furniture, moved here from every box's map shard:\n * arrangement, never compiled. Sites stay per box, in each box's own map shard,\n * because a hand belongs to one box.\n */\nexport interface ProjectMapShard extends CanvasFurniture {\n schema: typeof PROJECTMAP_SCHEMA;\n group: TagGroup;\n}\n\n/** A coverage input driver: during a coverage run the harness feeds a\n * host-scope property (`@world.x`, `@player.y`) values from `values`, so content gated on\n * external state gets exercised (Patter's coverageDrivers, carried whole). */\nexport interface CoverageDriver {\n /** \"initial\": set once as each playthrough starts. \"recurring\": re-rolled\n * per turn at the cadence, so one run passes through several states. */\n kind: \"initial\" | \"recurring\";\n /** For recurring drivers: how often to re-roll per turn (default \"sometimes\"). */\n cadence?: \"rarely\" | \"sometimes\" | \"often\";\n /** The pool the harness picks from (uniform). Empty = inert. */\n values: ScalarValue[];\n}\n\n/** Authoring-side coverage configuration (never compiled into the bundle). */\nexport interface CoverageConfig {\n /** Property drivers, keyed by ref (\"@world.danger\", \"@player.hp\"): any host scope's. */\n drivers?: Record<string, CoverageDriver>;\n\n}\n\n/** The version control systems a project can be set up for: `@wildwinter/toolkit/vcs`' list, written out here\n * because the model takes no tooling dependency (ops checks the two agree). Absent on a project means none. */\nexport type VcsKind = \"git\" | \"perforce\" | \"plastic\" | \"svn\";\n\nexport interface ProjectShard {\n schema: typeof PROJECT_SCHEMA;\n project: {\n id: string;\n name: string;\n version: string;\n };\n settings: ProjectSettings;\n /** Coverage drivers + argument domains (authoring/testing config; stays\n * out of the compiled bundle). */\n coverage?: CoverageConfig;\n /** Validation switches (authoring config; never compiled). Off is written\n * as ABSENT, like `export.map`: a shard says what an author chose. */\n validation?: {\n /** Also warn when state is WRITTEN but nothing reads it. Off by default:\n * cards are routinely written ahead of the content that will read them,\n * so mid-development this warning is mostly noise. The read side (a gate\n * on state nothing writes) always warns, because that kills cards now. */\n warnUnreadWrites?: boolean;\n };\n /**\n * Where the game's shared scopes folder is, relative to the folder holding this file\n * (`\"../../shared/game-scopes\"`). Authoring config, never compiled, and usually absent: the\n * tools find a `game-scopes/` folder by walking up from the project on their own, stopping at\n * the version-control root, so this is only for a folder that walk would not reach. A path\n * that doesn't exist is a project error. (patterkit design/shared-scopes.md.)\n */\n gameScopes?: string;\n /**\n * The version control system the project is kept in. Authoring config, never compiled. Set, Storyletter\n * talks to that system and no other, rather than guessing from the folders above the project (a project\n * inside an unrelated git checkout would otherwise be staged into it); `\"none\"` is plain writes, no version\n * control at all. ABSENT is \"not chosen\", and Storyletter finds the system from the folders as it always\n * did, which is every project made before the setting. Patter's `ProjectFile.vcs`, except that Patter\n * reads absent as none: its projects have had the setting from the start, and ours have not.\n */\n vcs?: VcsKind | \"none\";\n /**\n * The Patter project this one is paired with: a `.patter` folder, relative to the folder\n * holding this file (`\"../story/the-hamlet.patter\"`). Authoring config, never compiled: the\n * engines still know nothing of each other (Reboot 10). With it, `validate` checks each card\n * against the scene of the same name in the Patter project's published bundle, and\n * Storyletter can open Patterpad at a card's scene. A folder that isn't there is a warning,\n * since a writer may hold the cards without the dialogue.\n */\n patter?: string;\n /**\n * The boxes the game performs through the paired Patter project, by box id: every card in them\n * plays the scene named after it. Authoring config, never compiled, and only meaningful beside\n * `patter`. With it, `validate` checks only these boxes and also reports a card with no scene;\n * Storyletter's Board plays their cards' scenes, and the playable export carries them.\n * Absent, every card that has a scene is checked and none is required to have one.\n */\n patterBoxes?: string[];\n /**\n * The extensions this project takes, by id (ruled 2026-10-18: the project opts\n * in, a box does not). Authoring config: the bundle carries each one's\n * compiled declarations and values, never this block. An extension named here\n * whose manifest is missing is a warning, and its data is left untouched.\n */\n extensions?: Record<string, ProjectExtension>;\n /**\n * The game's own scopes this project declares (`@world`, `@player`, ...), in\n * Patter's shape: a token, a scope-level `writable` (true when absent), and\n * declarations (absent: an opaque scope, any name, unchecked). Where the game\n * has a scopes folder, `game.scopes.json` is the source and this is the\n * project's synced copy. Absent when the project declares none.\n */\n scopeRegistry?: HostScopeRegistry;\n /** The one game scope a project declared before host scopes: read as the\n * scope `world` until `storyletengine format` moves it into\n * `scopeRegistry`. Never written. */\n world?: LegacyWorldBlock;\n story: {\n properties: PropertyDecl[];\n };\n /** Templates of play: configuration bags keyed by template name. Core\n * validates only what it knows. */\n templates: Record<string, unknown>;\n export: {\n bundle: string;\n metadata: \"full\" | \"stripped\";\n /**\n * Does a `.storyletpack` carry the project's binary assets (the project\n * map's background images, in its root `assets/` folder)?\n *\n * Default false, and a project-level DEFAULT rather than a rule: a pack is a\n * delivery, so the caller can override it per pack (2026-08-07). Some\n * projects would benefit from sending their pictures in certain\n * circumstances and others never would, which is why neither \"always\" nor\n * \"never\" is the answer.\n *\n * Nothing to do with the compiled bundle, which has its own switch: `map`.\n */\n packAssets?: boolean;\n /**\n * Does the compiled bundle carry the project map's GEOMETRY (zone shapes,\n * background pictures and each opted-in box's sites, `map.geometry`)? The\n * zone group itself always ships when there is a project map: the engine\n * reads it.\n *\n * Default false, and the default matters: geometry is authoring data, the\n * runtime deals in tag names, and a shipping build should carry nothing it\n * does not use. But a host that wants an in-game map should not have to\n * invent its own export, and it is most useful early - a prototype with a\n * real map beats a prototype with a list of zone names.\n *\n * It sits beside `metadata` on purpose: that is already the switch for\n * \"authoring data that may or may not ship\", and this is its sibling rather\n * than a new concept. With it on, `export` also writes the background files\n * next to the bundle, and `describeBundle` says what is in there.\n */\n map?: boolean;\n };\n}\n\nexport interface BoxShard {\n schema: typeof BOX_SCHEMA;\n box: {\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n /** Authored display order among boxes (sparse; absent falls back to the\n * folder-name position). Authoring-only, like a card's (never compiled\n * into the bundle); merges as a per-field value. */\n order?: number;\n ranking: { specificity: boolean };\n /** Who moves the box's clock (see `Box.turn`): present, the game does,\n * with `seconds` a timed box; compiled through unchanged. */\n turn?: { seconds?: number };\n /** The Card template's dealing rules: what a card that sets none takes\n * (the hands-on review, 2026-10). Live, as field defaults are: a card left\n * on the default follows it, a card's own value stays its own. Absent\n * keys take the engine's defaults (Redraw \"always\", weight 1, one slot of\n * one hand, one hand at a time). */\n cardDefaults?: CardDefaults;\n fields: FieldDecl[];\n /** The outcome fields (see `Box.outcomeFields`); a shard without the key\n * declares none. */\n outcomeFields?: FieldDecl[];\n properties: PropertyDecl[];\n /** The box has opted in to the PROJECT MAP (design/project-map-contract.md\n * 1.2): its hands may bind the map's zones and its cards may be tagged\n * with them. Absent is \"not on the map\"; never written false. Compiled\n * through to `Box.usesMap`. */\n usesMap?: true;\n };\n}\n\n/** The box's OWN tag groups: how its cards are filed. Never the project map's\n * zone group, which lives in the root map shard and which a box may not\n * declare a copy of (a spatial group here is a compile error naming\n * `storyletengine format`). */\nexport interface TagsShard {\n schema: typeof TAGS_SCHEMA;\n groups: TagGroup[];\n}\n\n/** The box's hand templates + hands (the writer/programmer contract). */\nexport interface HandsShard {\n schema: typeof HANDS_SCHEMA;\n templates: HandTemplate<string>[];\n hands: Hand<string>[];\n}\n\nexport interface DeckShard {\n schema: typeof DECK_SCHEMA;\n deck: {\n id: string;\n gameId?: string;\n title?: string;\n purpose?: string;\n condition?: string;\n /** Scarce across flows: see Deck.shared. */\n shared?: boolean;\n /** Its `redraw: \"never\"` cards are spent past the run: see Deck.durable. */\n durable?: boolean;\n /** Authored display order within the box (sparse; see BoxShard). */\n order?: number;\n properties: PropertyDecl[];\n };\n cards: Card<string>[];\n}\n\n// --- templates of play --------------------------------------------------------\n// The spatial template's types, field access and geometry. Re-exported here so the\n// package has one entry point, and kept in its own module because core schema and\n// a template of play are different things (Reboot 6).\nexport * from \"./spatial.js\";\n\n// How a hand reaches a tag group, and whether that binding is the hand's own to\n// change. Core schema rather than a template of play, but the map is what needed\n// it said out loud.\nexport * from \"./hands.js\";\n\n// Frames: what an author draws around the content. Arrangement,\n// so it lives in the sidecar and reads forgivingly (furniture.ts says why).\nexport * from \"./furniture.js\";\n\n// Threaded comments: the conversation about a thing, in its own sidecar.\nexport * from \"./comments.js\";\n\n// Guessing a property's type from what an outcome writes: the quick fix's input.\nexport * from \"./infer.js\";\n","// ---------------------------------------------------------------------------\n// The state logger: what changed in the state kernel, as it changes.\n//\n// Both product families shipped one of these, in four runtimes each, and they\n// were not the same shape. The Storylet Engine's was PUSH-based on the\n// PropertyBag audit hook - a write logs the moment it lands, with the previous\n// value straight off the event. Patterplay's diffed whole saveGame() snapshots,\n// so it could only ever say what changed BETWEEN captures, and only for state a\n// save persists. This is the first one, because it is the better one: a diff\n// cannot tell a write from a write-and-write-back, cannot name the reason a\n// host attached to a write, and cannot see a value that changed and changed\n// back.\n//\n// A diff is still needed, and is kept, for everything that is NOT in a bag: a\n// product's own non-property state (turns, cooldowns, visit counts) arrives\n// through the adapter's `extra()` and is diffed on capture. Bags replaced\n// wholesale by a load fire no audit events either, so capture() re-reads and\n// re-mounts.\n//\n// Line format: `${label}${path}: ${from} -> ${to}`, `<unset>` for a value that\n// was not there.\n// ---------------------------------------------------------------------------\n\nimport type { PropertyBag, ScalarValue } from \"./index.js\";\n\n/** A flattened snapshot: path -> value. */\nexport type StateSnapshot = Record<string, ScalarValue>;\n\nexport interface StateChange {\n path: string;\n from: ScalarValue | undefined;\n to: ScalarValue | undefined;\n}\n\n/**\n * One bag on the logger's path space.\n *\n * Named for the LOG, not the bag: a product may already have its own type for enumerating\n * bags (the Storylet Engine's LogMount, which labels a mount \"story\" for its own purposes),\n * and in the ported runtimes both land in one namespace. They are also not the same thing,\n * which the prefix rule below is about.\n *\n * `pathPrefix` is used VERBATIM, separator included, exactly as the bag's own\n * is - it is not a scope token with a dot implied. Omit it and the bag's own\n * `pathPrefix` is used, which is what a product wants whenever its log paths\n * and its property addresses agree.\n *\n * They do not always agree, which is why this can be overridden: Patterplay\n * addresses a scene property `@scene.mood` (relative to the flow's current\n * scene) but has to LOG it as `@scene:kitchen.mood`, because a log covering\n * several scenes needs to say which one.\n */\nexport interface LogMount {\n bag: PropertyBag;\n pathPrefix?: string;\n}\n\n/** What a product supplies: its kernel bags (re-read on every capture, so a\n * product that replaces its bags on load re-mounts), and its non-property\n * state as flattened paths. */\nexport interface StateLoggerAdapter {\n mounts(): LogMount[];\n extra?(): StateSnapshot;\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to console.log. */\n sink?: (line: string) => void;\n /** Prefixed to every line, verbatim (e.g. `\"[board] \"`). */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state. Logs nothing. */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the audited writes already logged as\n * they landed, plus anything that changed WITHOUT an audit event, diffed,\n * logged and re-baselined. */\n capture(): StateChange[];\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\n/** The sorted set of paths that differ between two snapshots. */\nexport function diffState(prev: StateSnapshot, next: StateSnapshot): StateChange[] {\n const changes: StateChange[] = [];\n const paths = new Set([...Object.keys(prev), ...Object.keys(next)]);\n for (const path of [...paths].sort()) {\n const from = prev[path], to = next[path];\n if (JSON.stringify(from) !== JSON.stringify(to)) changes.push({ path, from, to });\n }\n return changes;\n}\n\nconst show = (v: ScalarValue | undefined): string => (v === undefined ? \"<unset>\" : JSON.stringify(v));\n\nconst prefixOf = (m: LogMount): string => m.pathPrefix ?? m.bag.pathPrefix;\n\nexport function createStateLogger(adapter: StateLoggerAdapter, opts: StateLoggerOptions = {}): StateLogger {\n const sink = opts.sink ?? ((line: string) => console.log(line));\n const label = opts.label ?? \"\";\n const emit = (c: StateChange): void => { sink(`${label}${c.path}: ${show(c.from)} -> ${show(c.to)}`); };\n\n const full = (): StateSnapshot => {\n const out: StateSnapshot = {};\n for (const m of adapter.mounts()) {\n const prefix = prefixOf(m);\n for (const [name, value] of Object.entries(m.bag.values)) out[prefix + name] = value;\n }\n Object.assign(out, adapter.extra?.() ?? {});\n return structuredClone(out);\n };\n\n let baseline = full();\n let pushed: StateChange[] = [];\n let mounted: { bag: PropertyBag; off: () => void }[] = [];\n\n const hook = (prefix: string, bag: PropertyBag): (() => void) =>\n bag.onAudit((change) => {\n // The write logs as it lands, `from` straight off the event; the baseline\n // moves with it so capture() never re-reports what was already said.\n const c: StateChange = structuredClone({ path: prefix + change.name, from: change.prev, to: change.next });\n emit(c);\n pushed.push(c);\n baseline[c.path] = structuredClone(change.next);\n });\n\n const mount = (): void => {\n const mounts = adapter.mounts();\n const same = mounted.length === mounts.length && mounts.every((m, i) => mounted[i]!.bag === m.bag);\n if (same) return;\n for (const m of mounted) m.off();\n mounted = mounts.map((m) => ({ bag: m.bag, off: hook(prefixOf(m), m.bag) }));\n };\n mount();\n\n return {\n snapshot: full,\n capture(): StateChange[] {\n // Whatever arrived WITHOUT an audit event: the adapter's non-property\n // paths, and bag values replaced wholesale by a load (which fires none).\n const next = full();\n const diffed = diffState(baseline, next);\n for (const c of diffed) emit(c);\n const changes = [...pushed, ...diffed];\n pushed = [];\n baseline = next;\n mount(); // a load replaces a product's bags; re-hook them\n return changes;\n },\n dispose(): void {\n for (const m of mounted) m.off();\n mounted = [];\n pushed = [];\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// @wildwinter/scoperegistry - the scope registry / runtime state container that\n// sits on top of @wildwinter/expr.\n//\n// expr is a stateless calculator: given an AST, an EvalContext (the state), and\n// a Dialect, it computes. This package is the *state* layer: it owns the world\n// state as a set of named scopes - each either an **owned** scope (a property\n// bag this registry stores and saves) or a **foreign** scope (host- or\n// other-engine-resolved at runtime, never stored here) - and produces the\n// `EvalContext` (for evaluation) and `ExpressionSchema` (for validation) that\n// expr consumes. Plus the `scopeRegistrySpec` interop format for importing a\n// foreign owner's scope declarations.\n//\n// Design: design/scope-registry.md (in the patter repo). expr never depends on\n// this; this depends one-way on expr.\n// ---------------------------------------------------------------------------\n\nimport { valueFits } from \"@wildwinter/expr\";\nimport type {\n EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver,\n} from \"@wildwinter/expr\";\n\nexport type { EvalContext, ExpressionSchema, PropertyType, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\n\n// ---------------------------------------------------------------------------\n// Declarations + the scopeRegistrySpec interop format\n// ---------------------------------------------------------------------------\n\n/**\n * A property declaration. `default` is used by an *owned* scope to seed its bag\n * (foreign scopes ignore it - the host owns the value). `writable: false` makes\n * a property read-only TO THE STORY; the HOST still writes it, by passing\n * `{ host: true }` (see `set`). Default is read/write. (`type`/`values` feed\n * validation.)\n *\n * The distinction is the whole point of the flag on a foreign scope, where the\n * value is the game's own: a flag carried in the story's bundle must not lock a\n * game out of its own state. Ruled 2026-09-05, after both products met it - the\n * Storylet Engine's venue clock and Patter's coverage driver were each blocked\n * from the one property they existed to move.\n */\nexport interface ScopeDeclaration {\n name: string;\n type: PropertyType;\n values?: string[]; // for enum / flags\n /** A quality's ordered ladder of stage names (quality.md). */\n stages?: string[];\n default?: ScalarValue; // owned scopes: seed value\n writable?: boolean; // default true\n /** The author's note on what the property is for, which another tool's picker shows as a tip\n * (shared game scopes, scopes.ts). The registry itself ignores it. */\n purpose?: string;\n}\n\n/** One scope in a `scopeRegistrySpec`: a token + (optional) declarations. */\nexport interface ScopeSpec {\n token: string;\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Property declarations; omit for an opaque scope (any name, unchecked). */\n declarations?: ScopeDeclaration[];\n}\n\n/**\n * The interop format an owner (Storylet Studio, a host game) exports so another\n * engine can validate references into its scopes. Carried under the well-known\n * `scopeRegistrySpec` JSON key (inside a `.storyworld`, or a standalone file).\n */\nexport interface ScopeRegistrySpec {\n version: number;\n scopes: ScopeSpec[];\n}\n\n/** The spec versions this build understands. */\nexport const SUPPORTED_SPEC_VERSIONS = [1] as const;\n\n/**\n * Extract + validate a `scopeRegistrySpec` from any JSON value (a parsed\n * `.storyworld` bundle, or a vanilla `{ scopeRegistrySpec: ... }` manifest).\n * Returns null when the key is absent (so callers can probe arbitrary files);\n * throws on a malformed or unsupported-version spec.\n */\nexport function readScopeRegistrySpec(source: unknown): ScopeRegistrySpec | null {\n if (!source || typeof source !== \"object\") return null;\n const raw = (source as Record<string, unknown>).scopeRegistrySpec;\n if (raw === undefined) return null;\n if (typeof raw !== \"object\" || raw === null) throw new Error(\"scopeRegistrySpec must be an object\");\n const spec = raw as Record<string, unknown>;\n if (typeof spec.version !== \"number\") throw new Error(\"scopeRegistrySpec.version must be a number\");\n if (!(SUPPORTED_SPEC_VERSIONS as readonly number[]).includes(spec.version)) {\n throw new Error(`unsupported scopeRegistrySpec version ${spec.version} (supported: ${SUPPORTED_SPEC_VERSIONS.join(\", \")})`);\n }\n if (!Array.isArray(spec.scopes)) throw new Error(\"scopeRegistrySpec.scopes must be an array\");\n for (const s of spec.scopes) {\n if (!s || typeof s !== \"object\" || typeof (s as ScopeSpec).token !== \"string\") {\n throw new Error(\"each scopeRegistrySpec scope needs a string token\");\n }\n }\n return spec as unknown as ScopeRegistrySpec;\n}\n\n// ---------------------------------------------------------------------------\n// PropertyBag - the state kernel's unit of state (added 0.2.0; design:\n// storylets-new/design/engine-runtimes.md 3.1). A typed, declared property\n// bag with defaults, the firing rule (engine writes notify subscribers;\n// host writes are silent but always auditable), examiner rows, one\n// sanctioned clone door, and bare-value save/load. Owned registry scopes\n// are bags; products may also hold bag families of their own (per-box,\n// per-scene) and mount the shared ones.\n// ---------------------------------------------------------------------------\n\n/** One property change. `silent` marks a host write (the firing rule: it\n * reaches the audit hook but not subscribers); `reason` is the host's own\n * note for its log. */\nexport interface BagChange {\n name: string;\n prev?: ScalarValue;\n next: ScalarValue;\n silent: boolean;\n reason?: string;\n}\n\n/** One examiner row: what a property examiner/editor needs to render and\n * edit a declared property. */\nexport interface PropertyRow {\n name: string;\n /** The address this property answers to - what getProperty/setProperty take.\n * A bag composes it from its own `pathPrefix` and the name, so a row is\n * self-describing: an examiner can render and write a row without being told\n * separately where it came from.\n *\n * The PREFIX CARRIES ITS OWN SEPARATOR rather than the bag assuming a dot,\n * because a prefix is not always a bare scope token: the Storylet Engine\n * addresses a deck's properties as `deck.<id>.name`, so the prefix is already\n * a dotted path. Patterplay's `@patter.gold` and `@scene.mood` are the plain\n * case. (`@gold` also resolves - splitRef defaults an unqualified name to the\n * patter scope - but it is the shorthand, not the address a row reports.)\n *\n * With no prefix this is just the name. Both families forked this interface\n * to add exactly this field - once per runtime - which is the same reason\n * `stages` is here. */\n path: string;\n type: PropertyType;\n value: ScalarValue | undefined;\n default: ScalarValue;\n values?: string[];\n /** A quality's ordered stage ladder, so an inspector can offer the stages\n * instead of a free-text box. `quality` has been in PropertyType since the\n * ladder landed, and the evaluator compares stages by LADDER POSITION and\n * refuses an unknown one, so free text is not a soft failure: a typo breaks\n * play rather than being corrected. This row is the only thing an examiner\n * sees, so a ladder it cannot carry is a ladder no editor can offer. One\n * consumer forked this whole interface to add the field; the field belongs\n * here, beside the `values` it is the closed-set twin of. */\n stages?: string[];\n writable: boolean;\n}\n\n/** A saved value a refitting load put back to its declaration's default:\n * the property's name, and what the save held. */\nexport interface BagMisfit {\n name: string;\n value: ScalarValue;\n}\n\nexport class PropertyBag {\n /** The live values record (stable identity across reseed, so an\n * EvalContext built over it stays valid). Read-path for evaluation;\n * writes go through `set` so the firing rule applies.\n *\n * It has no prototype, so a scope answers only its own properties: on a plain\n * object, `@world.constructor` read the built-in `Object` function rather than\n * \"missing\", and a write to `__proto__` vanished (October 2026 review). */\n readonly values: Record<string, ScalarValue> = Object.create(null) as Record<string, ScalarValue>;\n private decls = new Map<string, ScopeDeclaration>();\n private readonly subscribers = new Set<(change: BagChange) => void>();\n private readonly auditors = new Set<(change: BagChange) => void>();\n /** Name normalisation policy: lowercase by default (the registry's\n * long-standing contract); a product whose names are case-significant\n * passes identity. */\n private readonly norm: (name: string) => string;\n\n /** The address prefix this bag's rows carry, separator included (`@`,\n * `@scene.`, `world.`, `deck.<id>.`). Empty means a row's path is its name. */\n readonly pathPrefix: string;\n\n constructor(\n declarations: ScopeDeclaration[] = [],\n opts?: { normalise?: (name: string) => string; pathPrefix?: string },\n ) {\n this.norm = opts?.normalise ?? ((n) => n.toLowerCase());\n this.pathPrefix = opts?.pathPrefix ?? \"\";\n this.seed(declarations);\n }\n\n private seed(declarations: ScopeDeclaration[]): void {\n for (const d of declarations) {\n const name = this.norm(d.name);\n this.decls.set(name, d);\n // Cloned so bags seeded from one declaration set never share a\n // mutable default (flags arrays).\n this.values[name] = structuredClone(d.default ?? defaultFor(d));\n }\n }\n\n get(name: string): ScalarValue | undefined {\n return this.values[this.norm(name)];\n }\n\n /** A name as this bag keys it: its normalisation policy applied. The registry\n * uses it to key quality ladders and the validation schema the bag's own way,\n * so a case-significant (identity) bag is not quietly folded to lower case\n * one layer up. */\n normalise(name: string): string {\n return this.norm(name);\n }\n\n /** Whether a STORY write to this property is allowed: false only for a\n * declaration marked `writable: false`. A host write is always allowed. The\n * registry asks before writing, so it never has to read a refusal out of an\n * error that a listener might have raised instead. */\n writable(name: string): boolean {\n return this.decls.get(this.norm(name))?.writable !== false;\n }\n\n /** Write a property. Engine writes (the default) notify subscribers;\n * pass `silent: true` for a host write, which reaches only the audit\n * hook. Throws on a read-only property unless the caller says it is the\n * HOST (`host: true`), for whom `writable: false` was never a rule - it is\n * the story's promise, not the game's. `silent` and `host` are separate on\n * purpose: one is about who hears the write, the other about who may make\n * it. Returns the change. */\n set(name: string, value: ScalarValue, opts?: { silent?: boolean; reason?: string; host?: boolean }): BagChange {\n const n = this.norm(name);\n if (!opts?.host && !this.writable(n)) throw new Error(`'${name}' is read-only`);\n const change: BagChange = {\n name: n,\n prev: this.values[n],\n next: value,\n silent: opts?.silent ?? false,\n reason: opts?.reason,\n };\n this.values[n] = value;\n // The listeners as they stood when the write began, each told once, whatever any\n // of them does to the lists meanwhile (unsubscribing itself or another): the\n // contract every port keeps, pinned by the registry corpus.\n for (const audit of [...this.auditors]) audit(change);\n if (!change.silent) for (const fn of [...this.subscribers]) fn(change);\n return change;\n }\n\n /** Notified of engine (non-silent) writes. Returns the unsubscribe. */\n subscribe(fn: (change: BagChange) => void): () => void {\n this.subscribers.add(fn);\n return () => this.subscribers.delete(fn);\n }\n\n /** Notified of EVERY write, silent or not. Returns the unsubscribe. */\n onAudit(fn: (change: BagChange) => void): () => void {\n this.auditors.add(fn);\n return () => this.auditors.delete(fn);\n }\n\n /** Examiner rows: the declared surface only (stray values are storage,\n * not surface). */\n rows(): PropertyRow[] {\n return [...this.decls.entries()].map(([name, d]) => rowFor(d, this.get(name), undefined, name, this.pathPrefix));\n }\n\n declarations(): ScopeDeclaration[] {\n return [...this.decls.values()];\n }\n\n /** The one sanctioned copy door: values deep-copied, declarations\n * duplicated, the normalisation policy carried, subscriptions NOT\n * carried. */\n clone(): PropertyBag {\n const c = new PropertyBag([], { normalise: this.norm, pathPrefix: this.pathPrefix });\n c.decls = new Map(this.decls);\n Object.assign(c.values, structuredClone(this.values));\n return c;\n }\n\n /** Clear and re-seed from new declarations, in place (the values record\n * keeps its identity, so contexts built over it stay valid). */\n reseed(declarations: ScopeDeclaration[]): void {\n for (const k of Object.keys(this.values)) delete this.values[k];\n this.decls.clear();\n this.seed(declarations);\n }\n\n /** Bare values, ready to embed in a product's save. */\n save(): Record<string, ScalarValue> {\n return structuredClone(this.values);\n }\n\n /** Lay saved values over the current ones (call after a fresh seed:\n * orphans land as strays, new declarations keep their defaults; the\n * product decides whether to prune). Does not fire events.\n *\n * With `refit`, a saved value that no longer fits its declaration (a stage,\n * an enum value or a flag the build has since deleted, or a value of another\n * type) goes back to the declaration's default instead, and is returned with\n * what the save held, in declaration order. Without it, nothing is returned\n * and every value lands as saved. */\n load(values: Record<string, ScalarValue>, opts?: { refit?: boolean }): BagMisfit[] {\n const saved = new Map<string, ScalarValue>();\n for (const [k, v] of Object.entries(values)) {\n const n = this.norm(k);\n // A copy, as every port's load takes one: the caller's blob (a flags list) is not this bag's.\n this.values[n] = structuredClone(v);\n saved.set(n, v);\n }\n const misfits: BagMisfit[] = [];\n if (!opts?.refit) return misfits;\n for (const [name, d] of this.decls) {\n if (!saved.has(name)) continue;\n const value = saved.get(name)!;\n // An enum whose own default is \"\" (none chosen) takes \"\" back, as a host write does.\n if (valueFits(d, value, { noneChosen: d.type === \"enum\" && d.default === \"\" })) continue;\n this.values[name] = structuredClone(defaultFor(d));\n misfits.push({ name, value });\n }\n return misfits;\n }\n}\n\nfunction rowFor(\n d: ScopeDeclaration,\n value: ScalarValue | undefined,\n writable?: boolean,\n name?: string,\n pathPrefix = \"\",\n): PropertyRow {\n const rowName = name ?? d.name.toLowerCase();\n return {\n name: rowName,\n path: pathPrefix + rowName,\n type: d.type,\n value,\n default: d.default ?? defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n // `stages` was added to the row so an examiner could offer a quality's ladder\n // instead of a free-text box, and then never populated here: every quality row\n // this function built came out without one. Fixed 2026-09-02.\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n writable: writable ?? d.writable ?? true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// The registry / state container\n// ---------------------------------------------------------------------------\n\ninterface OwnedScope {\n kind: \"owned\";\n bag: PropertyBag;\n owner?: string;\n}\ninterface ForeignScope {\n kind: \"foreign\";\n resolver: ScopeResolver;\n decls: Map<string, ScopeDeclaration>;\n scopeWritable: boolean;\n norm: (name: string) => string;\n owner?: string;\n}\ntype Entry = OwnedScope | ForeignScope;\n\nconst lowerCase = (name: string): string => name.toLowerCase();\n\n/** Options for an owned scope the registry builds (`defineOwned`). */\nexport interface OwnedScopeOptions {\n /** The address prefix its examiner rows carry, separator included. Defaults\n * to `<token>.`; the address grammar is the product's, not the registry's. */\n pathPrefix?: string;\n /** Name normalisation: lower case by default; a case-significant product\n * passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it (an engine's name). Named in a clash error and carried\n * on examiner rows, so one examiner can group a combined game by engine. */\n owner?: string;\n}\n\n/** Options for a foreign scope (`defineForeign`). */\nexport interface ForeignScopeOptions {\n /** Scope-level read/write default for its declarations (default true). */\n writable?: boolean;\n /** Name normalisation for the names passed to the resolver: lower case by\n * default; a case-significant product passes identity. */\n normalise?: (name: string) => string;\n /** Who registered it. See `OwnedScopeOptions.owner`. */\n owner?: string;\n}\n\n/** Options for a context or schema built from the registry. */\nexport interface AliasOptions {\n /**\n * Expression token -> registered key. `{ scene: \"patter/flow-2/scene/tavern\" }`\n * makes `@scene` read that instance bag, for this context only.\n *\n * The registry learns nothing about what the token MEANS: which flow, which\n * scene, which deck is an engine's own idea, and the engine names the key per\n * evaluation. It has to be the registry's mechanism rather than the engine\n * patching the context afterwards, because quality ladders and validation are\n * looked up by token too, and an alias applies to all three alike.\n *\n * An alias to a key that is not registered throws: a condition evaluated\n * against a scope that is not there is an engine bug, not a graceful false.\n */\n aliases?: Record<string, string>;\n}\n\n/** Options for `remove`. */\nexport interface RemoveOptions {\n /** Park an owned scope's values, to be handed back when the same key is next\n * registered (a live reload rebuilding an engine). No effect on a foreign\n * scope, whose values were never the registry's. */\n keep?: boolean;\n}\n\n/** Options for `load`. */\nexport interface LoadOptions {\n /** Keep what earlier loads parked, adding this blob's unclaimed sections to\n * it (a section for the same key replaces the parked one). Without it, each\n * load replaces whatever an earlier load parked. For an engine moving an\n * older save's values into a registry the game has already loaded. */\n keepParked?: boolean;\n /** Put each saved value that no longer fits its declaration back to the\n * default, and report it (`takeMisfits`): a save from before a stage, an\n * enum value or a flag was deleted. A section parked for a key nobody has\n * registered keeps the option, and is refitted when that key is claimed. */\n refit?: boolean;\n}\n\n/** A saved value a refitting load put back to its default: where it was, and\n * what the save held. */\nexport interface LoadMisfit {\n scope: string;\n name: string;\n value: ScalarValue;\n}\n\n/**\n * The versioned owned-state fragment.\n *\n * @deprecated Versioning belongs to the save that embeds the values, not to the\n * registry; no engine ever called this. Embed `save()` in your own versioned\n * save instead. Removed at the next breaking release.\n */\nexport interface OwnedStateFragment {\n version: number;\n scopes: Record<string, Record<string, ScalarValue>>;\n}\n\n/** @deprecated See `OwnedStateFragment`. Removed at the next breaking release. */\nexport const SAVE_FRAGMENT_VERSION = 1;\n\nexport class ScopeRegistry {\n private readonly scopes = new Map<string, Entry>();\n /** Values loaded for keys nobody has registered yet, waiting to be claimed. */\n private readonly parked = new Map<string, Record<string, ScalarValue>>();\n /** The parked keys whose load asked for a refit, applied when they are claimed. */\n private readonly parkedRefit = new Set<string>();\n /** What refitting loads have put back since the last `takeMisfits`. */\n private misfits: LoadMisfit[] = [];\n private rev = 0;\n\n /**\n * A counter that moves whenever a scope is registered or removed, and at no\n * other time: it starts at 0 and each registration or removal adds 1. Values\n * changing does not move it. A caller that caches a context built by\n * `toEvalContext()` rebuilds it when this moves, because the context's set of\n * scopes is fixed when it is built while the values it reads stay live.\n */\n get revision(): number {\n return this.rev;\n }\n\n /**\n * Register a scope this registry **owns and stores**. Its bag is seeded from\n * each declaration's `default` (or a type default). Owned scopes are\n * type-checked (declarations) and serialized by `save`/`load`.\n *\n * The third argument may be the path prefix alone (the pre-0.7 form) or an\n * options object.\n */\n defineOwned(token: string, declarations: ScopeDeclaration[], opts?: string | OwnedScopeOptions): this {\n const o: OwnedScopeOptions = typeof opts === \"string\" ? { pathPrefix: opts } : opts ?? {};\n // The scope knows its own token, so its rows can address themselves: `world.hp`.\n // The ADDRESS GRAMMAR is the product's, though, not the registry's - Patterplay\n // writes `@patter.gold` where the Storylet Engine writes `world.gold` - so a\n // caller may say how its addresses look. A bag MOUNTED here keeps whatever prefix\n // its holder gave it: the holder owns the addressing.\n const bag = new PropertyBag(declarations, {\n pathPrefix: o.pathPrefix ?? `${token}.`,\n ...(o.normalise ? { normalise: o.normalise } : {}),\n });\n return this.mountOwned(token, bag, o.owner !== undefined ? { owner: o.owner } : undefined);\n }\n\n /**\n * Attach an EXISTING bag as an owned scope: an engine (or a host) holds the\n * bag and this registry reads, writes, lists and saves it like its own.\n *\n * If values were loaded for this key before anyone registered it, the bag\n * claims them now: laid over its seeded defaults by the bag's own `load` rule.\n */\n mountOwned(token: string, bag: PropertyBag, opts?: { owner?: string }): this {\n this.assertFree(token, opts?.owner);\n this.scopes.set(token, { kind: \"owned\", bag, ...(opts?.owner !== undefined ? { owner: opts.owner } : {}) });\n this.rev++;\n const waiting = this.parked.get(token);\n if (waiting) {\n this.note(token, bag.load(waiting, { refit: this.parkedRefit.has(token) }));\n this.parked.delete(token);\n this.parkedRefit.delete(token);\n }\n return this;\n }\n\n /**\n * Unregister a scope. With `{ keep: true }` an owned scope's values are parked\n * and handed back when the same key is next registered, which is how a live\n * reload hands an engine's state to its replacement. Throws on an unknown key.\n */\n remove(token: string, opts?: RemoveOptions): this {\n const e = this.scopes.get(token);\n if (!e) throw new Error(`unknown scope '@${token}'`);\n if (opts?.keep && e.kind === \"owned\") {\n this.parked.set(token, e.bag.save());\n this.parkedRefit.delete(token);\n }\n this.scopes.delete(token);\n this.rev++;\n return this;\n }\n\n /**\n * Drop parked values nobody claimed. Parked values are kept in the next save by\n * default, so nothing loaded is lost to a flow or deck that simply has not\n * reopened yet; a game that knows they are dead drops them here.\n *\n * With a `prefix`, only keys starting with it are dropped: an engine resetting\n * itself drops its own instance keys (`my-engine/`) and leaves every other\n * engine's alone.\n */\n discardParked(prefix?: string): this {\n if (prefix === undefined) { this.parked.clear(); this.parkedRefit.clear(); }\n else {\n for (const key of [...this.parked.keys()]) {\n if (key.startsWith(prefix)) { this.parked.delete(key); this.parkedRefit.delete(key); }\n }\n }\n return this;\n }\n\n /**\n * The saved values refitting loads (`load(blob, { refit: true })`) have put\n * back to their defaults since the last call, each with its scope, its name\n * and what the save held, in the order they were met; the list is emptied.\n * An engine reports these as a load's drifted values. With `only`, it takes\n * the misfits of the scopes `only` accepts (its own keys), in order, and\n * leaves the rest for their owners: one registry serves several engines.\n */\n takeMisfits(only?: (scope: string) => boolean): LoadMisfit[] {\n if (!only) {\n const taken = this.misfits;\n this.misfits = [];\n return taken;\n }\n const taken = this.misfits.filter((m) => only(m.scope));\n this.misfits = this.misfits.filter((m) => !only(m.scope));\n return taken;\n }\n\n private note(scope: string, found: BagMisfit[]): void {\n for (const m of found) this.misfits.push({ scope, name: m.name, value: m.value });\n }\n\n /** An owned scope's bag (subscribe, audit, rows live there). */\n ownedBag(token: string): PropertyBag {\n const e = this.scopes.get(token);\n if (!e || e.kind !== \"owned\") throw new Error(`'@${token}' is not an owned scope`);\n return e.bag;\n }\n\n /**\n * Re-initialise an existing **owned** scope's bag from new declarations,\n * clearing its current values. For scope-local state that resets on a context\n * change (e.g. entering a new scene / site / deck) without disturbing other\n * scopes. Mutates the bag in place, so an `EvalContext` already built from this\n * registry stays valid.\n */\n reseedOwned(token: string, declarations: ScopeDeclaration[]): this {\n this.ownedBag(token).reseed(declarations);\n return this;\n }\n\n /**\n * Register a **foreign** scope backed by a host `{ get, set? }` resolver. The\n * values live in the host/other engine and are never stored or saved here.\n * `declarations` (optional, e.g. imported from a `scopeRegistrySpec`) are used\n * only for validation; omit them for an opaque scope.\n */\n defineForeign(\n token: string,\n resolver: ScopeResolver,\n declarations: ScopeDeclaration[] = [],\n opts: boolean | ForeignScopeOptions = true,\n ): this {\n // A boolean is the pre-0.7 form: the scope-level writable default alone.\n const o: ForeignScopeOptions = typeof opts === \"boolean\" ? { writable: opts } : opts;\n this.assertFree(token, o.owner);\n const norm = o.normalise ?? lowerCase;\n const decls = new Map<string, ScopeDeclaration>();\n for (const d of declarations) decls.set(norm(d.name), d);\n this.scopes.set(token, {\n kind: \"foreign\", resolver, decls, scopeWritable: o.writable ?? true, norm,\n ...(o.owner !== undefined ? { owner: o.owner } : {}),\n });\n this.rev++;\n return this;\n }\n\n has(token: string): boolean {\n return this.scopes.has(token);\n }\n\n /** Read a property; undefined if the scope or property is not present. */\n get(scope: string, name: string): ScalarValue | undefined {\n const e = this.scopes.get(scope);\n if (!e) return undefined;\n return e.kind === \"owned\" ? e.bag.get(name) : e.resolver.get(e.norm(name));\n }\n\n /** Write a property (an ENGINE write: the bag's subscribers fire; use\n * the bag directly for silent host writes). Throws on an unknown scope.\n *\n * `writable: false` is the STORY's promise, so a story write is refused and\n * a HOST write is not: pass `{ host: true }` from a host's own surface (its\n * `setProperty`, its tooling, a coverage driver) and never from the path an\n * outcome or effect takes. A foreign scope whose resolver has no `set` is\n * refused for everyone, host included - that is not a rule to bypass, it is\n * a game that gave no way to write. */\n set(scope: string, name: string, value: ScalarValue, opts?: { host?: boolean }): void {\n const e = this.scopes.get(scope);\n if (!e) throw new Error(`unknown scope '@${scope}'`);\n if (e.kind === \"owned\") {\n // Asked first, never caught afterwards: catching whatever the bag's write raised\n // reported a listener's own error (a game's hook) as a read-only refusal.\n if (!opts?.host && !e.bag.writable(name)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.bag.set(name, value, opts?.host ? { host: true } : undefined);\n return;\n }\n const n = e.norm(name);\n if (!e.resolver.set) throw new Error(`'@${scope}.${name}' is read-only`);\n if (!opts?.host && !this.foreignWritable(e, n)) throw new Error(`'@${scope}.${name}' is read-only`);\n e.resolver.set(n, value);\n }\n\n private foreignWritable(e: ForeignScope, name: string): boolean {\n if (!e.resolver.set) return false; // no setter => read-only scope\n return e.decls.get(name)?.writable ?? e.scopeWritable;\n }\n\n /** Examiner rows across every scope with a declared surface: owned bags\n * first, then declared foreign scopes (values read through, writability\n * reflecting the resolver). Opaque foreign scopes are not listed. */\n listProperties(): ({ scope: string; owner?: string } & PropertyRow)[] {\n const out: ({ scope: string; owner?: string } & PropertyRow)[] = [];\n for (const [token, e] of this.scopes) {\n const owner = e.owner !== undefined ? { owner: e.owner } : {};\n if (e.kind === \"owned\") {\n for (const row of e.bag.rows()) out.push({ scope: token, ...owner, ...row });\n } else {\n for (const [n, d] of e.decls) {\n out.push({\n scope: token, ...owner,\n ...rowFor(d, e.resolver.get(n), this.foreignWritable(e, n), n, `${token}.`),\n });\n }\n }\n }\n return out;\n }\n\n /**\n * Build the `EvalContext` expr's `evaluate` consumes: owned scopes as static\n * bags, foreign scopes as their resolvers. `host` carries dialect-function\n * callbacks (PRNG, tag lookups) and is passed through untouched.\n */\n toEvalContext(host?: Record<string, unknown>, opts?: AliasOptions): EvalContext {\n const view = this.view(opts?.aliases);\n const scopes: EvalContext[\"scopes\"] = {};\n for (const [token, e] of view) scopes[token] = e.kind === \"owned\" ? e.bag.values : e.resolver;\n // The quality channel (quality.md): declared here once, so a host that\n // registers a quality gets ordering comparisons and advance() with no\n // further wiring. Only added when a quality exists, so contexts stay\n // byte-identical for products that declare none.\n const qualities = this.qualityLadders(view);\n return qualities.size === 0 ? { scopes, host } : {\n scopes, host,\n qualities: (scope, name) => {\n const e = view.get(scope);\n return e ? qualities.get(scope)?.get(normOf(e)(name)) : undefined;\n },\n };\n }\n\n /**\n * The scopes an expression sees: every registered key under its own token,\n * then each alias token pointing at its key's entry (an alias shadows a key of\n * the same name). Keys an engine uses for instance bags (`engine/flow-2/...`)\n * are not valid expression tokens, so they are present but unreachable.\n */\n private view(aliases?: Record<string, string>): Map<string, Entry> {\n const out = new Map(this.scopes);\n for (const [token, key] of Object.entries(aliases ?? {})) {\n const e = this.scopes.get(key);\n if (!e) throw new Error(`alias '@${token}' names '${key}', which is not registered`);\n out.set(token, e);\n }\n return out;\n }\n\n /** Every quality declaration's ladder, keyed scope token then name (the\n * scope's own normalisation). */\n private qualityLadders(view: Map<string, Entry>): Map<string, Map<string, readonly string[]>> {\n const out = new Map<string, Map<string, readonly string[]>>();\n for (const [token, e] of view) {\n for (const [n, d] of declsOf(e)) {\n if (d.type !== \"quality\" || d.stages === undefined) continue;\n let m = out.get(token);\n if (!m) { m = new Map(); out.set(token, m); }\n m.set(n, d.stages);\n }\n }\n return out;\n }\n\n /**\n * Build the `ExpressionSchema` expr's validator consumes. Scopes with no\n * declarations are **omitted** (opaque - references into them are not flagged);\n * declared scopes contribute their property types for validation. Aliases\n * apply as they do to `toEvalContext`, so a condition written against `@scene`\n * validates against the instance bag the engine names.\n */\n toSchema(opts?: AliasOptions): ExpressionSchema {\n const properties = new Map<string, Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>>();\n for (const [token, e] of this.view(opts?.aliases)) {\n const decls = declsOf(e);\n if (decls.length === 0) continue;\n const m = new Map<string, { type: PropertyType; enumValues?: string[]; stages?: string[] }>();\n for (const [n, d] of decls) m.set(n, {\n type: d.type, enumValues: d.values,\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n });\n properties.set(token, m);\n }\n return { properties };\n }\n\n /** Serialize **owned** scopes (foreign scopes are the game's, and the game\n * saves them), as bare bags keyed by token, plus any values still parked, so\n * a save taken before every engine has re-registered loses nothing. The\n * registry knows nothing about game saves: a game embeds this in its own. */\n save(): Record<string, Record<string, ScalarValue>> {\n const out: Record<string, Record<string, ScalarValue>> = {};\n for (const [token, e] of this.scopes) if (e.kind === \"owned\") out[token] = e.bag.save();\n for (const [token, vals] of this.parked) out[token] = structuredClone(vals);\n return out;\n }\n\n /**\n * Restore from a `save` blob. An owned scope lays its section over its current\n * values (the bag's `load` rule). A section for a key nobody has registered\n * yet is PARKED and handed over when that key registers, so a game can load\n * its registry before its engines have reopened their flows or decks. A\n * section for a foreign scope is ignored: those values are the game's.\n *\n * A load replaces whatever was parked before it: it is a whole restore, and\n * residue from an earlier load must not leak into this one.\n *\n * Changed in 0.7.0: sections for unregistered keys used to be dropped.\n */\n load(blob: Record<string, Record<string, ScalarValue>>, opts?: LoadOptions): void {\n if (!opts?.keepParked) { this.parked.clear(); this.parkedRefit.clear(); }\n const refit = opts?.refit === true;\n for (const [token, vals] of Object.entries(blob)) {\n const e = this.scopes.get(token);\n if (e?.kind === \"owned\") this.note(token, e.bag.load(vals, { refit }));\n else if (!e) {\n this.parked.set(token, structuredClone(vals));\n if (refit) this.parkedRefit.add(token); else this.parkedRefit.delete(token);\n }\n }\n }\n\n /**\n * `save()` wrapped with a version stamp.\n *\n * @deprecated Versioning belongs to the save that embeds the values; no\n * engine ever called this. Embed `save()` in your own versioned save.\n * Removed at the next breaking release.\n */\n saveFragment(): OwnedStateFragment {\n return { version: SAVE_FRAGMENT_VERSION, scopes: this.save() };\n }\n\n /**\n * Restore from a versioned fragment; an unsupported version throws.\n *\n * @deprecated See `saveFragment`. Removed at the next breaking release.\n */\n loadFragment(fragment: OwnedStateFragment): void {\n if (fragment.version !== SAVE_FRAGMENT_VERSION) {\n throw new Error(`unsupported owned-state fragment version ${fragment.version} (supported: ${SAVE_FRAGMENT_VERSION})`);\n }\n this.load(fragment.scopes);\n }\n\n /**\n * A token is taken once. There is no reserved-token list: a clash surfaces\n * here, the moment a game combines its engines, which is the only moment\n * anyone knows which engines are present. With owners recorded the error says\n * whose token it already is.\n */\n private assertFree(token: string, owner?: string): void {\n const e = this.scopes.get(token);\n if (!e) return;\n const by = e.owner !== undefined ? ` by ${e.owner}` : \"\";\n const wants = owner !== undefined ? ` (wanted by ${owner})` : \"\";\n throw new Error(`scope '@${token}' is already registered${by}${wants}`);\n }\n}\n\n/** A scope entry's declarations, keyed by its own normalisation. */\nfunction declsOf(e: Entry): [string, ScopeDeclaration][] {\n if (e.kind === \"foreign\") return [...e.decls.entries()];\n return e.bag.declarations().map((d) => [e.bag.normalise(d.name), d]);\n}\n\n/** A scope entry's name normalisation. */\nfunction normOf(e: Entry): (name: string) => string {\n return e.kind === \"foreign\" ? e.norm : (n) => e.bag.normalise(n);\n}\n\n/** The seed value for a declared property: its own `default`, else the type's.\n *\n * Exported because it was being written again wherever a declaration needed seeding, and a\n * copy of a defaults table is a copy that stops agreeing. Patterplay carried three of them in\n * one file, for its shared decls, its host-scope decls and its scene decls - three declaration\n * TYPES, one behaviour, and nothing to notice if a case drifted. The parameter is structurally\n * typed for exactly that reason: anything with `type` and the optional `default` / `values` /\n * `stages` fits, whatever the caller calls its declaration.\n *\n * A quality seeds at the FIRST rung of its ladder: the ladder's start is the story's start. */\nexport function defaultFor(d: {\n readonly type: string; readonly default?: ScalarValue; readonly values?: readonly string[]; readonly stages?: readonly string[];\n}): ScalarValue {\n if (d.default !== undefined) return d.default;\n switch (d.type) {\n case \"boolean\": return false;\n case \"number\": return 0;\n // A property's `string`, and a field's `text` and `multiline` (the family's field types).\n case \"string\":\n case \"text\":\n case \"multiline\": return \"\";\n case \"enum\": return d.values?.[0] ?? \"\";\n case \"flags\": return [];\n // A quality starts at the first rung of its ladder.\n case \"quality\": return d.stages?.[0] ?? \"\";\n // Unreachable for a well-typed declaration, and deliberately present anyway: a bundle\n // is DATA, and a hand-edited or newer-than-this-build one can carry a type string the\n // union does not have. Falling off the switch would seed `undefined`, which is not a\n // ScalarValue and travels a long way before it fails. Patterplay's copy of this had the\n // guard and this one did not, which is the drift you only find by removing a duplicate.\n default: return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// The state logger, which both product families had written twice each.\n// ---------------------------------------------------------------------------\nexport type {\n StateSnapshot, StateChange, LogMount, StateLoggerAdapter, StateLoggerOptions, StateLogger,\n} from \"./state-logger.js\";\nexport { createStateLogger, diffState } from \"./state-logger.js\";\n","// ---------------------------------------------------------------------------\n// The reference runtime: an Engine is the world + flow manager, and ALL play\n// happens on a Flow handle (design/flows.md; the shape is Patter's, adopted\n// deliberately so a host driving both engines holds two objects with the\n// same grammar). The dealing semantics of design/storylets-schema.md\n// section 3 run per flow, implemented exactly and held to the conformance\n// corpus.\n//\n// The flow model, in one place:\n// - an Engine owns the bundle, every lookup built from it, the SHARED\n// property partitions and the host scopes (@world, ...); a Flow owns its own\n// PRNG, per-box clocks, cooldowns, board, claims, play history and the\n// per-flow property partitions. Flows meet only through shared state.\n// - sharing is a per-property `shared` flag on the declaration (never a\n// scope token): @story defaults shared; box, deck, hand and tag\n// properties default per-flow. Every name is shared XOR per-flow, so a\n// read is a union of two bags and a write routes by name.\n// - every property bag lives in ONE ScopeRegistry per game (the\n// one-registry model): the game hands the engine its registry\n// (EngineOptions.registry) or the engine makes its own and acts as its\n// own game. The shared @story registers under `story`, every other bag\n// under a key starting `storylets/`, which no expression can name.\n// saveGame() carries what is NOT a property, plus the registry's values\n// only when the engine made the registry itself.\n// - the HOST SCOPES (@world, @player, ...: every scope the bundle's\n// scopeRegistry declares) are the game's: each one a resolver it binds\n// (EngineOptions.hostScopes, never saved), a scope it registers in its\n// registry, or, for a standalone engine, a self-backed bag the registry\n// stores and saves.\n// - there is no default flow and no ambient current flow: openFlow(id) is\n// the only way in, an existing id is REPLACED (the old flow closes),\n// and a closed flow's handle is INERT - every verb throws (Patter's\n// stale-handle rule).\n// - engine.getProperty serves host scopes (world.*) and shared refs only; a ref that\n// resolves per-flow throws, naming the fix (Patter's teaching rule).\n// - the PROJECT MAP (design/project-map-contract.md 3) is one zone group\n// above the boxes, in `bundle.map.group` and in no box's `tagGroups`. A\n// box that opts in (`usesMap`) sees its name beside its own groups', so\n// its hands bind zones and its cards are tagged with them by id exactly\n// as with a box group. Each zone is ONE value bag, keyed by the tag's id\n// like any tag's, so a zone property is one value whichever box's hand\n// is dealt to it; the `shared` flag still decides per-flow against one\n// for the engine. What the boxes on a map do NOT share: a hand deals only\n// from its own box's decks, and play history stays the asking box's\n// (D7). A bundle that breaks the map's rules is refused at construction\n// (3.8) rather than half-played.\n//\n// Key dealing contracts, unchanged from round 2 (all per flow now):\n// - two verbs: deal(hand) claims, peek(box, criteria) just looks; you can\n// never play a card you only peeked (3.1, look/use rule)\n// - availability order: hand condition -> deck gate -> taken -> cooldown ->\n// tags -> card condition -> claims -> priority (3.1), the order an ask\n// reaches the verdicts below in\n// - claims are physical WITHIN a flow: a card sits in at most `copies`\n// hands of that flow's board at once, at most once in any one hand; the\n// ledger is derived from the board contents (3.5)\n// - a SHARED card (its deck's flag, or its own overriding it) is scarce\n// across flows too: at most `sharedCopies` hands anywhere, counted over\n// every live flow's board, and a shared `redraw: \"never\"` is spent for\n// everyone the first time anyone plays it. A finite redraw deliberately\n// stays per flow - a cooldown is an absolute turn of a per-flow clock, so\n// there is nothing shared to compare it against (design/shared-scarcity.md)\n// - the reserved home group inverts the wildcard: a homed card is\n// available only to an ask binding its home (2.4)\n// - ranking: priority desc -> specificity desc (box toggle) -> seeded\n// shuffle of each maximal tie run (3.2)\n// - one PRNG per flow: expression random(), tie shuffles and the batch\n// deal's hand-order shuffle all advance it; state lives in the save (3.3)\n// - each box has its own turn counter PER FLOW; cooldowns are absolute\n// next-eligible turns of the card's box's clock, set at play time from\n// the post-advance turn; \"never\" is MAX_SAFE_INTEGER, not Infinity (3.4)\n// - @hand composes bound-tag props -> hand props -> chosen tags/criteria\n// (by group name), later shadowing earlier; writes route back to their\n// source; criteria names cannot be written (3.6)\n// - outcome availability is never snapshotted: outcomes() and play()\n// evaluate gates against current state (3.1, 3.7)\n// - a trace event fires after the state it reports has landed, so a\n// handler reading the flow inside it sees the effect (the Live Link's\n// board snapshot depends on this; the shared fixture pins it)\n// ---------------------------------------------------------------------------\n\nimport { deserialiseAst, evaluate, valueFits } from \"@wildwinter/expr\";\nimport type { EvalContext, ExprNode, ScalarValue, ScopeResolver } from \"@wildwinter/expr\";\nimport { matchedSpecificity } from \"@wildwinter/expr-specificity\";\nimport { storyletsDialect, NEVER_PLAYED } from \"@storylet-studio/dialect\";\n\n/** The play-history indexes' key for one (box, group, tag) triple.\n *\n * The BOX is in it because play history is box-specific (design/project-map-\n * contract.md 3.7, D7, ruled 2026-10-01): `count_played_in` asks about the\n * asking box's own plays. A box group was already box-unique, so its key never\n * needed the box; a project-map zone is one tag every opted-in box tags its\n * cards with, and without the box a play of a newspaper at the quay in one box\n * would count as an encounter at the quay in another.\n *\n * A UNIT SEPARATOR (U+001F) joins them: ids are letters, digits and\n * underscores, so a control character cannot occur in one and two different\n * triples can never collide into one key the way a \".\" or \":\" join could. NUL\n * would say the same thing and was the first choice, but GDScript will not\n * carry one in a string - it substitutes U+FFFD and warns on every parse - and\n * the four runtimes keep the same spelling. */\nconst tagKey = (boxId: string, groupId: string, tagId: string): string =>\n `${boxId}\\u001f${groupId}\\u001f${tagId}`;\n\nimport type { StoryletsHost } from \"@storylet-studio/dialect\";\nimport {\n BUNDLE_SCHEMA, BUNDLE_SCHEMAS, PLACE_GROUP, bundleHostScopes, allTagGroups, ambiguousValueAddressMessage, effectiveGameId, groupsOfBox,\n isHoleRef, parseHoleRef, valueAddresses, zoneQualifiedValueAddressMessage,\n} from \"@storylet-studio/model\";\nimport type {\n Box, Bundle, BundleContent, Card, Deck, Expression, FlowSave, Hand, HandTemplate, HostScopeDecl, HostScopeSpec,\n LoadEviction, LoadProperty, LoadReport, NodeExtensions, PlayRecord, PropertyBag, PropertyDecl, PropsPartition,\n SaveEnvelope, SaveEnvelopeV1, Tag, TagGroup,\n} from \"@storylet-studio/model\";\nimport { SAVE_SCHEMA, SAVE_SCHEMA_V1, SAVE_SCHEMA_V2 } from \"@storylet-studio/model\";\nimport { PropertyBag as StateBag, ScopeRegistry, defaultFor } from \"@wildwinter/scoperegistry\";\nimport type { LoadMisfit, PropertyRow, ScopeDeclaration } from \"@wildwinter/scoperegistry\";\nimport { makePrng, shuffleInPlace } from \"./prng.js\";\nimport type { Prng } from \"./prng.js\";\n\n/**\n * The error the engine throws when it refuses something itself: an unknown\n * box, hand, card or flow, a closed flow, a gated-shut outcome, a write it will\n * not make, a bundle or save it cannot read. A host can tell these apart from a\n * fault in its own code with `instanceof StoryletError`; the message is the\n * same text it always was. Errors the shared property kernel raises reach the\n * host as they are.\n */\nexport class StoryletError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"StoryletError\";\n }\n}\n\nexport interface EngineOptions {\n /** Default seed for each flow's PRNG; override per flow in openFlow\n * (cross-runtime determinism, schema 3.3). Default 0. */\n seed?: number;\n /** Retain each flow's event log for introspection - the game-engine seam\n * (schema 5): every trace event, sequence-stamped and turn-stamped where\n * the event has a box context. `true` keeps the default 1000 entries\n * (oldest dropped first). Off by default; subscribeTrace stays the\n * zero-retention stream. */\n log?: boolean | { cap?: number };\n /**\n * The host scopes the game binds, by token: for each, a resolver over\n * values the game owns and the story reads (and, where `set` is offered,\n * writes), such as `{ world: { get, set } }` for `@world.*`. A declared\n * scope left unbound is self-backed from its defaults, as a property the\n * registry stores and saves. Engine-level, shared by all flows, never\n * saved: the game keeps these values. A game running several engines\n * registers its scopes in its registry itself instead, and binds none\n * here. Tokens are case-sensitive: a token the bundle doesn't declare is\n * reported (`setup`), naming the declared spelling when only the case\n * differs. Patterplay's option, name for name, on every runtime.\n */\n hostScopes?: Record<string, HostScope>;\n /**\n * The game's registry: ONE per game, holding every engine's properties\n * except those the game keeps itself, saved once. Given one, the engine\n * registers its own scopes in it (@story under `story`, every other bag\n * under a key starting `storylets/`, and each scope `hostScopes` binds),\n * reads every other scope from it, and `saveGame()` leaves the property\n * values to the game. The host scopes the bundle declares are then the\n * game's to register: owned if the registry should store them, foreign if\n * the game keeps them. Omit it and the engine makes its own registry and\n * acts as its own game: it self-backs every host scope the bundle\n * declares, and `saveGame()` carries the registry's values too.\n */\n registry?: ScopeRegistry;\n /**\n * Diagnostics hook (opt-in, dev tooling only): fired when `openFlow` REPLACES\n * a flow that still had cards dealt, with the flow id and how many. The\n * behaviour is unchanged - replacing is deliberate and the same in Patter -\n * this only makes it observable, because the case it catches is a host\n * calling `openFlow` straight after `loadGame` to \"re-take\" its handle and\n * silently discarding the hand the save just restored (`getFlow` is the\n * call). Zero cost when unset; leave it unset in shipped games.\n */\n onReplacedFlow?: (id: string, dealt: number) => void;\n /** Told of every condition the engine could not evaluate and counted as\n * false (see {@link PlayError}), whether or not anything is tracing, and of\n * each set-up problem (reported as the constructor ends). Default: a console\n * warning. Patterplay's `onError`, the same shape. */\n onError?: (error: PlayError) => void;\n /** Told what the registry's refitting loads put back for this engine: a saved\n * value that no longer fits its declaration (a stage, an enum value or a flag\n * deleted since the save, or a value of another type) goes back to its\n * default, and each one comes here with what the save held. A game that\n * loads its own registry passes `{ refit: true }` and hears its Storylets\n * share here as the engine's bags claim it (at construction, as a flow opens,\n * and as a save loads); the engine's own `loadGame` reports drift in its\n * LoadReport as before. Only this engine's keys (`story`, `storylets/...`,\n * the host scopes it self-backs): the other engines on the registry take\n * theirs. Default: a console warning per value, so a drifted save is never\n * silent. Patterplay's `onRefit`, the same shape (ruling 3, 2026-10-28). */\n onRefit?: (misfits: LoadMisfit[]) => void;\n}\n\n/**\n * A host scope's resolver: a `{ get, set? }` the game provides so the story can\n * read (and, if you allow it, write) values the game owns, `@world.*` among\n * them, at run time. Property metadata (types, read-only) comes from the\n * compiled bundle's declarations; the values themselves live in the game and\n * are never stored or saved by this engine. Patterplay's `HostScope`.\n */\nexport type HostScope = ScopeResolver;\n\n/** Every key `EngineOptions` takes: another is reported (`setup`), since a\n * plain-JS caller has no type check. */\nconst ENGINE_OPTIONS = new Set([\"seed\", \"log\", \"hostScopes\", \"registry\", \"onReplacedFlow\", \"onError\", \"onRefit\"]);\n\n/**\n * A content error the engine played through. Content can fail at run time in\n * ways the compiler cannot see: a host value of the wrong type, a resolver that\n * throws, a division by zero. A deal never stops for one: the condition counts\n * as false, so the card, deck or hand is not available, and the error is\n * reported here and as a `diagnostic` trace event. The same rule and the same\n * shape as Patterplay's, on all four runtimes (the hands-on review, 2026-10,\n * foundation 5: an error used to reach only a trace, so with tracing off a\n * resolver typo looked like a card that never came up). The engine also\n * reports how it was set up (`setup`: a host scope the bundle doesn't\n * declare, an option it doesn't take), as the constructor ends.\n */\nexport interface PlayError {\n /** The flow it happened in; \"\" for a `setup` report, which belongs to no flow. */\n flow: string;\n /** What failed: a condition (a Storylets deal evaluates only conditions), or\n * how the engine was set up (`setup`). */\n kind: \"condition\" | \"setup\";\n /** Whose condition: `card <gameId>`, `deck <gameId>`, `hand <gameId>` or\n * `outcome <card gameId>/<outcome gameId>`. For `setup`, the host-scope\n * token or option key at fault. */\n node: string;\n /** The expression's source text, when the bundle carries it. */\n source?: string;\n message: string;\n}\n\nexport interface OpenFlowOptions {\n /** Seed for this flow's PRNG (defaults to the engine's `seed`). */\n seed?: number;\n /**\n * Open this flow AS IT WAS: a blob from `saveFlow`, applied to the freshly\n * opened (or replaced) flow before the handle comes back\n * (design/engine-server.md 4.1).\n *\n * An option on `openFlow` rather than a `Flow.restore` verb on purpose:\n * restoring INTO a running flow is the trap hosts keep falling into\n * (openFlow REPLACES), and \"open this flow as it was\" is one act. Drift is\n * tolerated exactly as `loadGame` tolerates it, with one addition, because\n * this restore lands in a LIVE engine: a shared card whose world copies are\n * all held by the OTHER open flows is not put back, and is reported as\n * `claimed-elsewhere`. Ask `previewFlowRestore` first to see that coming.\n */\n restore?: FlowSave;\n /**\n * Open this flow with its POCKET in: a durable half from `flow.saveDurable()`,\n * written into the fresh flow before the handle comes back (ruling H). The\n * flow starts as any new flow does, on its own seed and clocks, with the\n * pocket's values laid over its defaults and its durable spends spent.\n * What no longer fits this build is reported through `onRestoreReport`, as\n * `loadDurable` reports the engine's half.\n *\n * Refused together with `restore`: a restore already carries the flow's\n * durable values, so a pocket beside it would be a second answer for the\n * same property, and which one won would be a rule nobody could see.\n */\n durable?: DurableSave;\n /** Handed the report of a `restore` or a `durable` as it happens. For a\n * restore it is the same report `previewFlowRestore` returns for the same\n * blob. Ignored without either; the report has nowhere else to go, since\n * `openFlow` returns the handle. */\n onRestoreReport?: (report: LoadReport) => void;\n}\n\n/** The schema tag every durable half carries, so the shape can change later\n * and a runtime can tell which one it was given. */\nexport const DURABLE_SCHEMA = \"storylets/durable@1\";\n\n/**\n * One DURABLE HALF (ruling H, 2026-10-06): what outlives a run. The engine's\n * half (`engine.saveDurable()`) is the installation's memory, a flow's half\n * (`flow.saveDurable()`) is the player's pocket, and the two have one shape.\n *\n * Keyed the way everything host-facing is, because a half is meant to cross\n * builds: a value by its property ADDRESS, exactly as `listProperties()`\n * prints it and `setProperty` takes it (`story.souls`, `hand.the-elder.met`,\n * `value.harbour/docks.lamps`), and a spend by the card's GAMEID. A save keys\n * its property values by internal id so they survive a rename (its cards,\n * hands and boxes by Game ID, from save version 3); a durable half keys by name\n * so a person can read it, and a rename shows up in the report instead.\n */\nexport interface DurableSave {\n schema: typeof DURABLE_SCHEMA;\n /** The build it was taken from: a load reports drift against it, and\n * refuses another project's. */\n content: BundleContent;\n /** Every durable property on this half, by address, keys sorted. */\n values: Record<string, ScalarValue>;\n /** The durable `redraw: \"never\"` spends on this half, by card gameId,\n * sorted. */\n spent: string[];\n}\n\n/** A card view in a dealt hand or a peeked list. Carries NO outcome\n * availability - ask `outcomes()` for current truth (schema 5). */\nexport interface DealtCard {\n id: string;\n gameId: string;\n title?: string;\n purpose?: string;\n fields?: Record<string, ScalarValue>;\n /** The card's Storylet extension values, by extension id, exactly as the\n * bundle carries them: raw and sparse, any JSON, never read by the engine.\n * Absent when it has none. `effectiveFields(extensionDecls(...), ...)`\n * fills in defaults. READ-ONLY: these are the bundle's own values, handed\n * out uncopied (unlike `fields`), so changing them changes them for every\n * later deal. */\n extensions?: NodeExtensions;\n}\n\nexport interface OutcomeView {\n id: string;\n gameId: string;\n title?: string;\n purpose?: string;\n /** The outcome's fields, exactly as the bundle carries them: game data\n * declared by the box's `outcomeFields`, never read by the engine. */\n fields?: Record<string, ScalarValue>;\n /** The outcome's Storylet extension values, by extension id, raw and\n * read-only, as a card's are. Absent when it has none. */\n extensions?: NodeExtensions;\n /** Evaluated against CURRENT state at the moment of the ask. */\n available: boolean;\n}\n\n/** What a peek returns: the top of the stock, looked at and put back.\n * The engine has no pick policy (Reboot 2.1). */\nexport interface RankedList {\n box: string;\n cards: DealtCard[];\n}\n\nexport interface PlayOptions {\n /** Turn advance override; default settings.playAdvancesTurns, or 0 when the\n * card's box is timed (design/engine-server.md 4.8). */\n advanceTurns?: number;\n}\n\n// --- the trace (schema 5): the deal/play log for tooling ----------------------\n\n/** Why a card did or did not make an ask, in availability order (schema 3.1). */\n/** Why a held card left its hand: an `evict` trace event's reason, and a\n * load report's when the load checks what it put back. */\nexport type EvictReason = CheckReason | \"outranked\";\n/** The reasons a held card fails the check a deal and a load both make. */\ntype CheckReason = \"hand-condition\" | \"vanished\" | \"deck-gate\" | \"taken\" | \"cooldown\" | \"tags\" | \"condition\";\n\nexport type TraceVerdict =\n | \"dealt\" // in the hand / the returned list\n | \"capped\" // eligible, ranked below the size cap\n | \"cooldown\" // schema 3.1 step 1\n | \"deck-gate\" // step 2\n | \"tags\" // step 3 (incl. the home group's inverted default)\n | \"condition\" // steps 4-5 (a failing or erroring condition)\n | \"priority\" // a priority expression errored or was not a number\n | \"claimed\" // step 6: no free copy on YOUR board\n | \"claimed-elsewhere\" // step 6: another flow holds the world's copies\n | \"taken\"; // a shared redraw:never was spent, by anyone, for everyone\n\n/** One event on the deal/play log - \"why did Ambush at the ford get dealt\n * here?\" is answered by the ask event's per-card verdicts and keys. The\n * verb is the event type, so a peek is distinguishable from a deal when\n * reading a run back.\n *\n * IDENTITY IS BY GAMEID throughout (design/engine-server.md 4.4). It was\n * mixed until then: `deal.hand` and `peek.box` were gameIds while\n * `evict.hand`, `play.card` and every `cards[].id` were internal ids, so\n * every consumer outside the engine - the Board, the four examiners, the\n * Live Link, a wire a kiosk reads - mapped one to the other itself. */\nexport type TraceEvent =\n | {\n type: \"deal\";\n /** Hand gameId. */\n hand: string;\n /** `id` is the card's GAMEID (design/engine-server.md 4.4). */\n cards: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[];\n }\n | {\n type: \"peek\";\n /** Box gameId. */\n box: string;\n criteria: Record<string, string>;\n /** `id` is the card's GAMEID (design/engine-server.md 4.4). */\n cards: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[];\n }\n /** Hand and card gameIds. A card the build no longer has (`vanished`) has\n * no gameId left and is named by the id the board carried. */\n | { type: \"evict\"; hand: string; card: string; reason: TraceVerdict | \"hand-condition\" | \"vanished\" | \"outranked\" }\n /** A card the game took out of a hand without playing it (`discard`):\n * card and hand gameIds. */\n | { type: \"discard\"; hand: string; card: string; turn: number }\n /** Card and outcome gameIds; `outcome` is \"\" for a card with no outcomes,\n * played with none. */\n | { type: \"play\"; card: string; outcome: string; turn: number }\n /** One landed outcome change; `path` is the resolved store location, in the\n * address grammar `getProperty` takes - the owner segment is its gameId (a\n * routed @hand write shows where it actually went, schema 3.6). `prev`\n * is the value it replaced, so a log can read \"0 -> 1\". */\n | { type: \"write\"; target: string; path: string; value: ScalarValue; prev?: ScalarValue }\n /** An explicit clock advance via advanceTurns (schema 3.4); `turn` is the\n * box's new value. Plays stamp their own turn on the play event. */\n | { type: \"turns\"; box: string; turn: number }\n /** An expression eval error: never a silent pass (schema 3.1), always a\n * visible diagnostic. */\n | { type: \"diagnostic\"; where: string; message: string };\n\nexport type TraceHandler = (event: TraceEvent) => void;\n/** The engine-level tap: every flow's events, tagged with the flow id -\n * the tools' one stream. */\nexport type EngineTraceHandler = (flow: string, event: TraceEvent) => void;\n\n/** A retained log entry: the trace event plus its place in flow time.\n * `seq` orders the whole flow (monotonic; survives clearLog). `turn` is\n * the clock of the box the event happened in when it fired (peek: the box;\n * deal/evict: the hand's box; play and its writes: the played card's box,\n * stamped together with the play's own turn). Diagnostics carry no turn. */\nexport type LogEntry = TraceEvent & { seq: number; turn?: number };\n\n/** One entry on the ENGINE's log: the same event, plus the flow it happened\n * in. A run is several flows over shared state, so \"what happened in this\n * run\" is only answerable in one ordered stream, and only if each line says\n * who. The flow's own log stays flow-local and unchanged. */\nexport type EngineLogEntry = LogEntry & { flow: string };\n\n// --- internals ---------------------------------------------------------------\n\ninterface CardEntry {\n card: Card<Expression>;\n deck: Deck<Expression>;\n box: Box<Expression>;\n}\n\n/** Is this card scarce across flows (design/shared-scarcity.md)? The deck says\n * what the pile is for and the card may override it, the same inheritance a\n * property has with its scope default. Hoist the deck's flag out of a card\n * loop and pass the answer down: the ask runs this per card per deal. */\nconst cardIsShared = (card: Card<Expression>, deckShared: boolean): boolean =>\n card.shared ?? deckShared;\n\n/** A card whose spend outlives the run: `redraw: \"never\"`, with `durable` on\n * the card or, failing that, its deck, the same fall-through `shared` has.\n * Only `never` can cross a run boundary: a finite cooldown is an absolute turn\n * of a clock that restarts with the run. */\nconst cardIsDurable = (card: Card<Expression>, deck: Deck<Expression>): boolean =>\n card.redraw === \"never\" && (card.durable ?? deck.durable ?? false) === true;\n\n/** How many hands ACROSS EVERY FLOW may hold this at once. Defaults to\n * `copies`, so the common case writes one number and only \"five in the world,\n * one to a customer\" needs both. Meaningless on an unshared card, and the\n * compiler warns when one sets it. */\nconst sharedCap = (card: Card<Expression>): number => card.sharedCopies ?? acrossLimit(card);\n\n/** How many hands of one flow may hold a card at once: one, or any number\n * (`acrossHands`), or a bundle from before it, its `copies`. */\nconst acrossLimit = (card: Card<Expression>): number =>\n card.acrossHands === \"any\" ? Infinity : card.acrossHands === \"one\" ? 1 : card.copies ?? 1;\n\n/**\n * One run of equally ranked cards, in the order a deal takes them (the\n * hands-on review, 2026-10, rulings 1 and 4). Cards the hand already holds\n * come first, in the order they were scored, so a re-ranking deal never swaps\n * one for an equal. The rest are shuffled, as runs always were, when their\n * weights are all equal; otherwise drawn by weight, one draw per place but the\n * last: each place takes the card whose share of the remaining weight the draw\n * falls in, walking the remaining cards in scored order. Both forms draw\n * (rest - 1) numbers, held cards none, so unweighted content deals exactly\n * as it did.\n */\nfunction orderRun<T extends { entry: CardEntry }>(run: T[], held: ReadonlyMap<string, number> | undefined, prng: Prng): T[] {\n const first = held === undefined ? [] : run.filter((s) => held.has(s.entry.card.id));\n const rest = held === undefined ? run : run.filter((s) => !held.has(s.entry.card.id));\n const weight = (s: T): number => s.entry.card.weight ?? 1;\n if (rest.length > 1 && rest.some((s) => weight(s) !== weight(rest[0]!))) {\n const drawn: T[] = [];\n const left = [...rest];\n while (left.length > 1) {\n let total = 0;\n for (const s of left) total += weight(s);\n let r = prng.next() * total;\n let k = 0;\n while (k < left.length - 1 && r >= weight(left[k]!)) { r -= weight(left[k]!); k++; }\n drawn.push(left.splice(k, 1)[0]!);\n }\n drawn.push(left[0]!);\n return [...first, ...drawn];\n }\n if (rest.length > 1) shuffleInPlace(rest, prng);\n return [...first, ...rest];\n}\n\n/** How many slots of one hand a card may fill (`inOneHand`, default 1). */\nconst inOneHandOf = (card: Card<Expression>): number => card.inOneHand ?? 1;\n\ntype HandSource = { kind: \"value\"; id: string } | { kind: \"hand\"; id: string } | { kind: \"criteria\" };\n\n/** One outcome change, resolved and checked but not yet landed. `path` is\n * the store location the trace reports, in the address grammar. */\ntype WritePlan =\n | { kind: \"host\"; host: HostScopeState; name: string; path: string }\n | { kind: \"bag\"; bag: StateBag; name: string; path: string }\n | { kind: \"scope\"; scope: string; name: string; path: string };\n\n/** The composed @hand for one ask: the read bag, plus where each name\n * routes on write (schema 3.6). */\ninterface HandEnv {\n bag: PropertyBag;\n sources: Map<string, HandSource>;\n /** tag group id -> bound tag id (home included, its \"tag\" a hand id). */\n boundTags: Map<string, string>;\n}\n\n/** One ask, resolved: a deal (hand present, condition from its template or\n * rule) or a peek (criteria only, no condition - schema 3.1). */\ninterface AskDescriptor {\n box: Box<Expression>;\n hand?: Hand<Expression>;\n condition?: Expression;\n /** tag group id -> tag id, everything the ask binds (fixed + chosen +\n * criteria; for deals also home -> the hand's own id). */\n boundTags: Map<string, string>;\n /** Chosen tags / criteria surfaced into @hand by group gameId, the tag's\n * gameId as the value (schema 3.6). */\n askNames: Record<string, string>;\n}\n\n/** The owner label on everything this engine registers: named in a clash\n * error and carried on the registry's examiner rows. */\nconst OWNER = \"Storylet Engine\";\n\n/** Identity: storylets property names are case-significant as authored. */\nconst identity = (n: string): string => n;\n\n/** A property path as the game may write it (ruling 3, 2026-10-28: Patter's\n * form as well as this engine's). `story.gold` and `@story.gold` are the same\n * path, as are `world.time` and `@world.time`; a bare `@gold` is the story's,\n * as it is in an expression. Anything else is passed through as it was. */\nfunction propertyPath(path: string): string {\n if (!path.startsWith(\"@\")) return path;\n const rest = path.slice(1);\n return rest.includes(\".\") ? rest : `story.${rest}`;\n}\n\n/** The registry keys this engine's bags live under. An id is escaped (`%` and\n * `/`) so no two keys can meet. Every runtime writes the same keys: they are\n * in the save. Owners are keyed by INTERNAL id, as the save always was, so a\n * save survives a rename. */\nconst esc = (id: string): string => id.replace(/%/g, \"%25\").replace(/\\//g, \"%2F\");\nconst unesc = (id: string): string => id.replace(/%2F/g, \"/\").replace(/%25/g, \"%\");\nconst sharedKey = (kind: OwnedScope, id: string): string => `storylets/${kind}/${esc(id)}`;\nconst flowPrefix = (flowId: string): string => `storylets/flow/${esc(flowId)}/`;\nconst flowKey = (flowId: string, kind: FlaggedScope, id?: string): string =>\n kind === \"story\" ? `${flowPrefix(flowId)}story` : `${flowPrefix(flowId)}${kind}/${esc(id!)}`;\n\n/** A registry section's values: registry key -> name -> value. */\ntype Sections = Record<string, PropertyBag>;\n\nconst emptyPartitionValues = (): PropsPartition => ({ story: {}, box: {}, deck: {}, hand: {}, value: {} });\n\n/** Sort a registry save's sections back into partitions, for the load walk:\n * the shared ones, each flow's (only the flows the save restores; the rest\n * are dropped), and everything that is not this engine's, passed through. */\nfunction partitionsFromSections(sections: Sections, flowIds: Set<string>): {\n shared: PropsPartition; flows: Map<string, PropsPartition>; rest: Sections;\n} {\n const shared = emptyPartitionValues();\n const flows = new Map<string, PropsPartition>();\n const rest: Sections = {};\n const flowOf = (escaped: string): PropsPartition | undefined => {\n const id = unesc(escaped);\n if (!flowIds.has(id)) return undefined;\n let p = flows.get(id);\n if (!p) { p = emptyPartitionValues(); flows.set(id, p); }\n return p;\n };\n for (const [key, values] of Object.entries(sections)) {\n let m: RegExpExecArray | null;\n if (key === \"story\") shared.story = values;\n else if ((m = /^storylets\\/(box|deck|hand|value)\\/([^/]+)$/.exec(key))) shared[m[1] as OwnedScope][unesc(m[2]!)] = values;\n else if ((m = /^storylets\\/flow\\/([^/]+)\\/story$/.exec(key))) { const p = flowOf(m[1]!); if (p) p.story = values; }\n else if ((m = /^storylets\\/flow\\/([^/]+)\\/(box|deck|hand|value)\\/([^/]+)$/.exec(key))) {\n const p = flowOf(m[1]!); if (p) p[m[2] as OwnedScope][unesc(m[3]!)] = values;\n } else if (!key.startsWith(\"storylets/\")) rest[key] = values;\n }\n return { shared, flows, rest };\n}\n\n/** A cleaned partition as registry sections, keyed the way its bags register.\n * Empty sections are left out: they would load nothing, and a section for a\n * bag that never registers would wait in the registry for ever. */\nfunction sectionsOf(p: PropsPartition, keyOf: (kind: FlaggedScope, id?: string) => string, out: Sections): void {\n if (Object.keys(p.story).length > 0) out[keyOf(\"story\")] = p.story;\n for (const kind of OWNED_SCOPES) {\n for (const [id, values] of Object.entries(p[kind])) if (Object.keys(values).length > 0) out[keyOf(kind, id)] = values;\n }\n}\n\n// Stores are shared-kernel bags (@wildwinter/scoperegistry, the properties\n// implementer Patter shares): identity normalisation because storylets\n// property names are case-significant as authored.\n// `pathPrefix` carries its own separator, so a bag composes its rows' addresses itself\n// (`story.gold`, `deck.tavern.drawn`) instead of every caller pasting a prefix onto a row.\nconst bagFromDecls = (decls: PropertyDecl[], pathPrefix: string): StateBag =>\n new StateBag(decls, { normalise: identity, pathPrefix });\n\n/** One name read from a bag as an OWN value. The bag's `values` is an ordinary\n * object, so a plain index would answer `constructor`, `toString` and the rest\n * of Object.prototype with a function: an expression reading `@deck.constructor`\n * must find nothing there, as it would in every other runtime. Identity\n * normalisation (above) means the authored name IS the key. */\nconst ownValue = (bag: StateBag | undefined, name: string): ScalarValue | undefined =>\n bag !== undefined && Object.prototype.hasOwnProperty.call(bag.values, name) ? bag.values[name] : undefined;\n\n/** The scope an expression sees where there is nothing to read (@deck in a\n * hand condition, a box or deck with no reader): empty, and with no prototype,\n * so no built-in property answers either. */\nconst EMPTY_SCOPE: PropertyBag = Object.freeze(Object.create(null) as PropertyBag);\n\n// Truthiness for a bare condition. Booleans and numbers as you would expect;\n// a string passes when non-empty and a flag list when non-empty, matching\n// JavaScript's own coercion for those two.\n//\n// Until 2026-09-01 this admitted ONLY booleans and numbers, and Patterplay's\n// `truthy` admitted strings and lists as well. That was drift from writing the\n// two engines at different times, not a considered difference: the two share a\n// property registry, so the same value read from the same registry answered a\n// condition differently depending on which engine asked. expr-specificity calls\n// truthiness host-bound, and it is, but \"host-bound\" is licence for a host to\n// choose, not licence for two hosts in one family to disagree by accident.\nfunction conditionPasses(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[] (flags)\n}\n\n/** One kernel bag with its store path prefix (story / box.<gameId> / deck.<gameId>\n * / hand.<gameId> / value.<gameId>): the state logger's mount surface\n * (design/engine-runtimes.md 3.4 - the logger builds on the PropertyBag\n * audit hook, so it needs the bags themselves, not just their rows).\n * The Engine lists the shared bags, a Flow its own; the @world container\n * is the host's bag and the host mounts it itself. loadGame() rebuilds\n * every flow, and so every flow's bags, so re-enumerate after a load. */\nexport interface BagMount {\n prefix: string;\n bag: StateBag;\n}\n\n/** One box on the enumeration surface (examiners, hosts): identity plus\n * its clock (per flow). */\nexport interface BoxView {\n id: string;\n gameId: string;\n title?: string;\n turn: number;\n /** The box's Storylet extension values, by extension id, raw and read-only, as a card's are. Absent when it has none. */\n extensions?: NodeExtensions;\n}\n\n/** The sharing default per scope (design/flows.md, the old system's rule):\n * @story is the playthrough family's globals, shared; the narrower scopes\n * are geographic, per-flow, so \"this flow's danger in the docks\" stays\n * expressible. A declaration's `shared` flag overrides. */\nconst SCOPE_DEFAULT_SHARED = { story: true, box: false, deck: false, hand: false, value: false } as const;\ntype FlaggedScope = keyof typeof SCOPE_DEFAULT_SHARED;\n\nconst isShared = (scope: FlaggedScope, d: PropertyDecl): boolean => d.shared ?? SCOPE_DEFAULT_SHARED[scope];\nconst sharedHalf = (scope: FlaggedScope, decls: PropertyDecl[]): PropertyDecl[] =>\n decls.filter((d) => isShared(scope, d));\nconst flowHalf = (scope: FlaggedScope, decls: PropertyDecl[]): PropertyDecl[] =>\n decls.filter((d) => !isShared(scope, d));\n\n/** One side's five declaration lists, keyed by owner id where the scope has\n * owners. The bags are built from these; so is the load report's answer to\n * \"what does this build declare that the save does not carry\". */\ninterface DeclSet {\n story: PropertyDecl[];\n box: Map<string, PropertyDecl[]>;\n deck: Map<string, PropertyDecl[]>;\n hand: Map<string, PropertyDecl[]>;\n value: Map<string, PropertyDecl[]>;\n}\n\n/** The four owned property scopes: the ones whose address carries an owner\n * segment. `story` has no owner and `world` is the host's. */\ntype OwnedScope = \"box\" | \"deck\" | \"hand\" | \"value\";\nconst OWNED_SCOPES = [\"box\", \"deck\", \"hand\", \"value\"] as const;\nconst isOwnedScope = (kind: string | undefined): kind is OwnedScope =>\n (OWNED_SCOPES as readonly string[]).includes(kind ?? \"\");\n\n/** The owner segment of a property address, both ways round\n * (design/engine-server.md 4.4).\n *\n * `gameId` is the segment the ADDRESS uses; `id` is the internal id everything\n * inside the engine is keyed by - the bags, the save envelope, the ladders.\n * Both maps are built in bundle order and a repeated gameId does NOT\n * overwrite the first.\n *\n * Box, deck, hand and card gameIds are unique bundle-wide, so for three of the\n * four scopes the segment is simply the gameId. A TAG's is unique only within\n * its group, and a group's only within its box, so two boxes may each name a\n * tag \"docks\": the value scope's segment is box-qualified,\n * `value.<boxGameId>/<tagGameId>.<name>`, wherever a gameId repeats, and the\n * short form is REFUSED there rather than resolved to the first in bundle\n * order. `valueAddresses` in the model is the one definition of that rule -\n * the Board draws these addresses from the bundle while the engine builds them\n * from this index, and the two have to agree - and `repeated` is what it\n * found, so a refusal can name the candidates.\n *\n * Two GROUPS in one box naming the same tag is the case the box qualifier\n * cannot separate, and it is closing at the source rather than here (question\n * 16, ruled 2026-09-06): the compiler warns that a tag gameId must be unique\n * within its box, and refuses it from the next release. Until then the first\n * in bundle order answers, as it always did.\n */\ninterface OwnerIndex {\n gameId: Map<string, string>; // internal id -> owner segment\n id: Map<string, string>; // owner segment -> internal id (first in bundle order wins)\n repeated: Map<string, string[]>; // an ambiguous short form -> the qualified candidates\n zoneQualified: Map<string, string>; // \"<box>/<zone>\" -> \"<zone>\", refused (value scope only)\n}\n\ntype OwnerIndexes = Record<OwnedScope, OwnerIndex>;\n\nconst emptyOwnerIndex = (): OwnerIndex =>\n ({ gameId: new Map(), id: new Map(), repeated: new Map(), zoneQualified: new Map() });\nconst emptyOwnerIndexes = (): OwnerIndexes => ({\n box: emptyOwnerIndex(), deck: emptyOwnerIndex(), hand: emptyOwnerIndex(), value: emptyOwnerIndex(),\n});\n\nconst indexOwner = (index: OwnerIndex, entity: { id: string; gameId?: string; title?: string }): void => {\n const gameId = effectiveGameId(entity);\n index.gameId.set(entity.id, gameId);\n if (!index.id.has(gameId)) index.id.set(gameId, entity.id);\n};\n\n/** The value scope's index, whole: the segments to print, the segments to\n * accept, the gameIds that need qualifying, and the box-qualified forms of\n * the project map's zones, which are refused. Built from the bundle rather\n * than tag by tag, because whether a tag's own gameId is enough is a question\n * about the OTHER boxes. */\nconst indexValueOwners = (index: OwnerIndex, bundle: Bundle): void => {\n const addresses = valueAddresses(bundle);\n for (const [id, segment] of addresses.print) index.gameId.set(id, segment);\n for (const [segment, id] of addresses.accept) index.id.set(segment, id);\n for (const [gameId, candidates] of addresses.repeated) index.repeated.set(gameId, candidates);\n for (const [segment, zone] of addresses.zoneQualified) index.zoneQualified.set(segment, zone);\n};\n\n/** One side's five stores (shared on the engine, per-flow on each flow). */\ninterface Partition {\n story: StateBag;\n box: Map<string, StateBag>;\n deck: Map<string, StateBag>;\n hand: Map<string, StateBag>;\n value: Map<string, StateBag>;\n}\n\ntype PartitionKind = keyof Partition;\n\n/** One durable declaration on one side of the sharing flag, with the address a\n * durable half keys it by and where its bag is. */\ninterface DurableProp {\n address: string;\n kind: FlaggedScope;\n /** The owner's internal id; absent for `story`. */\n owner?: string;\n decl: PropertyDecl;\n}\n\n/** Every durable declaration in one side's DeclSet, in bundle order. Where two\n * owners print the same address (two groups in one box naming a tag alike,\n * the residual the compiler is closing) the first answers, which is the rule\n * `resolveOwner` keeps for the same address. */\nconst durableProps = (internals: Internals, decls: DeclSet): DurableProp[] => {\n const out: DurableProp[] = [];\n const seen = new Set<string>();\n const push = (p: DurableProp): void => {\n if (seen.has(p.address)) return;\n seen.add(p.address);\n out.push(p);\n };\n for (const decl of decls.story) if (decl.durable === true) push({ address: `story.${decl.name}`, kind: \"story\", decl });\n for (const kind of OWNED_SCOPES) {\n for (const [owner, list] of decls[kind]) {\n for (const decl of list) {\n if (decl.durable === true) push({ address: `${addressOf(internals, kind, owner)}.${decl.name}`, kind, owner, decl });\n }\n }\n }\n return out;\n};\n\nconst durableBag = (p: Partition, prop: DurableProp): StateBag | undefined =>\n prop.kind === \"story\" ? p.story : p[prop.kind].get(prop.owner!);\n\n/** One half's values, keys in byte order so the same state writes the same\n * text. An address never looks like an integer (it has a dot in it), so the\n * object keeps that order (ruling E). */\nconst durableValues = (p: Partition, props: DurableProp[]): Record<string, ScalarValue> => {\n const entries: [string, ScalarValue][] = [];\n for (const prop of props) {\n const value = ownValue(durableBag(p, prop), prop.decl.name);\n if (value !== undefined) entries.push([prop.address, value]);\n }\n return Object.fromEntries(byKey(entries, ([address]) => address));\n};\n\n/** What a durable half will write, decided before anything is written. */\ninterface DurablePlan {\n values: Map<DurableProp, ScalarValue>;\n /** Internal card ids to spend. */\n cards: string[];\n}\n\n/**\n * Walk one durable half against this build's durable declarations and\n * durable cards on one side (`flow` names the flow for a pocket; absent, it is\n * the engine's memory), filing what does not fit in the load report's own\n * fields. Pure: the caller writes the plan.\n *\n * A value lands only at an address this build declares DURABLE ON THIS SIDE:\n * a renamed property, one no longer durable, and one that moved to the other\n * side of `shared` are all dropped, since this half would never have carried\n * them. A value its declaration no longer takes is retyped and takes the\n * default; a durable declaration the half does not carry is defaulted. A spend\n * lands only on a card that is still a durable one-shot on this side, and is\n * otherwise a dropped cooldown (a pocket's spends are cooldowns) or a dropped\n * spent card (a memory's are the engine's spent set), named by the gameId the\n * half carried.\n */\nfunction planDurable(\n internals: Internals, props: DurableProp[], cards: CardEntry[],\n save: DurableSave, flow: string | undefined, draft: ReportDraft,\n): DurablePlan {\n const at = (path: string): LoadProperty => ({ ...(flow !== undefined ? { flow } : {}), path });\n const byAddress = new Map(props.map((p) => [p.address, p]));\n const values = new Map<DurableProp, ScalarValue>();\n const carried = save.values ?? {};\n for (const [address, value] of Object.entries(carried)) {\n const prop = byAddress.get(address);\n if (prop === undefined) { draft.droppedProperties.push(at(address)); continue; }\n if (!valueFits(prop.decl, value)) { draft.retypedProperties.push(at(address)); continue; }\n values.set(prop, value);\n }\n for (const prop of props) {\n if (!Object.prototype.hasOwnProperty.call(carried, prop.address)) draft.defaultedProperties.push(at(prop.address));\n }\n const eligible = new Set(cards.map((e) => e.card.id));\n const spend: string[] = [];\n for (const gameId of save.spent ?? []) {\n const entry = internals.cardsByGameId.get(gameId);\n if (entry !== undefined && eligible.has(entry.card.id)) spend.push(entry.card.id);\n else if (flow !== undefined) draft.droppedCooldowns.push({ flow, card: gameId });\n else draft.droppedSpent.push(gameId);\n }\n return { values, cards: spend };\n}\n\n/** The two refusals every durable load shares, made before anything moves: a\n * shape this runtime does not know, and another project's state. */\nconst assertDurable = (internals: Internals, save: DurableSave): void => {\n const schema = (save as { schema?: unknown } | null | undefined)?.schema;\n if (schema !== DURABLE_SCHEMA) throw new StoryletError(`unsupported durable schema: ${String(schema)}`);\n const project = internals.bundle.content.project;\n if (save.content?.project !== project) {\n throw new StoryletError(`durable state is for project \"${String(save.content?.project)}\", bundle is \"${project}\"`);\n }\n};\n\n/** A host write of one durable value: silent, as every host write is, and\n * past a `writable: false`, which is the story's promise and not the game's. */\nconst putDurable = (bag: StateBag | undefined, name: string, value: ScalarValue): void => {\n bag?.set(name, structuredClone(value), { silent: true, reason: \"host durable\", host: true });\n};\n\n/** Everything a Flow shares with its Engine: the bundle-derived lookups\n * (immutable), the shared stores (reseeded in place by loadGame/reset),\n * and the seams. One object, held by both classes - the two are one\n * machine in two lifetimes. */\ninterface Internals {\n bundle: Bundle;\n logCap?: number;\n cardsById: Map<string, CardEntry>;\n cardsByGameId: Map<string, CardEntry>;\n boxesByGameId: Map<string, Box<Expression>>;\n boxesById: Map<string, Box<Expression>>;\n handsById: Map<string, { hand: Hand<Expression>; box: Box<Expression> }>;\n handsByGameId: Map<string, { hand: Hand<Expression>; box: Box<Expression> }>;\n /** The owner segment of a property address, both ways round (4.4). */\n owners: OwnerIndexes;\n templatesById: Map<string, HandTemplate<Expression>>;\n /** Every group by internal id. `box` is absent for the project map's group,\n * which belongs to no box. */\n groupsById: Map<string, { group: TagGroup; box?: Box<Expression> }>;\n requiredGroups: Set<string>;\n nodeCache: WeakMap<Expression, ExprNode>;\n ladders: {\n /** Each host scope's ladders, by token, then name. */\n host: Map<string, Map<string, readonly string[]>>;\n story: Map<string, readonly string[]>;\n box: Map<string, Map<string, readonly string[]>>;\n deck: Map<string, Map<string, readonly string[]>>;\n value: Map<string, Map<string, readonly string[]>>;\n hand: Map<string, Map<string, readonly string[]>>;\n };\n hasQualities: boolean;\n /** Does ANY deck or card in the bundle opt into shared scarcity? False for\n * the overwhelming majority of projects, and when it is false the two\n * claim-ledger walks in dealing are skipped entirely. Same idea as\n * `hasQualities` above: a bundle that does not use a feature must not pay\n * for it. */\n hasShared: boolean;\n /** The per-flow halves of every declaration list, precomputed once: each\n * new flow builds its bags from these. */\n flowDecls: DeclSet;\n /** The shared halves, the same way. Not used to build anything - the shared\n * bags are built straight from the bundle - but a load report has to say\n * what the shared side WOULD hold without building a bag, which is what\n * makes previewLoad pure. */\n sharedDecls: DeclSet;\n /** The durable declarations and durable cards on each side (ruling H),\n * precomputed once from the two DeclSets and the decks. */\n durable: { shared: DurableProp[]; flow: DurableProp[]; sharedCards: CardEntry[]; flowCards: CardEntry[] };\n /** The shared stores, registered in the registry for the engine's life.\n * Reseeded in place by reset and by a load that carries values. */\n shared: Partition;\n /** The game's one registry (or the engine's own, when it is standalone). */\n registry: ScopeRegistry;\n /** True when the engine made the registry: `saveGame()` then carries its values. */\n ownsRegistry: boolean;\n /** Every OTHER scope in the registry, as an eval context sees it (instance\n * keys left out), rebuilt only when the registry's set of scopes moves. */\n registryView: () => { scopes: EvalContext[\"scopes\"]; qualities: EvalContext[\"qualities\"] };\n /** Every host scope the bundle declares (`@world`, `@player`, ...), by\n * token, in bundle order. */\n hostScopes: Map<string, HostScopeState>;\n /** The host scopes as an eval context's scopes, by token. */\n hostResolvers: Record<string, ScopeResolver>;\n /** The game's `onError` (EngineOptions), or the console warning. */\n onError: (error: PlayError) => void;\n /** Take this engine's share of the registry's refits and tell the game\n * (EngineOptions.onRefit, or the console warning). */\n reportRefits: () => void;\n /** `turn` is the box clock the event happened on, where the caller knows it\n * - the same stamp the flow's own log carries. Unity and Unreal passed it\n * from the start; JS and Godot dropped it, so their examiners printed \"[-]\"\n * on every deal, peek, evict and write line while the other two printed the\n * real turn. Four runtimes, two different run logs (2026-08-29). */\n emitEngine: (flow: string, event: TraceEvent, turn?: number) => void;\n engineTracing: () => boolean;\n}\n\n/** One host scope the bundle declares, as the engine serves it. */\ninterface HostScopeState {\n spec: HostScopeSpec;\n /** Read through the registry by name (the scope's own normalisation), so a\n * scope the game registered folded to lower case still answers the names\n * as authored. */\n resolver: ScopeResolver;\n /** The WRITE seam. `host` says the caller is the GAME's own surface -\n * setProperty, the coverage harness, the CLI's --set - which the shared\n * kernel lets past a `writable: false` (scoperegistry 0.6.0): that flag is\n * the story's promise, not the game's. The story's refusal is `readOnly`\n * below, consulted before this seam is reached. A BOUND resolver is opaque -\n * it takes a name and a value and keeps whatever rule the game has - so the\n * flag only ever reaches a self-backed bag. Undefined when the scope cannot\n * be written at all (a resolver bound with no `set`). */\n set?: (name: string, value: ScalarValue, host?: boolean) => void;\n /** True when this engine self-backed it (standalone, no resolver bound). */\n selfBacked: boolean;\n}\n\n/** Is a host scope's property read-only TO THE STORY? Its own `writable:\n * false`, or its scope's: the story's promise, kept at run time as the\n * compiler keeps it at publish (Reboot.md 10). */\nconst hostReadOnly = (spec: HostScopeSpec, name: string): boolean =>\n (spec.declarations?.find((d) => d.name === name)?.writable ?? spec.writable) === false;\n\n/** A host scope declaration as the registry takes it, for a foreign scope. */\nconst toForeignDecl = (d: HostScopeDecl): ScopeDeclaration => ({\n name: d.name, type: d.type,\n ...(d.values !== undefined ? { values: d.values } : {}),\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n ...(d.default !== undefined ? { default: d.default } : {}),\n ...(d.writable !== undefined ? { writable: d.writable } : {}),\n});\n\n/** A self-backed host scope's declarations: the scope's own `writable`\n * folded into each, since an owned bag reads writability per declaration. */\nconst selfBackedDecls = (spec: HostScopeSpec): ScopeDeclaration[] =>\n (spec.declarations ?? []).map((d) => {\n const decl = toForeignDecl(d);\n const writable = d.writable ?? spec.writable;\n return writable === undefined ? decl : { ...decl, writable };\n });\n\n/** A template instance inherits the template's property declarations; a\n * standalone hand declares its own (schema 2.6). `templatesById` is complete\n * before anything asks: the constructor indexes every box first. */\nconst handDeclsOf = (internals: Internals, hand: Hand<Expression>): PropertyDecl[] =>\n hand.template !== undefined\n ? internals.templatesById.get(hand.template)?.properties ?? []\n : hand.properties ?? [];\n\n/**\n * One owned property's ADDRESS, owner segment and all: `box.village.mood`.\n *\n * The stores, the save envelope and the ladders stay keyed by internal id -\n * a save must survive a rename, which is the whole reason ids exist - so this\n * is the one place the two vocabularies meet, and it is a formatter, never a\n * lookup key. An owner the build no longer has (a save that outlived an edit)\n * keeps the id it arrived with: there is no gameId left to give it, which is\n * the rule a load report's evictions have always used.\n */\nconst addressOf = (internals: Internals, kind: OwnedScope, id: string): string =>\n `${kind}.${internals.owners[kind].gameId.get(id) ?? id}`;\n\n/**\n * Resolve a property address's owner segment to the internal id the stores are\n * keyed by. `legacy` says the caller used the pre-4.4 form - an internal id\n * where a gameId belongs - which resolves for THIS release and earns a\n * diagnostic; the next lockstep release refuses it, in every scope including\n * `value`, which had the one reprieve until it gained an address of its own.\n * `ambiguous` is a short-form value address two boxes answer to: the caller\n * REFUSES it, listing those candidates. Undefined when the segment names no\n * owner at all, which is the caller's \"no <kind> store\" error.\n */\ntype OwnerLookup = { id: string; legacy: boolean } | { ambiguous: string[] } | { zone: string };\n\nconst resolveOwner = (internals: Internals, kind: OwnedScope, segment: string): OwnerLookup | undefined => {\n // Checked before the lookup, because the short form is deliberately NOT in\n // the accept map when it is ambiguous: silently picking the first tag in\n // bundle order is the bug this removes.\n const candidates = internals.owners[kind].repeated.get(segment);\n if (candidates !== undefined) return { ambiguous: candidates };\n // A box-qualified form of a project-map zone: never accepted, because the\n // zone belongs to no box (design/project-map-contract.md 3.4).\n const zone = internals.owners[kind].zoneQualified.get(segment);\n if (zone !== undefined) return { zone };\n const byGameId = internals.owners[kind].id.get(segment);\n if (byGameId !== undefined) return { id: byGameId, legacy: false };\n // A gameId that equals its id took the branch above, so anything reaching\n // here and known as an id is genuinely the old spelling.\n if (internals.owners[kind].gameId.has(segment)) return { id: segment, legacy: true };\n return undefined;\n};\n\n/** Resolve or throw: the two refusals every property address shares, in one\n * place, so the engine's surface and a flow's answer alike. */\nconst ownerOrThrow = (internals: Internals, kind: OwnedScope, segment: string, name: string): { id: string; legacy: boolean } => {\n const owner = resolveOwner(internals, kind, segment);\n if (owner === undefined) throw new StoryletError(`no ${kind} store \"${segment}\"`);\n if (\"ambiguous\" in owner) throw new StoryletError(ambiguousValueAddressMessage(segment, name, owner.ambiguous));\n if (\"zone\" in owner) throw new StoryletError(zoneQualifiedValueAddressMessage(segment, owner.zone, name));\n return owner;\n};\n\n/** What a legacy address is told. It NAMES the address to move to, because\n * \"that form is deprecated\" without the replacement leaves a host grepping a\n * bundle for ids it never chose. */\nconst legacyAddressMessage = (internals: Internals, kind: OwnedScope, segment: string, name: string): string =>\n `\"${kind}.${segment}.${name}\" names the ${kind} by its internal id; write \"${addressOf(internals, kind, segment)}.${name}\". `\n + `The internal-id form is refused after the next release.`;\n\n/** Build one side of the partition from that side's declarations (the\n * engine precomputes both halves once, in bundle order, as a DeclSet). The\n * bags are KEYED by internal id and ADDRESSED by gameId; see addressOf. */\nconst buildPartition = (internals: Internals, decls: DeclSet): Partition => {\n const bags = (kind: OwnedScope): Map<string, StateBag> => new Map([...decls[kind]].map(\n ([id, list]): [string, StateBag] => [id, bagFromDecls(list, `${addressOf(internals, kind, id)}.`)]));\n return {\n story: bagFromDecls(decls.story, \"story.\"),\n box: bags(\"box\"), deck: bags(\"deck\"), hand: bags(\"hand\"), value: bags(\"value\"),\n };\n};\n\nconst partitionValues = (p: Partition): PropsPartition => ({\n story: p.story.values,\n box: Object.fromEntries([...p.box].map(([id, bag]) => [id, bag.values])),\n deck: Object.fromEntries([...p.deck].map(([id, bag]) => [id, bag.values])),\n hand: Object.fromEntries([...p.hand].map(([id, bag]) => [id, bag.values])),\n value: Object.fromEntries([...p.value].map(([id, bag]) => [id, bag.values])),\n});\n\nconst loadPartition = (p: Partition, values: PropsPartition | undefined): void => {\n // Fresh defaults are already in the bags; the saved values land over\n // them: orphaned keys (deleted entities, re-flagged properties) drop;\n // newly declared properties keep defaults.\n p.story.load(values?.story ?? {});\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of Object.entries(values?.[kind] ?? {})) {\n p[kind].get(id)?.load(bag);\n }\n }\n};\n\n// --- the load report (design/engine-server.md 4.9) ---------------------------\n//\n// One walk, two entry points. `previewLoad` runs it and returns the report;\n// `loadGame` runs it, returns the same report and then applies the CLEANED\n// blob the walk produced. Two implementations of \"what does this save cost\"\n// would drift the first time one of them was fixed, so there is one, and the\n// apply half consumes its output rather than repeating its decisions.\n\n/** The report under construction: unsorted, until finishReport orders it. */\ninterface ReportDraft {\n evicted: LoadEviction[];\n droppedCooldowns: { flow: string; card: string }[];\n droppedSpent: string[];\n droppedProperties: LoadProperty[];\n defaultedProperties: LoadProperty[];\n retypedProperties: LoadProperty[];\n}\n\nconst emptyDraft = (): ReportDraft => ({\n evicted: [], droppedCooldowns: [], droppedSpent: [],\n droppedProperties: [], defaultedProperties: [], retypedProperties: [],\n});\n\n/** The sort key separator: a UNIT SEPARATOR, as the play-history indexes use,\n * because it cannot occur in an id, a gameId or a property name. */\nconst SORT_SEP = \"\\u001f\";\n\nconst byKey = <T>(items: T[], key: (item: T) => string): T[] =>\n [...items].map((item) => ({ item, k: key(item) }))\n .sort((a, b) => (a.k < b.k ? -1 : a.k > b.k ? 1 : 0))\n .map((e) => e.item);\n\nconst propKey = (p: LoadProperty): string => `${p.flow ?? \"\"}${SORT_SEP}${p.path}`;\n\n// Whether a saved value still fits its declaration is the kernel's `valueFits`\n// (@wildwinter/expr), shared with Patter's loads: the type first, then the\n// declaration's own vocabulary, so an enum value or a stage the edit struck out\n// no longer fits though it is still a string.\n\n/** Walk one bag's worth of saved values against one bag's worth of\n * declarations: report the orphans, the newcomers and the misfits, and\n * return the values that survive. */\nfunction walkScope(\n decls: PropertyDecl[] | undefined,\n saved: PropertyBag | undefined,\n path: (name: string) => string,\n flow: string | undefined,\n draft: ReportDraft,\n): PropertyBag {\n const at = (name: string): LoadProperty =>\n ({ ...(flow !== undefined ? { flow } : {}), path: path(name) });\n const byName = new Map((decls ?? []).map((d) => [d.name, d]));\n const values = saved ?? {};\n const clean: PropertyBag = {};\n for (const [name, value] of Object.entries(values)) {\n const decl = byName.get(name);\n if (decl === undefined) { draft.droppedProperties.push(at(name)); continue; }\n if (!valueFits(decl, value)) { draft.retypedProperties.push(at(name)); continue; }\n clean[name] = value;\n }\n for (const decl of decls ?? []) {\n if (!(decl.name in values)) draft.defaultedProperties.push(at(decl.name));\n }\n return clean;\n}\n\n/** The same walk over all five scopes of one partition. An owner the save\n * carries and the build no longer has drops whole (its bag is gone, so its\n * values have nowhere to land); an owner the build has and the save lacks\n * keeps every default. */\nfunction walkPartition(\n internals: Internals,\n decls: DeclSet,\n values: PropsPartition | undefined,\n flow: string | undefined,\n draft: ReportDraft,\n): PropsPartition {\n const out: PropsPartition = {\n story: walkScope(decls.story, values?.story, (n) => `story.${n}`, flow, draft),\n box: {}, deck: {}, hand: {}, value: {},\n };\n for (const kind of OWNED_SCOPES) {\n const savedKind = values?.[kind] ?? {};\n const ids = [...new Set([...decls[kind].keys(), ...Object.keys(savedKind)])].sort();\n for (const id of ids) {\n // The report's `path` is exactly what listProperties() prints and what\n // setProperty takes: one grammar, so an operator reading a hot-swap\n // report can paste the address straight back in (4.4).\n const owner = addressOf(internals, kind, id);\n out[kind][id] = walkScope(decls[kind].get(id), savedKind[id],\n (n) => `${owner}.${n}`, flow, draft);\n }\n }\n return out;\n}\n\n/** Order the draft and answer the identity questions. `saved` is the content\n * block the save carries; for a single-flow restore there is none, so the\n * caller passes the bundle's own and no drift is reported. */\nfunction finishReport(bundle: BundleContent, saved: BundleContent, flows: string[], draft: ReportDraft): LoadReport {\n const drift = saved.version !== bundle.version || saved.hash !== bundle.hash;\n const evicted = byKey(draft.evicted, (e) => [e.flow, e.hand, e.card, e.reason].join(SORT_SEP));\n const droppedCooldowns = byKey(draft.droppedCooldowns, (c) => `${c.flow}${SORT_SEP}${c.card}`);\n const droppedSpent = [...draft.droppedSpent].sort();\n const droppedProperties = byKey(draft.droppedProperties, propKey);\n const defaultedProperties = byKey(draft.defaultedProperties, propKey);\n const retypedProperties = byKey(draft.retypedProperties, propKey);\n return {\n // `flows` is what the load restores, not something it had to change, so\n // it never makes a report inexact.\n exact: !drift && evicted.length === 0 && droppedCooldowns.length === 0 && droppedSpent.length === 0\n && droppedProperties.length === 0 && defaultedProperties.length === 0 && retypedProperties.length === 0,\n project: bundle.project,\n version: { saved: saved.version, bundle: bundle.version },\n hash: { saved: saved.hash, bundle: bundle.hash },\n flows,\n evicted, droppedCooldowns, droppedSpent,\n droppedProperties, defaultedProperties, retypedProperties,\n };\n}\n\n// --- inputs the game hands over -------------------------------------------------\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n/** What a refused input was, for the message: `null`, `an array`, `a string`. */\nconst describeInput = (value: unknown): string =>\n value === null ? \"null\" : value === undefined ? \"nothing\" : Array.isArray(value) ? \"an array\" : `a ${typeof value}`;\n\n/** The parts of a bundle the engine reads before anything else, each named\n * with what it should be; empty when they are all there. */\nconst bundleShapeProblems = (bundle: Record<string, unknown>): string[] => {\n const problems: string[] = [];\n if (!isRecord(bundle[\"content\"])) problems.push(\"no content\");\n if (!isRecord(bundle[\"settings\"])) problems.push(\"no settings\");\n // A bundle @1 or @0 has a `world` block; from @2 the game's scopes are\n // `scopeRegistry`, absent when the project declares none.\n const scopes = bundle[\"schema\"] === BUNDLE_SCHEMA ? [\"story\"] as const : [\"story\", \"world\"] as const;\n for (const scope of scopes) {\n const part = bundle[scope];\n if (!isRecord(part) || !Array.isArray(part[\"properties\"])) problems.push(`no ${scope} properties`);\n }\n const registry = bundle[\"scopeRegistry\"];\n if (registry !== undefined && (!isRecord(registry) || !Array.isArray(registry[\"scopes\"]))) problems.push(\"no host scopes in its scopeRegistry\");\n if (!Array.isArray(bundle[\"boxes\"])) problems.push(\"no boxes\");\n return problems;\n};\n\n/** Refuse a save envelope this runtime cannot read, by what is wrong with it,\n * before anything reads it (the hands-on review, 2026-10, foundation 5). */\nconst refuseUnreadableSave = (envelope: unknown): void => {\n if (!isRecord(envelope)) throw new StoryletError(`not a Storylets save: expected an object, got ${describeInput(envelope)}`);\n const schema = envelope[\"schema\"];\n if (schema !== SAVE_SCHEMA && schema !== SAVE_SCHEMA_V2 && schema !== SAVE_SCHEMA_V1) {\n throw new StoryletError(`unsupported save schema: ${String(schema)} (this runtime reads ${SAVE_SCHEMA_V1}, ${SAVE_SCHEMA_V2} and ${SAVE_SCHEMA})`);\n }\n if (!isRecord(envelope[\"content\"])) throw new StoryletError(\"not a complete Storylets save: no content\");\n};\n\n/** What a declared property takes, in words, for a refused host write. */\nconst takes = (decl: PropertyDecl | HostScopeDecl): string => {\n const quoted = (xs: readonly string[] | undefined): string => (xs ?? []).map((x) => JSON.stringify(x)).join(\", \");\n switch (decl.type) {\n case \"number\": return \"a number\";\n case \"boolean\": return \"true or false\";\n case \"enum\": return `one of ${quoted(decl.values)}`;\n case \"flags\": return decl.values !== undefined && decl.values.length > 0 ? `a list of flags from ${quoted(decl.values)}` : \"a list of flags\";\n case \"quality\": return `one of its stages ${quoted(decl.stages)}`;\n default: return \"text\";\n }\n};\n\n// --- save keys ------------------------------------------------------------------\n//\n// A save names cards, hands and boxes by GAME ID from version 3 (the hands-on\n// review, 2026-10, foundation 7), as its play log always did: a card deleted\n// and made again under the same Game ID is the same card to a game, so it\n// keeps its cooldown and its place as well as its play count. Earlier saves\n// name them by internal id, so a saved key is read as a Game ID first and an\n// internal id second; the two never collide, ids being generated.\n\nconst savedCard = (internals: Internals, key: string): CardEntry | undefined =>\n internals.cardsByGameId.get(key) ?? internals.cardsById.get(key);\nconst savedHand = (internals: Internals, key: string): { hand: Hand<Expression>; box: Box<Expression> } | undefined =>\n internals.handsByGameId.get(key) ?? internals.handsById.get(key);\nconst savedBox = (internals: Internals, key: string): Box<Expression> | undefined =>\n internals.boxesByGameId.get(key) ?? internals.boxesById.get(key);\n/** The Game ID a save names an entity by; the id itself for one the build no\n * longer has, which is all there is to name it by. */\nconst cardKey = (internals: Internals, id: string): string => {\n const entry = internals.cardsById.get(id);\n return entry !== undefined ? effectiveGameId(entry.card) : id;\n};\n\n/**\n * Refuse a host write that does not fit what the content declared (the\n * hands-on review, 2026-10, foundation 5): a string for a number, a value an\n * enum or a quality does not list, a flag a flags property does not, or a name\n * the content never declared in a host scope it declares. The rule is the\n * kernel's `valueFits`, the one a load walks saved values with, so a value the\n * game may set is exactly one a save may carry. An opaque host scope declares\n * no names, so any name and value in it is the game's. Another engine's scope\n * (`patter.x`) is that engine's to check.\n */\nconst checkHostWrite = (internals: Internals, path: string, value: ScalarValue): void => {\n const parts = path.split(\".\");\n let decl: PropertyDecl | HostScopeDecl | undefined;\n const host = parts.length === 2 ? internals.hostScopes.get(parts[0]!) : undefined;\n if (host !== undefined) {\n const decls = host.spec.declarations;\n if (decls === undefined) return;\n const name = parts[1]!;\n decl = decls.find((d) => d.name === name) ?? decls.find((d) => d.name.toLowerCase() === name.toLowerCase());\n if (decl === undefined) throw new StoryletError(`no property at \"${path}\": the content declares no @${host.spec.token}.${name}`);\n } else if (parts.length === 2 && parts[0] === \"story\") {\n const name = parts[1]!;\n decl = internals.sharedDecls.story.find((d) => d.name === name) ?? internals.flowDecls.story.find((d) => d.name === name);\n } else if (parts.length === 3 && isOwnedScope(parts[0])) {\n const kind = parts[0];\n const [, segment, name] = parts as unknown as [string, string, string];\n const id = ownerOrThrow(internals, kind, segment, name).id;\n decl = internals.sharedDecls[kind].get(id)?.find((d) => d.name === name)\n ?? internals.flowDecls[kind].get(id)?.find((d) => d.name === name);\n }\n // An enum whose own default is \"\" (none chosen, what an editor writes for\n // \"(none)\") takes \"\" back, so a game can put its default back.\n if (decl !== undefined && !valueFits(decl, value, { noneChosen: decl.type === \"enum\" && decl.default === \"\" })) {\n throw new StoryletError(`\"${path}\" takes ${takes(decl)}; ${JSON.stringify(value)} doesn't fit`);\n }\n};\n\n/** Every declared host scope's properties as examiner rows, read through its\n * resolver, in bundle order (an opaque scope declares none, so lists none). */\nconst hostRows = (internals: Internals): PropertyRow[] => {\n const out: PropertyRow[] = [];\n for (const host of internals.hostScopes.values()) {\n for (const d of host.spec.declarations ?? []) {\n const value = host.resolver.get(d.name);\n out.push({\n path: `${host.spec.token}.${d.name}`, name: d.name, type: d.type,\n value: value ?? defaultFor(d), default: defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n ...(d.stages !== undefined ? { stages: d.stages } : {}),\n // A host scope is FOREIGN - a host resolver backs it - so writability is whether\n // that resolver can be written at all AND what the declaration says, which is\n // the shared registry's own rule for a foreign scope (its foreignWritable).\n //\n // A row is where `writable: false` is meant to SHOW (Reboot.md 10): it tells a\n // state panel this is the game's value, not the story's. It does not stop the\n // panel editing it - the host's setProperty passes `{ host: true }`.\n writable: host.set !== undefined && !hostReadOnly(host.spec, d.name),\n });\n }\n }\n return out;\n};\n\n// --- the Engine ---------------------------------------------------------------\n\n/**\n * Refuse, at construction, a bundle this engine cannot read faithfully\n * (design/project-map-contract.md 3.8). Throws one error naming every problem\n * found, each naming the box and the group or tag at fault.\n *\n * The compiler refuses all of these first. The engine checks again because it\n * cannot tell a hand-built or stale bundle from a compiled one, and the\n * alternative is the silent half-working the server audit found twice: a hand\n * whose bound group is looked up in the wrong place comes back empty, and an\n * old runtime given a map bundle deals plausibly while every zone value is\n * missing. Only what would make the engine's OWN resolution ambiguous or wrong\n * is refused here; the compiler's bundle-wide group-name rule is stricter.\n */\nconst refuseUnreadableBundle = (bundle: Bundle): void => {\n // Not an object at all (`new Engine(null)`, a bundle file read as text):\n // said plainly, rather than as whichever property the engine reached for\n // first (the hands-on review, 2026-10, foundation 5).\n if (!isRecord(bundle)) throw new StoryletError(`not a Storylets bundle: expected an object, got ${describeInput(bundle)}`);\n // The schema tag (D4). Checked alone and first: a bundle of a schema this\n // runtime does not know may not have any of the shape the rest reads.\n const schema = (bundle as { schema?: unknown }).schema;\n if (typeof schema !== \"string\" || !BUNDLE_SCHEMAS.includes(schema)) {\n throw new StoryletError(`unsupported bundle schema: ${String(schema)} (this runtime reads ${BUNDLE_SCHEMAS.slice(0, -1).join(\", \")} and ${BUNDLE_SCHEMAS[BUNDLE_SCHEMAS.length - 1]})`);\n }\n // The parts every bundle of a known schema has, by name: a bundle missing\n // one is cut short or hand-built, and would fail later as a raw TypeError.\n const missing = bundleShapeProblems(bundle);\n if (missing.length > 0) throw new StoryletError(`not a complete Storylets bundle: ${missing.join(\"; \")}`);\n const problems: string[] = [];\n const map = bundle.map?.group;\n const mapName = map !== undefined ? effectiveGameId(map) : undefined;\n if (map !== undefined && mapName === PLACE_GROUP) {\n problems.push(`the project map's tag group is called \"${PLACE_GROUP}\", which is reserved for a box's own hands`);\n }\n // Every box tag gameId, for the zone-name rule: a zone's address has no\n // qualified form to fall back on (3.4), so any box tag sharing it anywhere\n // makes `value.<zone>.<name>` ambiguous.\n const boxTags = new Map<string, { box: string; group: string }>();\n for (const box of bundle.boxes) {\n for (const group of box.tagGroups) {\n for (const tag of group.tags) {\n const gameId = effectiveGameId(tag);\n if (!boxTags.has(gameId)) boxTags.set(gameId, { box: effectiveGameId(box), group: effectiveGameId(group) });\n }\n }\n }\n for (const tag of map?.tags ?? []) {\n const zone = effectiveGameId(tag);\n const clash = boxTags.get(zone);\n if (clash !== undefined) {\n problems.push(`the project map's zone \"${zone}\" has the name of tag \"${zone}\" in box \"${clash.box}\", group \"${clash.group}\", `\n + `so \"value.${zone}.<name>\" would name two things`);\n }\n }\n for (const box of bundle.boxes) {\n const boxName = effectiveGameId(box);\n if (box.usesMap === true) {\n if (map === undefined) {\n problems.push(`box \"${boxName}\" uses the project map, but the bundle has no map`);\n continue;\n }\n // One namespace in an opted-in box (3.1): a box group with the map's\n // name would make every name-based lookup there a coin toss.\n const twin = box.tagGroups.find((g) => effectiveGameId(g) === mapName);\n if (twin !== undefined) {\n problems.push(`box \"${boxName}\" uses the project map and declares its own tag group \"${mapName}\", the map's name`);\n }\n continue;\n }\n if (map === undefined) continue;\n // A box NOT on the map may not reference its group at all: every route a\n // reference takes, card tags, template bindings and holes, a hand's\n // chosen tags and a rule's bindings, by the group's id as written.\n const names = (where: string): void => {\n problems.push(`box \"${boxName}\" is not on the project map, but ${where} names the map's tag group \"${mapName}\"`);\n };\n for (const deck of box.decks) {\n for (const card of deck.cards) {\n if (card.tags?.[map.id] !== undefined) names(`card \"${effectiveGameId(card)}\"`);\n }\n }\n for (const template of box.handTemplates) {\n if (template.bindings?.[map.id] !== undefined || template.chooses?.includes(map.id) === true) {\n names(`hand template \"${effectiveGameId(template)}\"`);\n }\n }\n for (const hand of box.hands) {\n if (hand.chosen?.[map.id] !== undefined || hand.rule?.bindings?.[map.id] !== undefined) {\n names(`hand \"${effectiveGameId(hand)}\"`);\n }\n }\n }\n if (problems.length > 0) throw new StoryletError(`bundle refused: ${problems.join(\"; \")}`);\n};\n\nexport class Engine {\n private readonly internals: Internals;\n private readonly seed: number;\n private readonly onReplacedFlow: EngineOptions[\"onReplacedFlow\"];\n private readonly flowsById = new Map<string, Flow>();\n private readonly engineTraceHandlers = new Set<EngineTraceHandler>();\n /** The options this engine was built with: hotSwap builds its replacement from them. */\n private readonly creationOptions: EngineOptions;\n /** How to register each shared scope again, in registration order: a failed\n * hotSwap puts this engine back exactly as it was. */\n private readonly sharedMounts: Array<{ key: string; mount: () => void }> = [];\n\n constructor(bundle: Bundle, opts: EngineOptions = {}) {\n // First, before anything is indexed or registered: a bundle this engine\n // cannot read faithfully is refused whole (design/project-map-contract.md\n // 3.8), and a refusal must leave the game's registry untouched.\n refuseUnreadableBundle(bundle);\n this.creationOptions = opts;\n this.seed = opts.seed ?? 0;\n this.onReplacedFlow = opts.onReplacedFlow;\n // Set-up problems found while building, reported as the constructor ends,\n // when the engine can report. A plain-JS caller gets no type check on its\n // options, so a mistyped or retired key would be ignored in silence.\n const setup: Array<{ node: string; message: string }> = [];\n for (const key of Object.keys(opts)) {\n if (ENGINE_OPTIONS.has(key)) continue;\n setup.push({ node: key, message: key === \"world\"\n ? `the engine option \"world\" has gone: bind the scope as hostScopes: { world }`\n : `\"${key}\" isn't an engine option, so it's ignored` });\n }\n const internals: Internals = {\n bundle,\n ...(opts.log ? { logCap: typeof opts.log === \"object\" ? (opts.log.cap ?? 1000) : 1000 } : {}),\n cardsById: new Map(), cardsByGameId: new Map(),\n boxesByGameId: new Map(), boxesById: new Map(),\n handsById: new Map(), handsByGameId: new Map(),\n owners: emptyOwnerIndexes(),\n templatesById: new Map(), groupsById: new Map(),\n requiredGroups: new Set(),\n nodeCache: new WeakMap(),\n ladders: { host: new Map(), story: new Map(), box: new Map(), deck: new Map(), value: new Map(), hand: new Map() },\n hasQualities: false,\n hasShared: false,\n flowDecls: { story: [], box: new Map(), deck: new Map(), hand: new Map(), value: new Map() },\n sharedDecls: { story: [], box: new Map(), deck: new Map(), hand: new Map(), value: new Map() },\n durable: { shared: [], flow: [], sharedCards: [], flowCards: [] },\n shared: undefined as unknown as Partition,\n registry: opts.registry ?? new ScopeRegistry(),\n onError: opts.onError ?? ((e) => console.warn(e.kind === \"setup\" ? `Storylet Engine: ${e.message}`\n : `Storylet Engine: the condition on ${e.node} in flow '${e.flow}' failed, counted as false: ${e.message}`)),\n ownsRegistry: opts.registry === undefined,\n reportRefits: () => {\n const misfits = this.internals.registry.takeMisfits((scope) => scope === \"story\" || scope.startsWith(\"storylets/\")\n || this.internals.hostScopes.get(scope)?.selfBacked === true);\n if (misfits.length === 0) return;\n if (opts.onRefit !== undefined) opts.onRefit(misfits);\n else for (const m of misfits) console.warn(`Storylet Engine: the save's ${m.scope}.${m.name} was ${JSON.stringify(m.value)}, which no longer fits its declaration; it is back at its default`);\n },\n registryView: () => view(),\n hostScopes: new Map(),\n hostResolvers: {},\n emitEngine: (flow, event, turn) => {\n if (this.internals.logCap !== undefined) {\n this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...(turn !== undefined ? { turn } : {}) });\n if (this.engineLog.length > this.internals.logCap) {\n this.engineLog.splice(0, this.engineLog.length - this.internals.logCap);\n }\n }\n // From a copy (ruling F): a handler that subscribes or unsubscribes\n // during an event changes who hears the NEXT one, never this one.\n for (const h of [...this.engineTraceHandlers]) h(flow, event);\n },\n engineTracing: () => this.engineTraceHandlers.size > 0,\n };\n this.internals = internals;\n let viewRevision = -1;\n let viewCache: ReturnType<Internals[\"registryView\"]> = { scopes: {}, qualities: undefined };\n const view = (): ReturnType<Internals[\"registryView\"]> => {\n const reg = internals.registry;\n if (reg.revision !== viewRevision) {\n const ctx = reg.toEvalContext();\n const scopes: EvalContext[\"scopes\"] = {};\n // An instance key (`storylets/deck/x`, another engine's) is no expression token.\n for (const [k, v] of Object.entries(ctx.scopes)) if (!k.includes(\"/\")) scopes[k] = v;\n viewCache = { scopes, qualities: ctx.qualities };\n viewRevision = reg.revision;\n }\n return viewCache;\n };\n\n // The value scope's segments come off the whole bundle at once (a tag\n // gameId is only unique within its group), so they are built before the\n // walk rather than tag by tag inside it.\n indexValueOwners(internals.owners.value, bundle);\n\n for (const box of bundle.boxes) {\n internals.boxesById.set(box.id, box);\n internals.boxesByGameId.set(effectiveGameId(box), box);\n indexOwner(internals.owners.box, box);\n for (const group of box.tagGroups) {\n internals.groupsById.set(group.id, { group, box });\n if (group.required === true) internals.requiredGroups.add(group.id);\n }\n for (const deck of box.decks) {\n indexOwner(internals.owners.deck, deck);\n if (deck.shared === true) internals.hasShared = true;\n for (const card of deck.cards) {\n const entry = { card, deck, box };\n internals.cardsById.set(card.id, entry);\n internals.cardsByGameId.set(effectiveGameId(card), entry);\n if (card.shared === true) internals.hasShared = true;\n if (cardIsDurable(card, deck)) {\n (cardIsShared(card, deck.shared ?? false) ? internals.durable.sharedCards : internals.durable.flowCards).push(entry);\n }\n }\n }\n for (const template of box.handTemplates) {\n internals.templatesById.set(template.id, template);\n }\n for (const hand of box.hands) {\n internals.handsById.set(hand.id, { hand, box });\n internals.handsByGameId.set(effectiveGameId(hand), { hand, box });\n indexOwner(internals.owners.hand, hand);\n }\n }\n // The project map's group: by id like any group, so a hand's binding, a\n // filled hole and tag matching need no logic of their own for it. Which\n // boxes may NAME it is `groupInBox`'s business (3.1); which may reference\n // it at all was settled by the refusal above.\n if (bundle.map !== undefined) {\n internals.groupsById.set(bundle.map.group.id, { group: bundle.map.group });\n if (bundle.map.group.required === true) internals.requiredGroups.add(bundle.map.group.id);\n }\n this.initLadders();\n\n // Both halves, precomputed once (a bundle's declarations never change):\n // each openFlow builds its bags from the per-flow half, and a load report\n // asks either half what it declares without building anything at all.\n const declSet = (half: (scope: FlaggedScope, decls: PropertyDecl[]) => PropertyDecl[]): DeclSet => ({\n story: half(\"story\", bundle.story.properties),\n box: new Map(bundle.boxes.map((box) => [box.id, half(\"box\", box.properties)])),\n deck: new Map(bundle.boxes.flatMap((box) => box.decks.map(\n (deck): [string, PropertyDecl[]] => [deck.id, half(\"deck\", deck.properties)]))),\n hand: new Map(bundle.boxes.flatMap((box) => box.hands.map(\n (hand): [string, PropertyDecl[]] => [hand.id, half(\"hand\", handDeclsOf(internals, hand))]))),\n value: new Map(allTagGroups(bundle).flatMap((group) => group.tags.map(\n (tag): [string, PropertyDecl[]] => [tag.id, half(\"value\", tag.properties ?? [])]))),\n });\n internals.flowDecls = declSet(flowHalf);\n internals.sharedDecls = declSet(sharedHalf);\n internals.durable.shared = durableProps(internals, internals.sharedDecls);\n internals.durable.flow = durableProps(internals, internals.flowDecls);\n\n this.initShared(opts.hostScopes ?? {}, setup);\n // The shared bags claimed values the game may have loaded first.\n internals.reportRefits();\n // Last, so the game's handler and the decision log hear about an engine\n // that is ready to be asked.\n for (const s of setup) this.reportSetup(s.node, s.message);\n }\n\n /** Report a set-up problem (a `setup` PlayError, which belongs to no flow):\n * to the game's onError, and to the decision log. */\n private reportSetup(node: string, message: string): void {\n this.internals.onError({ flow: \"\", kind: \"setup\", node, message });\n if (this.internals.logCap !== undefined || this.internals.engineTracing()) {\n this.internals.emitEngine(\"\", { type: \"diagnostic\", where: \"setup\", message });\n }\n }\n\n /** Build the shared stores, register them and the host scopes, and set up\n * each host scope's seam. Once, for the engine's life: reset and loads\n * reseed the bags in place, so the registry never sees them come and go. */\n private initShared(bound: Record<string, HostScope>, setup: Array<{ node: string; message: string }>): void {\n const internals = this.internals;\n const reg = internals.registry;\n const declared = bundleHostScopes(internals.bundle);\n internals.shared = buildPartition(internals, internals.sharedDecls);\n const registered: string[] = [];\n // Register now, and remember how, so a failed hotSwap can register again.\n const mount = (key: string, register: () => void): void => {\n register();\n registered.push(key);\n this.sharedMounts.push({ key, mount: register });\n };\n try {\n // `story` is this engine's token whether or not the bundle declares a shared\n // @story property: registering it is what makes a clash show at once.\n const story = internals.shared.story;\n mount(\"story\", () => { reg.mountOwned(\"story\", story, { owner: OWNER }); }); // claims values the game loaded first\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of internals.shared[kind]) {\n if (bag.declarations().length === 0) continue; // holds nothing: not registered\n mount(sharedKey(kind, id), () => { reg.mountOwned(sharedKey(kind, id), bag, { owner: OWNER }); });\n }\n }\n // Each scope the game binds is EXTERNAL: the game keeps the values, the\n // registry never saves them. Its declarations (types, read-only) come\n // from the bundle.\n for (const [token, resolver] of Object.entries(bound)) {\n if (resolver === undefined) continue;\n const spec = declared.find((s) => s.token === token);\n // Tokens are case-sensitive everywhere, so a near miss is reported,\n // never folded: folding would make a second spelling of the scope.\n // Another engine's scope the content names is that engine's to declare.\n if (spec === undefined && !(internals.bundle.externalScopes ?? []).includes(token)) {\n const near = declared.find((s) => s.token.toLowerCase() === token.toLowerCase());\n setup.push({ node: token, message: near\n ? `hostScopes binds \"${token}\", which this bundle doesn't declare: it declares \"${near.token}\", and a scope's token is case-sensitive`\n : `hostScopes binds \"${token}\", which this bundle doesn't declare` });\n }\n const decls = spec?.declarations?.map(toForeignDecl);\n mount(token, () => { reg.defineForeign(token, resolver, decls, { normalise: identity, writable: spec?.writable ?? true, owner: OWNER }); });\n }\n // A declared host scope nobody bound. A standalone engine is its own\n // game, so it self-backs the scope from its declared defaults,\n // DECLARATIONS AND ALL, as a property the registry stores and SAVES (only\n // a resolver the game binds is external). The bag keeps `writable: false`\n // so an examiner still reads it there, and the kernel lets a\n // `{ host: true }` write past it, which is what the game's own surface\n // passes. Given the GAME's registry, the engine registers nothing here:\n // those scopes are the game's to register.\n for (const spec of internals.ownsRegistry ? declared : []) {\n const token = spec.token;\n if (bound[token] !== undefined || reg.has(token)) continue;\n reg.defineOwned(token, selfBackedDecls(spec), { normalise: identity, pathPrefix: `${token}.`, owner: OWNER });\n const bag = reg.ownedBag(token);\n registered.push(token);\n this.sharedMounts.push({ key: token, mount: () => { reg.mountOwned(token, bag, { owner: OWNER }); } });\n }\n } catch (e) {\n for (const k of registered) reg.remove(k, { keep: true }); // a clash leaves the game's registry as it was\n throw e;\n }\n internals.hostScopes = new Map();\n internals.hostResolvers = {};\n for (const spec of declared) {\n const token = spec.token;\n const resolver = bound[token];\n const isBound = resolver !== undefined;\n const state: HostScopeState = {\n spec,\n resolver: {\n get: (n) => reg.get(token, n),\n set: (n, v) => { reg.set(token, n, v); },\n },\n // A bound resolver is opaque: it keeps whatever rule the game has, so\n // the story's `writable: false` is kept by `hostReadOnly` before this seam.\n ...(isBound\n ? resolver!.set !== undefined ? { set: (n: string, v: ScalarValue): void => { reg.set(token, n, v, { host: true }); } } : {}\n : { set: (n: string, v: ScalarValue, host?: boolean): void => { reg.set(token, n, v, host === true ? { host: true } : undefined); } }),\n selfBacked: !isBound && this.sharedMounts.some((m) => m.key === token),\n };\n internals.hostScopes.set(token, state);\n internals.hostResolvers[token] = state.resolver;\n }\n }\n\n /** Every shared bag back to its declared defaults, in place (the registry\n * keeps them registered), the self-backed host scopes included. */\n private reseedShared(): void {\n const { shared, sharedDecls, registry } = this.internals;\n shared.story.reseed(sharedDecls.story as unknown as ScopeDeclaration[]);\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of shared[kind]) bag.reseed((sharedDecls[kind].get(id) ?? []) as unknown as ScopeDeclaration[]);\n }\n for (const host of this.internals.hostScopes.values()) {\n if (host.selfBacked) registry.reseedOwned(host.spec.token, selfBackedDecls(host.spec));\n }\n }\n\n /** Quality ladders by scope for the eval channel (design/quality.md):\n * host scopes by token then name; story by name; box/deck/value keyed by\n * owner id then name.\n * Built once - a bundle's declarations never change. Ladders are\n * declaration-level, so the sharing flag does not touch them. */\n private initLadders(): void {\n const internals = this.internals;\n const grab = (decls: readonly (PropertyDecl | HostScopeDecl)[] | undefined): Map<string, readonly string[]> => {\n const m = new Map<string, readonly string[]>();\n for (const d of decls ?? []) if (d.type === \"quality\" && d.stages !== undefined) m.set(d.name, d.stages);\n return m;\n };\n const b = internals.bundle;\n for (const spec of bundleHostScopes(b)) internals.ladders.host.set(spec.token, grab(spec.declarations));\n internals.ladders.story = grab(b.story.properties);\n for (const box of b.boxes) {\n internals.ladders.box.set(box.id, grab(box.properties));\n for (const deck of box.decks) internals.ladders.deck.set(deck.id, grab(deck.properties));\n for (const group of box.tagGroups) {\n for (const tag of group.tags) internals.ladders.value.set(tag.id, grab(tag.properties));\n }\n for (const hand of box.hands) internals.ladders.hand.set(hand.id, grab(handDeclsOf(internals, hand)));\n }\n // The zones, once, keyed by tag id like every other tag (3.3).\n for (const tag of b.map?.group.tags ?? []) internals.ladders.value.set(tag.id, grab(tag.properties));\n const any = (m: Map<string, Map<string, readonly string[]>>): boolean =>\n [...m.values()].some((x) => x.size > 0);\n internals.hasQualities = [...internals.ladders.host.values()].some((x) => x.size > 0) || internals.ladders.story.size > 0\n || any(internals.ladders.box) || any(internals.ladders.deck)\n || any(internals.ladders.value) || any(internals.ladders.hand);\n }\n\n /** The registry this engine's bags live in: the game's, the very object the\n * `registry` option passed in, or the one the engine made because it was\n * given none. The same object for the engine's life. Read-only: a game\n * hands an engine its registry at construction and never swaps it. */\n get registry(): ScopeRegistry {\n return this.internals.registry;\n }\n\n // --- flow management (Patter's surface, name for name) ----------------------\n\n /** Open (or REPLACE) the named flow. An existing id's flow is closed\n * first - re-opening a name is a reset of that name's whole per-flow\n * state; shared state is untouched. There is no default flow: \"main\" is\n * a caller convention, not an engine rule. */\n openFlow(id: string, opts: OpenFlowOptions = {}): Flow {\n return this.open(id, opts, false);\n }\n\n /** openFlow, and loadGame's rebuild. `claim` says the new flow's bags take\n * the values the registry holds for them (a load); a fresh open is a reset\n * of that name, so anything waiting for it is discarded first. */\n private open(id: string, opts: OpenFlowOptions, claim: boolean): Flow {\n if (opts.durable !== undefined && opts.restore !== undefined) {\n throw new StoryletError(`openFlow \"${id}\": restore and durable cannot be given together; a restore already carries the flow's durable state`);\n }\n // A pocket is checked and planned before the name is touched: a refusal,\n // and anything the plan finds, must leave the flow already open as it was.\n let durable: { plan: DurablePlan; draft: ReportDraft; content: BundleContent } | undefined;\n if (opts.durable !== undefined) {\n assertDurable(this.internals, opts.durable);\n const draft = emptyDraft();\n const { flow: props, flowCards } = this.internals.durable;\n durable = { plan: planDurable(this.internals, props, flowCards, opts.durable, id, draft), draft, content: opts.durable.content };\n }\n this.assertExternalScopes();\n // The world's claims as they stand WITHOUT this name, taken before the\n // replace: a resume competes with the other flows, never with the flow it\n // is replacing (which is about to release everything it holds).\n const otherClaims = opts.restore !== undefined ? this.sharedClaims(id) : undefined;\n // Replacing an existing id KEEPS its place in the order. `close()` would\n // drop the key, and a JS Map re-inserts a deleted key at the END, so\n // openFlow(\"a\"); openFlow(\"b\"); openFlow(\"a\") listed [b, a] here and\n // [a, b] on all three ports - a different `flows()` order and, since\n // `saveGame` keys its flows in that order, a different `.storyletsave`\n // byte stream for the same run (2026-08-29). markClosed without dropFlow\n // is the difference: the old handle goes inert, the slot stays put. The\n // registry cannot keep a key's place the same way (the old flow's keys are\n // removed and the new flow's appended), so `saveGame` writes the registry\n // section in flow order itself: see `registrySection` (2026-10-01).\n const existing = this.flowsById.get(id);\n if (existing) {\n // Say so BEFORE the old flow goes inert, while its board is still readable.\n const dealt = existing.heldCardIds().length;\n if (dealt > 0) this.onReplacedFlow?.(id, dealt);\n existing.markClosed();\n }\n if (!claim) this.internals.registry.discardParked(flowPrefix(id));\n const flow = new Flow(this, this.internals, id, opts.seed ?? this.seed);\n this.flowsById.set(id, flow);\n if (opts.restore !== undefined) {\n const draft = emptyDraft();\n // Cloned: a caller holding one blob may resume two flows from it, and\n // an aliased playLog would then grow in both.\n const clean = this.planFlowRestore(id, structuredClone(opts.restore), otherClaims, draft);\n flow.restore(clean);\n const content = this.internals.bundle.content;\n opts.onRestoreReport?.(finishReport(content, content, [id], draft));\n }\n if (durable !== undefined) {\n flow.writeDurable(durable.plan);\n opts.onRestoreReport?.(finishReport(this.internals.bundle.content, durable.content, [id], durable.draft));\n }\n return flow;\n }\n\n getFlow(id: string): Flow | undefined {\n return this.flowsById.get(id);\n }\n\n /** Every live flow, open order. */\n flows(): Flow[] {\n return [...this.flowsById.values()];\n }\n\n /** Close the named flow: its handle goes INERT (every verb throws). A\n * dropped-but-held flow must not keep writing shared state (Patter's\n * stale-handle lesson). Unknown ids are a quiet no-op, like closing a\n * closed door. */\n closeFlow(id: string): void {\n const flow = this.flowsById.get(id);\n if (!flow) return;\n this.flowsById.delete(id);\n flow.markClosed();\n }\n\n /** @internal - Flow.close() routes here so both doors agree. */\n dropFlow(id: string, flow: Flow): void {\n if (this.flowsById.get(id) === flow) this.flowsById.delete(id);\n }\n\n /** Close every flow and reseed the shared state to its defaults (the\n * self-backed host scopes included; a host-bound one is the host's and is\n * not touched). */\n reset(): void {\n this.dropRun(false);\n this.reseedShared();\n // Values loaded for bags nobody has claimed yet are the old run's too: a\n // flow opened after the reset must not pick them up. Other engines' stay.\n this.internals.registry.discardParked(\"storylets/\");\n }\n\n /** End the run: clear the log, close every flow, forget spent cards. Each\n * flow's bags leave the registry; `keepFlows` names the flows whose values\n * are kept there for the flow that replaces them (a load into the game's\n * registry). */\n private dropRun(keepFlows: Set<string> | false): void {\n // The log is a run-lifetime utility and is not saved; a reset is a new run.\n // All three ports cleared it here and the reference did not, so `reset()`\n // (and `loadGame`) left the previous run's entries in place with `seq`\n // continuing across the boundary - while the same call on Godot, Unity or\n // Unreal returned an empty log (2026-08-29).\n this.engineLog = [];\n for (const [id, flow] of this.flowsById) {\n flow.releaseBags(keepFlows !== false && keepFlows.has(id));\n flow.markClosed();\n }\n this.flowsById.clear();\n this.spent.clear();\n }\n\n // --- shared scarcity (design/shared-scarcity.md) -----------------------------\n\n /** Cards a shared `redraw: \"never\"` has taken out of the world, by card id.\n * The claim ledger is DERIVED from live boards and so needs no storage;\n * this one is durable, so it rides the save's shared half. */\n private spent = new Set<string>();\n\n /** @internal */\n isTaken(cardId: string): boolean {\n return this.spent.has(cardId);\n }\n\n /** @internal - take a shared one-shot out of the world, as its play does.\n * Keyed by internal id. A host carries spends across a run with\n * `saveDurable` and `loadDurable`, which speak gameIds and check that the\n * card is still one that may be carried (ruling H). */\n markTaken(cardId: string): void {\n this.spent.add(cardId);\n }\n\n // --- the run's log (design/shared-scarcity.md 8.2) ---------------------------\n\n /** Every flow's events in one ordered stream, each tagged with its flow.\n * Opt in with the same `log` option the flow logs use; capped the same way.\n *\n * This exists because a flow's own log cannot answer the question a run\n * raises: when a story action in ANOTHER flow moves shared state, your\n * flow's log says nothing and your value simply changes. Reading a run\n * needs one stream that says who did what, and merging the per-flow logs by\n * hand is a thing every host would otherwise have to write. */\n private engineLog: EngineLogEntry[] = [];\n private engineSeq = 0;\n\n log(): readonly EngineLogEntry[] {\n return this.engineLog;\n }\n\n clearLog(): void {\n this.engineLog = [];\n }\n\n /** @internal - shared claims across every LIVE flow, card id -> holders.\n * Derived, which is what makes closeFlow and the openFlow replace release\n * what a flow was holding: its board leaves the map with it. `except`\n * leaves one flow out: what the REST of the world holds, which is the\n * question a resume under that name has to ask. */\n sharedClaims(except?: string): Map<string, number> {\n const counts = new Map<string, number>();\n for (const [flowId, flow] of this.flowsById) {\n if (flowId === except) continue;\n for (const cardId of flow.heldCardIds()) counts.set(cardId, (counts.get(cardId) ?? 0) + 1);\n }\n return counts;\n }\n\n // --- engine-level state access ----------------------------------------------\n\n /**\n * Read shared state by path: \"world.x\", \"story.gold\" (when shared),\n * \"box.village.heat\" (when shared) - the owner segment is its GAMEID\n * (design/engine-server.md 4.4). A ref that resolves PER-FLOW throws,\n * naming the fix - silently answering with some flow's copy (or a junk\n * default) was the bug Patter's engine.getProperty guard exists to stop.\n */\n getProperty(path: string): ScalarValue {\n const found = this.resolveShared(propertyPath(path));\n const value = found.kind === \"host\" ? found.host.resolver.get(found.name)\n : found.kind === \"scope\" ? this.internals.registry.get(found.token, found.name)\n : found.bag.get(found.name);\n if (value === undefined) throw new StoryletError(`no property at \"${path}\"`);\n return value;\n }\n\n setProperty(path: string, value: ScalarValue): void {\n const found = this.resolveShared(propertyPath(path));\n checkHostWrite(this.internals, path, value);\n if (found.kind === \"host\") {\n if (!found.host.set) {\n throw new StoryletError(`@${found.host.spec.token} is read-only here: the host bound no write`);\n }\n found.host.set(found.name, value, true);\n return;\n }\n if (found.kind === \"scope\") {\n this.internals.registry.set(found.token, found.name, value, { host: true });\n return;\n }\n // A host write: silent under the firing rule (no subscriber feedback\n // loop), but visible to the bag's audit hook - and a HOST write, so a\n // `writable: false` does not refuse it. That flag is the story's promise\n // about its own outcomes; this is the game speaking, in any scope.\n found.bag.set(found.name, value, { silent: true, reason: \"host setProperty\", host: true });\n }\n\n private resolveShared(path: string):\n | { kind: \"host\"; host: HostScopeState; name: string }\n | { kind: \"scope\"; token: string; name: string }\n | { kind: \"bag\"; bag: StateBag; name: string } {\n const parts = path.split(\".\");\n const perFlow = (): never => {\n throw new StoryletError(`\"${path}\" is per-flow state - read it on a Flow, not the Engine`);\n };\n const host = parts.length === 2 ? this.internals.hostScopes.get(parts[0]!) : undefined;\n if (host !== undefined) return { kind: \"host\", host, name: parts[1]! };\n // Another engine's game-wide scope (`patter.gold`): every engine reads every scope.\n if (parts.length === 2 && parts[0] !== \"story\" && this.internals.registry.has(parts[0]!)) {\n return { kind: \"scope\", token: parts[0]!, name: parts[1]! };\n }\n if (parts.length === 2 && parts[0] === \"story\") {\n const name = parts[1]!;\n if (ownValue(this.internals.shared.story, name) !== undefined) return { kind: \"bag\", bag: this.internals.shared.story, name };\n if (this.internals.flowDecls.story.some((d) => d.name === name)) perFlow();\n throw new StoryletError(`no property at \"${path}\"`);\n }\n if (parts.length === 3 && isOwnedScope(parts[0])) {\n const kind = parts[0];\n const [, segment, name] = parts as unknown as [string, string, string];\n const owner = ownerOrThrow(this.internals, kind, segment, name);\n if (owner.legacy) this.diagnose(legacyAddressMessage(this.internals, kind, segment, name));\n const id = owner.id;\n const bag = this.internals.shared[kind].get(id);\n if (bag !== undefined && ownValue(bag, name) !== undefined) return { kind: \"bag\", bag, name };\n if (this.internals.flowDecls[kind].get(id)?.some((d) => d.name === name)) perFlow();\n if (bag === undefined && !this.internals.flowDecls[kind].has(id)) throw new StoryletError(`no ${kind} store \"${segment}\"`);\n throw new StoryletError(`no property at \"${path}\"`);\n }\n throw new StoryletError(`bad property path \"${path}\"`);\n }\n\n /** The engine's own surface has no flow, so an engine-level diagnostic\n * carries the empty flow id - the same way a LoadReport's shared half\n * carries no flow. It reaches the run log and the engine tap; there is\n * nowhere else for it to go, and it fires only on a legacy address. */\n private diagnose(message: string): void {\n this.internals.emitEngine(\"\", { type: \"diagnostic\", where: \"property address\", message });\n }\n\n /** Content that names another engine's scope (`@patter.visits`) runs only\n * where that engine is on this registry: without it every read would answer\n * false and every write fail, so the flow is refused as it opens, before\n * anything changes. By then a game has built all of its engines, whatever\n * order it built them in. The same message on every runtime. */\n private assertExternalScopes(): void {\n for (const token of this.internals.bundle.externalScopes ?? []) {\n if (!this.internals.registry.has(token)) {\n throw new StoryletError(`this content names @${token}, which no engine on this registry registered: `\n + `give every engine the game's one registry`);\n }\n }\n }\n\n /** The shared surface as examiner rows: the host scopes (read through\n * their resolvers) then the shared partitions. Per-flow rows live on each Flow. */\n listProperties(): PropertyRow[] {\n const out: PropertyRow[] = hostRows(this.internals);\n // The bag composes its rows' addresses from its own pathPrefix, so the prefix here is\n // only the caller's label for the mount; the row arrives already addressed.\n const add = (_prefix: string, bag: StateBag): void => {\n for (const row of bag.rows()) out.push(row);\n };\n add(\"story\", this.internals.shared.story);\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of this.internals.shared[kind]) add(addressOf(this.internals, kind, id), bag);\n }\n return out;\n }\n\n /** The SHARED kernel bags with their store path prefixes (the state\n * logger's mount surface). The @world container is the host's own bag -\n * the host mounts it itself. */\n listBags(): BagMount[] {\n const mounts: BagMount[] = [{ prefix: \"story\", bag: this.internals.shared.story }];\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of this.internals.shared[kind]) mounts.push({ prefix: addressOf(this.internals, kind, id), bag });\n }\n return mounts;\n }\n\n /** Every flow's trace, one stream, each event tagged with its flow id. */\n subscribeTrace(handler: EngineTraceHandler): () => void {\n // A Set: the same handler subscribed twice is registered once (ruling F).\n this.engineTraceHandlers.add(handler);\n return () => { this.engineTraceHandlers.delete(handler); };\n }\n\n // --- persistence (schema 4) -------------------------------------------------\n\n /**\n * Live bundle refresh: rebuild on an edited bundle with the whole run carried\n * over, and return the replacement with the report its load produced.\n *\n * Standalone, that is a save and a load into a new engine, and this one is left\n * untouched (discard it). With the game's registry the two cannot both hold the\n * same keys, so this engine is spent afterwards (its flows closed, the\n * replacement holding everything on the same registry): it carries its\n * own values into the snapshot, steps out of the registry, and the replacement\n * loads them the way a standalone save loads: so the report covers the\n * properties the edit dropped, defaulted, or retyped, and a dropped property\n * is dropped rather than kept. Values the game loaded that were still waiting\n * for a flow of this engine carry across as they were, and nothing belonging\n * to any other engine is touched. A save for another project is refused before\n * anything moves; if the rebuild fails for any other reason, this engine takes\n * its registrations back and is left exactly as it was.\n */\n hotSwap(bundle: Bundle, opts: EngineOptions = {}): { engine: Engine; report: LoadReport } {\n if (bundle.content.project !== this.internals.bundle.content.project) {\n // Names the BUNDLE: there is no save in a hot swap, so the load's own\n // \"save is for project\" wording pointed the reader at the wrong thing.\n throw new StoryletError(`hotSwap: the bundle is for project \"${bundle.content.project}\", this engine runs \"${this.internals.bundle.content.project}\"`);\n }\n const options: EngineOptions = { ...this.creationOptions, ...opts };\n const snapshot = this.saveGame();\n const reg = this.internals.registry;\n if (this.internals.ownsRegistry) {\n // Its own registry: a save and a load into a new engine, as it always was,\n // and this engine is left untouched.\n const next = new Engine(bundle, { ...options, registry: undefined });\n return { engine: next, report: next.loadGame(snapshot) };\n }\n // This engine's own values, registered or still waiting for a flow.\n const mine: Record<string, Record<string, ScalarValue>> = {};\n for (const [key, values] of Object.entries(reg.save())) {\n if (key === \"story\" || key.startsWith(\"storylets/\")) mine[key] = values;\n }\n const registeredKeys = new Set([\n ...this.sharedMounts.map((m) => m.key),\n ...[...this.flowsById.values()].flatMap((f) => f.registeredKeys()),\n ]);\n const waiting = Object.fromEntries(Object.entries(mine).filter(([key]) => !registeredKeys.has(key)));\n // Step out of the registry. The bags keep their values, so stepping back in is exact.\n for (const { key } of this.sharedMounts) if (reg.has(key)) reg.remove(key);\n for (const flow of this.flowsById.values()) flow.releaseBags(false);\n let next: Engine | undefined;\n try {\n next = new Engine(bundle, { ...options, registry: reg });\n const report = next.loadGame({ ...snapshot, registry: mine });\n // Values that were waiting for a flow wait on, for the replacement's flow of that name.\n const stillWaiting = Object.fromEntries(Object.entries(waiting).filter(([key]) => !reg.has(key)));\n if (Object.keys(stillWaiting).length > 0) reg.load(stillWaiting, { keepParked: true });\n this.dropRun(false);\n return { engine: next, report };\n } catch (e) {\n // Back as it was. The replacement's registrations go; a constructor that\n // failed part way parked what it had claimed, so this engine's keys are\n // cleared of anything waiting, registered again, and its own values laid\n // back over them (the waiting ones parked again).\n next?.dropRun(false);\n for (const { key } of next?.sharedMounts ?? []) if (reg.has(key)) reg.remove(key);\n reg.discardParked(\"storylets/\");\n for (const { mount } of this.sharedMounts) mount();\n for (const flow of this.flowsById.values()) flow.mountBags();\n // `story` may have claimed what the failed replacement parked there: back to\n // its declarations, then this engine's own values over it.\n this.reseedShared();\n reg.load(mine, { keepParked: true });\n throw e;\n }\n }\n\n /** The whole engine's NON-property state, one envelope: the spent cards\n * once, then every live flow (board, clocks, cooldowns, PRNG, play log)\n * keyed by its id. The property values are the registry's: a standalone\n * engine (one that made its own registry) carries them here under\n * `registry`, self-backed host scopes included; a game that passed a registry\n * saves it once itself, beside each engine's envelope. */\n saveGame(): SaveEnvelope {\n return structuredClone({\n schema: SAVE_SCHEMA,\n content: this.internals.bundle.content,\n ...(this.internals.ownsRegistry ? { registry: this.registrySection() } : {}),\n shared: { spent: [...this.spent].map((id) => cardKey(this.internals, id)).sort() },\n flows: Object.fromEntries([...this.flowsById].map(([id, flow]) => [id, flow.snapshot(false)])),\n });\n }\n\n /** The registry's values in CANONICAL order, the order a load rebuilds them\n * in: the engine-wide keys as the constructor registered them, then each\n * flow's keys in the order the save's `flows` lists the flows (each flow's\n * own registration order), then\n * anything else the registry holds (values still waiting for a key), as the\n * registry lists it. The registry itself lists keys in registration order,\n * and a flow replaced in place (`open()` above keeps its slot in\n * `flowsById`) re-registers its keys at the END, so `openFlow(\"a\");\n * openFlow(\"b\"); openFlow(\"a\")` saved b's keys before a's while a load\n * rebuilt a's first: the same run, different `.storyletsave` bytes, and a\n * save loaded and saved again no longer equal to itself. It is the\n * 2026-08-29 rule carried into the section save@2 moved the per-flow values\n * to (2026-10-01). Order does not matter on READ (`partitionsFromSections`\n * sorts by key shape), so a save written in the old order loads as before.\n *\n * \"The order the save lists them\" is JS OBJECT order (ruling E,\n * 2026-10-06), which is not always `flows()` order: a flow id that looks like\n * an integer (\"7\") comes first, ascending, in any JS object, and a load\n * reopens the flows in the order the parsed save hands them over. Without\n * this, `openFlow(\"main\"); openFlow(\"7\")` saved main's keys first and the\n * loaded engine saved 7's first. */\n private registrySection(): Sections {\n const all = this.internals.registry.save() as Sections;\n const out: Sections = {};\n const take = (key: string): void => {\n if (Object.prototype.hasOwnProperty.call(all, key) && !Object.prototype.hasOwnProperty.call(out, key)) out[key] = all[key]!;\n };\n for (const { key } of this.sharedMounts) take(key);\n for (const flow of Object.values(Object.fromEntries(this.flowsById))) for (const key of flow.registeredKeys()) take(key);\n for (const key of Object.keys(all)) take(key);\n return out;\n }\n\n /** ONE flow's blob, to park a visit that is walking away: the same shape\n * the envelope carries per flow, and the same shape `openFlow`'s `restore`\n * option takes back (design/engine-server.md 4.1). Saving the whole\n * envelope to park one of four hundred players is wrong in cost and in\n * meaning. Throws for a name that is not open - a closed flow has nothing\n * left to save. */\n saveFlow(id: string): FlowSave {\n const flow = this.flowsById.get(id);\n if (!flow) throw new StoryletError(`unknown flow \"${id}\"`);\n // Parked whole, properties included: a parked flow's bags leave the\n // registry when it closes, so its values have to travel with it.\n return structuredClone(flow.snapshot(true));\n }\n\n /**\n * The engine's DURABLE HALF, the installation's memory (ruling H): every\n * shared `durable` property's value by its address, and every shared\n * durable one-shot that has been spent, by card gameId. What a new run\n * starts from, through `loadDurable`; a flow's own half is\n * `flow.saveDurable()`.\n */\n saveDurable(): DurableSave {\n const { bundle, shared, durable } = this.internals;\n return structuredClone({\n schema: DURABLE_SCHEMA,\n content: bundle.content,\n values: durableValues(shared, durable.shared),\n spent: durable.sharedCards.filter((e) => this.spent.has(e.card.id)).map((e) => effectiveGameId(e.card)).sort(),\n });\n }\n\n /**\n * Write the engine's durable half into this engine, a fresh one at the top\n * of a new run (ruling H). Every shared durable property takes the memory's\n * value, or its default where the memory carries none that fits, and every\n * spend the memory lists is taken out of the world. Nothing else is touched:\n * not a run-scoped value, not a flow, not a spend the memory does not name.\n *\n * Returns the report `loadGame` would give, for this half: an address this\n * build does not declare durable and shared is dropped, a value its\n * declaration no longer takes is retyped, a durable declaration the memory\n * lacks is defaulted, and a spend for a card that is no longer a shared\n * durable one-shot is a dropped spent card. Another project's memory, or an\n * unknown schema, is refused before anything moves.\n */\n loadDurable(memory: DurableSave): LoadReport {\n const internals = this.internals;\n assertDurable(internals, memory);\n const draft = emptyDraft();\n const plan = planDurable(internals, internals.durable.shared, internals.durable.sharedCards, memory, undefined, draft);\n for (const prop of internals.durable.shared) {\n const value = plan.values.has(prop)\n ? plan.values.get(prop)!\n : defaultFor(prop.decl as unknown as ScopeDeclaration);\n putDurable(durableBag(internals.shared, prop), prop.decl.name, value);\n }\n for (const cardId of plan.cards) this.spent.add(cardId);\n return finishReport(internals.bundle.content, memory.content, [], draft);\n }\n\n /** What `loadGame(envelope)` would do that is not a plain restore, without\n * doing any of it (design/engine-server.md 4.9). Pure: nothing on this\n * engine moves. A project mismatch is refused here exactly as `loadGame`\n * refuses it - it is the one thing neither call will tolerate. */\n previewLoad(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport {\n refuseUnreadableSave(envelope);\n this.assertSameProject(envelope);\n const plan = this.planLoad(envelope);\n if (plan.sections === undefined) return plan.report;\n // The held cards are checked against the values the envelope carries,\n // which only a load puts in place: so the load runs on a scratch engine of\n // its own, with its own registry, and this one is left as it was.\n const { seed, hostScopes } = this.creationOptions;\n const scratch = new Engine(this.internals.bundle, {\n ...(seed !== undefined ? { seed } : {}), ...(hostScopes !== undefined ? { hostScopes } : {}), onError: () => {},\n });\n try {\n return scratch.loadGame(envelope);\n } catch {\n return plan.report;\n }\n }\n\n /** What `openFlow(id, { restore: saved })` would do to a flow of that name,\n * without doing it: the same report shape, since a visit parked under one\n * build and resumed under the next raises the same questions. Pure. */\n previewFlowRestore(id: string, saved: FlowSave): LoadReport {\n const draft = emptyDraft();\n this.planFlowRestore(id, saved, this.sharedClaims(id), draft);\n const content = this.internals.bundle.content;\n return finishReport(content, content, [id], draft);\n }\n\n /** Restore: shared state once, then every flow REBUILT from its blob.\n * Handles held from before the load are closed and inert (Patter's\n * rule); take fresh ones from getFlow()/flows().\n *\n * Property values come from the registry. An envelope that carries them\n * (a standalone engine's, or a version 1 envelope) has them walked, cleaned,\n * and moved into the registry here, over fresh defaults. Otherwise the game\n * loads its registry itself, before or after this call: each flow's bags\n * are handed back to the registry with their values, and the restored\n * flows claim them. The report then covers only what this envelope holds;\n * the registry's own load rule applies to the values.\n *\n * Returns the report `previewLoad` would have given for this envelope: the\n * drift tolerance that makes a load forgiving is what hides its cost, so\n * the cost comes back with the load whether or not anybody looked first. */\n loadGame(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport {\n try {\n return this.loadGameInto(envelope);\n } finally {\n // The game's own refitting load of its registry lands before this one.\n this.internals.reportRefits();\n }\n }\n\n private loadGameInto(envelope: SaveEnvelope | SaveEnvelopeV1): LoadReport {\n refuseUnreadableSave(envelope);\n this.assertSameProject(envelope);\n this.assertExternalScopes();\n const plan = this.planLoad(structuredClone(envelope));\n const reg = this.internals.registry;\n if (plan.sections !== undefined) {\n // The envelope carries the values: every bag of this engine back to its\n // defaults, then the cleaned values over them.\n this.reset();\n // The engine's own registry takes the save wholesale. A game's registry may\n // hold values the game loaded for other engines, still waiting: add to\n // those, never replace them.\n if (this.internals.ownsRegistry) reg.load(plan.sections);\n else reg.load(plan.sections, { keepParked: true });\n } else {\n this.dropRun(new Set(plan.flows.map(([id]) => id)));\n }\n for (const id of plan.spent) this.spent.add(id);\n for (const [id, clean] of plan.flows) this.open(id, {}, true).restore(clean);\n if (plan.sections === undefined) return plan.report;\n // With the values in place, every held card is checked as a deal checks\n // it, and what no longer qualifies goes, in the report (the hands-on\n // review, 2026-10, foundation 7). A game that loads its own registry has\n // no values here yet: its next deal does the same.\n const dropped = plan.flows.flatMap(([id]) =>\n (this.flowsById.get(id)?.dropUnqualified() ?? []).map((e) => ({ flow: id, ...e })));\n if (dropped.length === 0) return plan.report;\n return {\n ...plan.report,\n exact: false,\n evicted: byKey([...plan.report.evicted, ...dropped], (e) => [e.flow, e.hand, e.card, e.reason].join(SORT_SEP)),\n };\n }\n\n private assertSameProject(envelope: SaveEnvelope | SaveEnvelopeV1): void {\n if (envelope.content.project !== this.internals.bundle.content.project) {\n throw new StoryletError(`save is for project \"${envelope.content.project}\", bundle is \"${this.internals.bundle.content.project}\"`);\n }\n }\n\n /** The whole-envelope walk: the report, and the cleaned state the apply\n * half writes. Nothing here touches the engine, which is what lets\n * previewLoad and loadGame share it. */\n private planLoad(envelope: SaveEnvelope | SaveEnvelopeV1): {\n report: LoadReport;\n /** The cleaned property values to move into the registry, when the\n * envelope carries any. */\n sections?: Sections;\n spent: string[];\n flows: [string, FlowSave][];\n } {\n const schema = (envelope as { schema?: unknown }).schema;\n if (schema !== SAVE_SCHEMA && schema !== SAVE_SCHEMA_V2 && schema !== SAVE_SCHEMA_V1) throw new StoryletError(`unsupported save schema: ${String(schema)}`);\n const draft = emptyDraft();\n // Where the property values are, if this envelope has them: a version 1\n // envelope's partitions, or a standalone engine's registry sections.\n const flowIds = new Set(Object.keys(envelope.flows ?? {}));\n let moved: { shared: PropsPartition; flows: Map<string, PropsPartition>; rest: Sections } | undefined;\n if (envelope.schema === SAVE_SCHEMA_V1) {\n moved = {\n shared: envelope.shared?.props ?? emptyPartitionValues(),\n flows: new Map(Object.entries(envelope.flows ?? {}).map(([id, f]) => [id, f.props ?? emptyPartitionValues()])),\n rest: {},\n };\n } else if (envelope.registry !== undefined) {\n moved = partitionsFromSections(envelope.registry, flowIds);\n }\n const shared = moved !== undefined\n ? walkPartition(this.internals, this.internals.sharedDecls, moved.shared, undefined, draft)\n : undefined;\n const spent: string[] = [];\n for (const key of envelope.shared?.spent ?? []) {\n const entry = savedCard(this.internals, key);\n if (entry !== undefined) spent.push(entry.card.id);\n else draft.droppedSpent.push(key);\n }\n const flows: [string, FlowSave][] = [];\n const sections: Sections | undefined = moved !== undefined ? { ...moved.rest } : undefined;\n if (sections !== undefined && shared !== undefined) {\n sectionsOf(shared, (kind, id) => (kind === \"story\" ? \"story\" : sharedKey(kind, id!)), sections);\n }\n for (const [id, saved] of Object.entries(envelope.flows ?? {})) {\n const withProps: FlowSave = moved !== undefined\n ? { ...saved, props: moved.flows.get(id) ?? emptyPartitionValues() }\n : { ...saved };\n const clean = this.planFlowRestore(id, withProps, undefined, draft);\n if (sections !== undefined && clean.props !== undefined) {\n // The flow's values go into the registry, where its bags claim them; the\n // restored flow itself carries none.\n sectionsOf(clean.props, (kind, owner) => flowKey(id, kind, owner), sections);\n delete clean.props;\n }\n flows.push([id, clean]);\n }\n return {\n report: finishReport(this.internals.bundle.content, envelope.content, flows.map(([id]) => id), draft),\n ...(sections !== undefined ? { sections } : {}),\n spent, flows,\n };\n }\n\n /** One flow's walk. `otherClaims` is the rest of the world's shared ledger\n * and is present only for a SINGLE-flow restore into a live engine: a\n * whole-envelope load rebuilds every flow from one consistent moment, so\n * there is nobody else to compete with. */\n private planFlowRestore(\n id: string,\n saved: FlowSave,\n otherClaims: Map<string, number> | undefined,\n draft: ReportDraft,\n ): FlowSave {\n const internals = this.internals;\n // A flow blob without properties (a version 2 envelope's: the registry has\n // them) has nothing to walk.\n const props = saved.props !== undefined ? walkPartition(internals, internals.flowDecls, saved.props, id, draft) : undefined;\n\n // Every key the save carries resolves to the build's internal id (see\n // savedCard); one the build no longer has is named by the key itself.\n const cooldowns: Record<string, number> = {};\n for (const [key, turn] of Object.entries(saved.cooldowns ?? {})) {\n const entry = savedCard(internals, key);\n if (entry !== undefined) cooldowns[entry.card.id] = turn;\n else draft.droppedCooldowns.push({ flow: id, card: key });\n }\n\n // A deleted entity has no gameId left, so it is named by the key the save\n // carries; everything the build still knows is named by its gameId.\n const cardName = (key: string): string => {\n const entry = savedCard(internals, key);\n return entry ? effectiveGameId(entry.card) : key;\n };\n const board: Record<string, string[]> = {};\n const restored = new Map<string, number>();\n for (const [handKey, keys] of Object.entries(saved.board ?? {})) {\n const known = savedHand(internals, handKey);\n if (known === undefined) {\n for (const key of keys) {\n draft.evicted.push({ flow: id, hand: handKey, card: cardName(key), reason: \"hand-vanished\" });\n }\n continue;\n }\n const hand = effectiveGameId(known.hand);\n const kept: string[] = [];\n for (const key of keys) {\n const entry = savedCard(internals, key);\n if (entry === undefined) {\n draft.evicted.push({ flow: id, hand, card: key, reason: \"vanished\" });\n continue;\n }\n const cardId = entry.card.id;\n if (otherClaims !== undefined && cardIsShared(entry.card, entry.deck.shared ?? false)) {\n const held = (otherClaims.get(cardId) ?? 0) + (restored.get(cardId) ?? 0);\n if (held >= sharedCap(entry.card)) {\n draft.evicted.push({ flow: id, hand, card: effectiveGameId(entry.card), reason: \"claimed-elsewhere\" });\n continue;\n }\n restored.set(cardId, (restored.get(cardId) ?? 0) + 1);\n }\n kept.push(cardId);\n }\n board[known.hand.id] = kept;\n }\n\n const turns: Record<string, number> = {};\n for (const [key, turn] of Object.entries(saved.turns ?? {})) {\n const box = savedBox(internals, key);\n if (box !== undefined) turns[box.id] = turn;\n }\n\n return {\n ...(props !== undefined ? { props } : {}),\n turns,\n prng: saved.prng,\n cooldowns,\n board,\n playLog: saved.playLog ?? [],\n };\n }\n}\n\n// --- the Flow -----------------------------------------------------------------\n\nexport class Flow {\n readonly id: string;\n private readonly engine: Engine;\n private readonly internals: Internals;\n private closed = false;\n\n private prng: Prng;\n /** Per-box turn counters, keyed by box id (schema 3.4) - per flow. */\n private turnCounts = new Map<string, number>();\n private cooldowns: Record<string, number> = {};\n /** The board: hand contents (card ids, dealt order), keyed by hand id. */\n private boardContents = new Map<string, string[]>();\n private playLog: PlayRecord[] = [];\n // --- play-history indexes -------------------------------------------------\n // The four play-history host functions used to SCAN playLog on every call,\n // and they are called once per candidate card per ask, so dealing was\n // O(candidates x playLog): a shipped game got measurably slower the longer\n // somebody played it, which is the failure mode nobody meets in testing.\n // Measured before the change, 2000 cards: a box with no history condition\n // held flat at 0.3ms while `count_played` went 0.8ms -> 27.9ms as the log\n // reached 4000 plays.\n //\n // These are a pure summary of `playLog`, maintained where it is appended and\n // rebuilt where it is replaced, so they cannot drift from it. They are NOT\n // saved: `playLog` is the record, this is a derivation, and rebuilding on\n // load keeps the save format untouched.\n //\n // The tag keys are the played card's OWN (groupId, tagId) pairs, which is\n // what makes the box-local rule survive: a group name resolves inside the\n // ASKING box, so a card in another box carries different ids and cannot\n // match, exactly as the per-record `inTag` check used to decide.\n private playCount = new Map<string, number>();\n private lastPlayOf = new Map<string, PlayRecord>();\n private tagPlayCount = new Map<string, number>();\n private lastPlayInTag = new Map<string, PlayRecord>();\n /** The per-flow property partitions (the not-shared halves), each bag\n * that declares something registered under this flow's keys. */\n private stores: Partition;\n private readonly registered: string[] = [];\n /** This flow's bags that declare something, under their registry keys. */\n private readonly bagKeys: Array<[string, StateBag]> = [];\n\n private traceHandlers = new Set<TraceHandler>();\n private logEntries: LogEntry[] = [];\n private logSeq = 0;\n\n /** Merged read view per scope, built once (bags are stable for the\n * flow's life): the flow's own bag first, the shared bag behind it.\n * Names are disjoint (shared XOR per-flow by declaration), so \"first\"\n * is routing, not shadowing. */\n private readonly storyReader: ScopeResolver;\n private readonly boxReaders = new Map<string, ScopeResolver>();\n private readonly deckReaders = new Map<string, ScopeResolver>();\n\n /** @internal - built by Engine.openFlow / Engine.loadGame only. */\n constructor(engine: Engine, internals: Internals, id: string, seed: number) {\n this.engine = engine;\n this.internals = internals;\n this.id = id;\n this.prng = makePrng(seed);\n this.stores = buildPartition(internals, internals.flowDecls);\n // Register the bags: each claims whatever the registry holds for its key (a\n // load); openFlow discarded that first for a fresh flow.\n const put = (key: string, bag: StateBag): void => {\n if (bag.declarations().length === 0) return; // holds nothing: not registered\n this.bagKeys.push([key, bag]);\n };\n put(flowKey(id, \"story\"), this.stores.story);\n for (const kind of OWNED_SCOPES) for (const [owner, bag] of this.stores[kind]) put(flowKey(id, kind, owner), bag);\n this.mountBags();\n // A bag that claimed values a refitting load parked says what it put back.\n internals.reportRefits();\n for (const box of internals.bundle.boxes) {\n this.turnCounts.set(box.id, 0);\n for (const hand of box.hands) this.boardContents.set(hand.id, []);\n }\n // Own values only (`ownValue`): a name Object.prototype carries is not a\n // property of any scope here.\n const pair = (own: StateBag | undefined, shared: StateBag | undefined): ScopeResolver => ({\n get: (n) => ownValue(own, n) ?? ownValue(shared, n),\n });\n // The shared bags are built once for the engine's life and reseeded in\n // place by loadGame/reset, so these readers never go stale.\n this.storyReader = pair(this.stores.story, internals.shared.story);\n for (const box of internals.bundle.boxes) {\n this.boxReaders.set(box.id, pair(this.stores.box.get(box.id), internals.shared.box.get(box.id)));\n for (const deck of box.decks) {\n this.deckReaders.set(deck.id, pair(this.stores.deck.get(deck.id), internals.shared.deck.get(deck.id)));\n }\n }\n }\n\n // --- lifetime ----------------------------------------------------------------\n\n get isClosed(): boolean {\n return this.closed;\n }\n\n /** Close this flow: the handle goes inert, every verb throws. */\n close(): void {\n if (this.closed) return;\n this.engine.dropFlow(this.id, this);\n this.markClosed();\n }\n\n /** @internal - the handle goes inert, its bags leave the registry, and it\n * lets go of its trace handlers: a closed flow never emits again, and a\n * handler that holds the flow would otherwise hold it for good on a runtime\n * that counts references (Godot, C++). */\n markClosed(): void {\n this.releaseBags(false);\n this.traceHandlers.clear();\n this.closed = true;\n }\n\n /** @internal - take this flow's bags out of the registry; with `keep`, their\n * values wait there for the flow that replaces this one (a load into the\n * game's registry). Idempotent. */\n releaseBags(keep: boolean): void {\n for (const key of this.registered) this.internals.registry.remove(key, { keep });\n this.registered.length = 0;\n }\n\n /** @internal - the registry keys this flow holds right now. */\n registeredKeys(): string[] {\n return [...this.registered];\n }\n\n /** @internal - register this flow's bags (again): at construction, and when a\n * failed hotSwap hands them back. Each claims what the registry holds for it. */\n mountBags(): void {\n for (const [key, bag] of this.bagKeys) {\n this.internals.registry.mountOwned(key, bag, { owner: OWNER });\n this.registered.push(key);\n }\n }\n\n private assertOpen(): void {\n if (this.closed) throw new StoryletError(`flow \"${this.id}\" is closed`);\n }\n\n /** A box by gameId or id, or the refusal every verb that takes one gives. */\n private resolveBox(boxRef: string): Box<Expression> {\n const box = this.internals.boxesByGameId.get(boxRef) ?? this.internals.boxesById.get(boxRef);\n if (!box) throw new StoryletError(`unknown box \"${boxRef}\"`);\n return box;\n }\n\n /** A box's current turn (schema 3.4), on THIS flow's clock. */\n turn(boxRef: string): number {\n this.assertOpen();\n return this.turnCounts.get(this.resolveBox(boxRef).id) ?? 0;\n }\n\n /** Subscribe to this flow's deal/play trace (schema 5). Returns the\n * unsubscribe. With no subscribers anywhere the flow does no trace work. */\n subscribeTrace(handler: TraceHandler): () => void {\n // A Set: the same handler subscribed twice is registered once (ruling F).\n this.traceHandlers.add(handler);\n return () => { this.traceHandlers.delete(handler); };\n }\n\n private get tracing(): boolean {\n return this.traceHandlers.size > 0 || this.internals.logCap !== undefined || this.internals.engineTracing();\n }\n\n private emit(event: TraceEvent, turn?: number): void {\n if (this.internals.logCap !== undefined) {\n this.logEntries.push({ ...event, seq: this.logSeq++, ...(turn !== undefined ? { turn } : {}) });\n if (this.logEntries.length > this.internals.logCap) this.logEntries.splice(0, this.logEntries.length - this.internals.logCap);\n }\n // From a copy (ruling F): a handler that subscribes or unsubscribes during\n // an event changes who hears the NEXT one, never this one. A live Set\n // would call a handler added mid-delivery straight away.\n for (const handler of [...this.traceHandlers]) handler(event);\n this.internals.emitEngine(this.id, event, turn);\n }\n\n /** The retained flow log (opt-in via the Engine's `log`), oldest first,\n * capped. The introspection seam for hosts and tools; the durable play\n * history in a save stays `playLog` (schema 4) - the log is a\n * flow-lifetime utility and is NOT saved. */\n log(): readonly LogEntry[] {\n return this.logEntries;\n }\n\n /** Empty the retained log; `seq` keeps counting, so ordering across a\n * clear stays meaningful. */\n clearLog(): void {\n this.logEntries = [];\n }\n\n // --- expression plumbing ----------------------------------------------------\n\n private node(expr: Expression): ExprNode {\n let node = this.internals.nodeCache.get(expr);\n if (!node) {\n node = deserialiseAst(expr.ast);\n this.internals.nodeCache.set(expr, node);\n }\n return node;\n }\n\n /** Tag group names are box-scoped: two boxes may name a group the same way\n * (schema 1 - boxes namespace their groups), so a name is only ever\n * resolved inside the box being asked, never bundle-wide. Ids are\n * project-unique and accepted here too, still confined to the box.\n *\n * A box on the project map sees ONE namespace: its own groups, then the\n * map's group (design/project-map-contract.md 3.1). A box that has not\n * opted in does not see the map's name at all, so a peek naming it there\n * is the ordinary unknown-group refusal. Own groups first is stated for\n * determinism only: a bundle that loads never has the two share a name. */\n private groupInBox(box: Box<Expression>, ref: string): TagGroup | undefined {\n const groups = groupsOfBox(this.internals.bundle, box);\n return groups.find((g) => effectiveGameId(g) === ref)\n ?? groups.find((g) => g.id === ref);\n }\n\n /** Fold one play into the indexes. O(the card's tags), not O(the log). */\n private indexPlay(record: PlayRecord): void {\n this.playCount.set(record.card, (this.playCount.get(record.card) ?? 0) + 1);\n this.lastPlayOf.set(record.card, record);\n const entry = this.internals.cardsByGameId.get(record.card);\n if (!entry) return;\n for (const [groupId, tagIds] of Object.entries(entry.card.tags ?? {})) {\n for (const tagId of tagIds) {\n // Keyed by the PLAYED card's box: history is box-specific (D7).\n const key = tagKey(entry.box.id, groupId, tagId);\n this.tagPlayCount.set(key, (this.tagPlayCount.get(key) ?? 0) + 1);\n this.lastPlayInTag.set(key, record);\n }\n }\n }\n\n /** Rebuild the indexes from the log. Called wherever `playLog` is REPLACED\n * rather than appended to, which is `restore` alone. */\n private rebuildPlayIndex(): void {\n this.playCount.clear();\n this.lastPlayOf.clear();\n this.tagPlayCount.clear();\n this.lastPlayInTag.clear();\n for (const record of this.playLog) this.indexPlay(record);\n }\n\n /** One host per box, built once.\n *\n * The closures below read `this.playCount`, `this.turnCounts` and the rest\n * LIVE, so a cached host answers with current state - which is what makes\n * caching safe rather than a snapshot bug. Unreal did this from the start\n * (`hostsByBox_`, built at flow construction) and the other three rebuilt a\n * host, and its closures, on every `evalCtx` call: once per deck per ask,\n * and once per surviving card in the eviction pass. Structural divergence\n * in one port AND the allocation the audit flagged, so the other three\n * copied it (2026-08-29). Lazy rather than eager, so a bundle's unvisited\n * boxes cost nothing. */\n private hostsByBox = new Map<string, StoryletsHost>();\n\n private host(box: Box<Expression>): StoryletsHost {\n const cached = this.hostsByBox.get(box.id);\n if (cached !== undefined) return cached;\n const made = this.makeHost(box);\n this.hostsByBox.set(box.id, made);\n return made;\n }\n\n /** `box` is the box whose ask is being evaluated: the play-history\n * functions take a bare group name, so it resolves there, and they count\n * only that box's own plays (the box is in the index key). That was\n * automatic while every group was a box's; a project-map zone is shared,\n * and its history is still not (design/project-map-contract.md 3.7, D7).\n * History is THIS flow's: countPlayed answers \"have I done this\". */\n private makeHost(box: Box<Expression>): StoryletsHost {\n /** A group NAME and tag name resolved in THIS box, as the index's key,\n * with this box in it: a zone's plays in another box are not this box's\n * history. Undefined when either name is unknown here, which answers\n * \"never\".\n *\n * MEMOISED per (group, tag): the answer depends only on the bundle, which\n * never changes under a flow, and a history condition asks it once per\n * candidate card per ask, each time a walk of the box's groups and then\n * the group's tags. */\n const keys = new Map<string, string | undefined>();\n const keyOf = (group: string, tag: string): string | undefined => {\n const memo = `${group}\\u001f${tag}`;\n if (keys.has(memo)) return keys.get(memo);\n const found = this.groupInBox(box, group);\n const t = found?.tags.find((v) => v.gameId === tag);\n const key = found && t ? tagKey(box.id, found.id, t.id) : undefined;\n keys.set(memo, key);\n return key;\n };\n /** Turns-since is measured on the played card's box's clock (3.4). */\n const since = (record: PlayRecord): number => {\n const entry = this.internals.cardsByGameId.get(record.card);\n if (!entry) return NEVER_PLAYED;\n return (this.turnCounts.get(entry.box.id) ?? 0) - record.turn;\n };\n return {\n nextRandom: () => this.prng.next(),\n countPlayed: (card) => this.playCount.get(card) ?? 0,\n turnsSincePlayed: (card) => {\n const last = this.lastPlayOf.get(card);\n return last ? since(last) : NEVER_PLAYED;\n },\n countPlayedIn: (group, tag) => {\n const key = keyOf(group, tag);\n return key === undefined ? 0 : this.tagPlayCount.get(key) ?? 0;\n },\n turnsSincePlayedIn: (group, tag) => {\n const key = keyOf(group, tag);\n const last = key === undefined ? undefined : this.lastPlayInTag.get(key);\n return last ? since(last) : NEVER_PLAYED;\n },\n };\n }\n\n /** The evaluation environment (schema 3.1/6.2): @box/@deck resolve to the\n * card under evaluation; in hand-condition contexts @deck is an empty bag,\n * so any reference is an eval error (missing-policy throw). Every scope\n * is the flow's MERGED view - its own copies over the shared values,\n * names disjoint - and each host scope reads through the engine's resolver. */\n private evalCtx(box: Box<Expression>, deck: Deck<Expression> | undefined, handEnv: HandEnv): EvalContext {\n const others = this.internals.registryView();\n return {\n scopes: {\n // Every other engine's game-wide scope first (every engine reads every\n // scope); this engine's own tokens are its merged views, over the top.\n ...others.scopes,\n ...this.internals.hostResolvers,\n story: this.storyReader,\n box: this.boxReaders.get(box.id) ?? EMPTY_SCOPE,\n deck: deck ? this.deckReaders.get(deck.id) ?? EMPTY_SCOPE : EMPTY_SCOPE,\n hand: handEnv.bag,\n },\n host: this.host(box) as unknown as Record<string, unknown>,\n // The quality channel, answering for THIS ask's box and deck. Only wired\n // when a quality exists, so a bundle without one evaluates byte-\n // identically to before the feature.\n ...(this.internals.hasQualities || others.qualities !== undefined ? {\n qualities: (scope: string, name: string): readonly string[] | undefined =>\n scope === \"story\" ? this.internals.ladders.story.get(name)\n : scope === \"box\" ? this.internals.ladders.box.get(box.id)?.get(name)\n : scope === \"deck\" && deck ? this.internals.ladders.deck.get(deck.id)?.get(name)\n // @hand is composed, so the ladder belongs to whatever supplied the\n // value THIS ask: the bound tag, or the asking hand. `sources` is\n // already the map that answers that, because write-back needs it.\n : scope === \"hand\" ? this.handLadder(handEnv, name)\n : this.internals.ladders.host.get(scope)?.get(name) ?? others.qualities?.(scope, name),\n } : {}),\n };\n }\n\n /** The ladder behind one composed @hand name, or undefined when the name is\n * not a quality (or came from criteria, which are tag NAMES, never state). */\n private handLadder(handEnv: HandEnv, name: string): readonly string[] | undefined {\n const source = handEnv.sources.get(name);\n if (!source) return undefined;\n return source.kind === \"value\" ? this.internals.ladders.value.get(source.id)?.get(name)\n : source.kind === \"hand\" ? this.internals.ladders.hand.get(source.id)?.get(name)\n : undefined;\n }\n\n private eval(expr: Expression, ctx: EvalContext): ScalarValue {\n return evaluate(this.node(expr), ctx, storyletsDialect);\n }\n\n /** `node` names whose condition it is (`card <gameId>`, ...): a function, so\n * the label is built only when an evaluation fails. */\n private passes(expr: Expression | undefined, ctx: EvalContext, node: () => string): boolean {\n if (!expr) return true;\n try {\n return conditionPasses(this.eval(expr, ctx));\n } catch (e) {\n // An eval error is never a silent pass: the card/deck is unavailable\n // (schema 3.1), and the game hears of it through onError, tracing or\n // not; the trace carries it too.\n const message = e instanceof Error ? e.message : String(e);\n const at = node();\n const source = (expr as { src?: unknown }).src;\n this.internals.onError({ flow: this.id, kind: \"condition\", node: at, ...(typeof source === \"string\" && source !== \"\" ? { source } : {}), message });\n if (this.tracing) {\n this.emit({ type: \"diagnostic\", where: at.startsWith(\"deck \") ? `${at} gate` : `${at} condition`, message });\n }\n return false;\n }\n }\n\n // --- resolving asks (schema 2.6 + 3.6) -----------------------------------------\n\n private tagByGameId(group: TagGroup, gameId: string): Tag | undefined {\n return group.tags.find((t) => t.gameId === gameId);\n }\n\n /** A deal's ask: the hand's template bindings + chosen tags, or its rule's\n * bindings, plus the implicit home binding (schema 2.4). */\n private askForHand(hand: Hand<Expression>, box: Box<Expression>): AskDescriptor {\n const boundTags = new Map<string, string>();\n const askNames: Record<string, string> = {};\n /** A template instance's `chosen` and a standalone hand's rule\n * `bindings` bind the same way, so one loop serves both: a card reading\n * @hand.<group> must not care HOW the group got bound\n * (design/hand-typing.md, the residues). */\n const bindAll = (bindings: Record<string, string> | undefined): void => {\n for (const [groupId, tagId] of Object.entries(bindings ?? {})) {\n // A hole filled from a property rather than with a tag: resolve it\n // now, before tag composition (4.6, the hand that moves).\n if (isHoleRef(tagId)) {\n this.fillHoleFromProperty(hand, groupId, tagId, boundTags, askNames);\n continue;\n }\n boundTags.set(groupId, tagId);\n const found = this.internals.groupsById.get(groupId);\n const tag = found?.group.tags.find((t) => t.id === tagId);\n if (found && tag) askNames[effectiveGameId(found.group)] = effectiveGameId(tag);\n }\n };\n let condition: Expression | undefined;\n if (hand.template !== undefined) {\n const template = this.internals.templatesById.get(hand.template);\n if (!template) throw new StoryletError(`hand \"${effectiveGameId(hand)}\": unknown template \"${hand.template}\"`);\n // A template's FIXED bindings bind and are not named in @hand.\n for (const [groupId, tagId] of Object.entries(template.bindings ?? {})) {\n boundTags.set(groupId, tagId);\n }\n bindAll(hand.chosen);\n condition = template.condition;\n } else {\n bindAll(hand.rule?.bindings);\n condition = hand.rule?.condition;\n }\n boundTags.set(PLACE_GROUP, hand.id);\n this.bindStateGroups(box, boundTags, askNames);\n return { box, hand, ...(condition !== undefined ? { condition } : {}), boundTags, askNames };\n }\n\n /** A peek's ask: raw criteria ({group gameId: tag gameId}), bindings only,\n * no condition slot (schema 3.1; the boundary, Reboot 4). */\n private askForPeek(box: Box<Expression>, criteria: Record<string, string>): AskDescriptor {\n const boundTags = new Map<string, string>();\n const askNames: Record<string, string> = {};\n for (const [groupRef, tagRef] of Object.entries(criteria)) {\n if (groupRef === PLACE_GROUP) {\n const hand = this.internals.handsByGameId.get(tagRef) ?? this.internals.handsById.get(tagRef);\n if (!hand) throw new StoryletError(`peek: unknown hand \"${tagRef}\" in home criteria`);\n boundTags.set(PLACE_GROUP, hand.hand.id);\n continue;\n }\n const found = this.groupInBox(box, groupRef);\n if (!found) {\n throw new StoryletError(`peek: unknown tag group \"${groupRef}\" in box \"${effectiveGameId(box)}\"`);\n }\n const tag = this.tagByGameId(found, tagRef) ?? found.tags.find((t) => t.id === tagRef);\n if (!tag) throw new StoryletError(`peek: unknown tag \"${tagRef}\" in group \"${effectiveGameId(found)}\"`);\n boundTags.set(found.id, tag.id);\n askNames[effectiveGameId(found)] = effectiveGameId(tag);\n }\n this.bindStateGroups(box, boundTags, askNames);\n return { box, boundTags, askNames };\n }\n\n /**\n * Fill one hole from the property its value names: the hand that moves\n * (design/engine-server.md 4.6).\n *\n * The semantics are `bindStateGroups`' below, word for word, applied per\n * HOLE instead of per group: resolved at ask time, and a value naming no tag\n * leaves the hole UNBOUND (a wildcard) with a diagnostic rather than dealing\n * a silently empty hand. What is added is the `@hand` scope - the asking\n * hand's OWN declared state, read here from the flow's merged view (the\n * shared half under the flow's own, so a `shared: true` declaration moves\n * the hole for every flow and a per-flow one moves it for this flow alone).\n *\n * Read BEFORE tag composition, which is the whole reason it is safe: the\n * @hand bag a card sees is built from the bound tags, so resolving a hole\n * from it would be circular. A hand's own declarations are not, so they are.\n */\n private fillHoleFromProperty(\n hand: Hand<Expression>, groupId: string, ref: string,\n boundTags: Map<string, string>, askNames: Record<string, string>,\n ): void {\n const found = this.internals.groupsById.get(groupId);\n const groupName = found ? effectiveGameId(found.group) : groupId;\n const where = `hand ${effectiveGameId(hand)}, tag group ${groupName}`;\n const parsed = parseHoleRef(ref);\n if (!parsed) {\n this.emit({ type: \"diagnostic\", where, message: `\"${ref}\" is not a @hand, @world or @story property reference` });\n return;\n }\n if (!found) {\n this.emit({ type: \"diagnostic\", where, message: `\"${ref}\" fills a tag group that is not in this box` });\n return;\n }\n let value: ScalarValue | undefined;\n if (parsed.scope === \"hand\") {\n value = this.valuesOf(\"hand\", hand.id)[parsed.name];\n } else {\n try {\n value = this.getProperty(`${parsed.scope}.${parsed.name}`);\n } catch {\n value = undefined;\n }\n }\n if (value === undefined) {\n this.emit({ type: \"diagnostic\", where, message: `\"${ref}\" names a property that is not declared` });\n return;\n }\n const wanted = typeof value === \"string\" ? value : String(value);\n const tag = found.group.tags.find((t) => effectiveGameId(t) === wanted);\n if (!tag) {\n this.emit({ type: \"diagnostic\", where, message: `${ref} is \"${wanted}\", which is not one of the tags of \"${groupName}\"` });\n return;\n }\n boundTags.set(groupId, tag.id);\n askNames[groupName] = effectiveGameId(tag);\n }\n\n /**\n * Bind every `boundBy` group in the box from the property it names.\n *\n * The gap this closes: only a hand could bind a group, and `deal` takes no\n * criteria, so an axis driven by state (an act, a chapter) had nowhere to\n * gate. Runs AFTER the hand's own bindings and never overwrites one: an\n * explicit binding is a deliberate act and beats a default.\n *\n * A value naming no tag in the group leaves the group UNBOUND rather than\n * matching nothing. Unbound is a wildcard, so the ask still deals; a silent\n * empty hand would look like content that does not exist, and the diagnostic\n * is what says otherwise.\n */\n private bindStateGroups(box: Box<Expression>, boundTags: Map<string, string>, askNames: Record<string, string>): void {\n // The box's own groups and, when it is on the project map, the map's\n // group: a `boundBy` there binds in every opted-in box (3.2).\n for (const group of groupsOfBox(this.internals.bundle, box)) {\n if (group.boundBy === undefined || boundTags.has(group.id)) continue;\n const ref = /^@(world|story)\\.([a-z][a-z0-9_-]*)$/.exec(group.boundBy);\n if (!ref) {\n this.emit({ type: \"diagnostic\", where: `tag group ${effectiveGameId(group)}`, message: `boundBy \"${group.boundBy}\" is not a @world or @story property reference` });\n continue;\n }\n let value: ScalarValue | undefined;\n try {\n value = this.getProperty(`${ref[1]}.${ref[2]}`);\n } catch {\n this.emit({ type: \"diagnostic\", where: `tag group ${effectiveGameId(group)}`, message: `boundBy \"${group.boundBy}\" names a property that is not declared` });\n continue;\n }\n const wanted = typeof value === \"string\" ? value : String(value);\n const tag = group.tags.find((t) => effectiveGameId(t) === wanted);\n if (!tag) {\n this.emit({ type: \"diagnostic\", where: `tag group ${effectiveGameId(group)}`, message: `${group.boundBy} is \"${wanted}\", which is not one of its tags` });\n continue;\n }\n boundTags.set(group.id, tag.id);\n askNames[effectiveGameId(group)] = effectiveGameId(tag);\n }\n }\n\n // --- @hand composition (schema 3.6) -------------------------------------------\n\n /** A store's full value view for one owner: the shared half under the\n * flow's half. Names are disjoint, so the spread is routing, not\n * shadowing. */\n private valuesOf(kind: Exclude<PartitionKind, \"story\">, id: string): PropertyBag {\n // No prototype: `@hand.constructor` must find nothing, not Object's.\n return Object.assign(Object.create(null) as PropertyBag,\n this.internals.shared[kind].get(id)?.values ?? {},\n this.stores[kind].get(id)?.values ?? {});\n }\n\n private buildHandEnv(ask: AskDescriptor): HandEnv {\n // No prototype, as above: the composed @hand is read by plain index, so an\n // ordinary object would answer `@hand.constructor` with a function.\n const bag: PropertyBag = Object.create(null) as PropertyBag;\n const sources = new Map<string, HandSource>();\n\n // 1. Tag properties of every bound tag (home binds a hand, not a tag).\n for (const [groupId, tagId] of ask.boundTags) {\n if (groupId === PLACE_GROUP) continue;\n for (const [name, value] of Object.entries(this.valuesOf(\"value\", tagId))) {\n bag[name] = value;\n sources.set(name, { kind: \"value\", id: tagId });\n }\n }\n // 2. Hand properties, when the ask is a deal.\n if (ask.hand) {\n for (const [name, value] of Object.entries(this.valuesOf(\"hand\", ask.hand.id))) {\n bag[name] = value;\n sources.set(name, { kind: \"hand\", id: ask.hand.id });\n }\n }\n // 3. Chosen tags / criteria, by group name (the tag's gameId as value).\n for (const [name, value] of Object.entries(ask.askNames)) {\n bag[name] = value;\n sources.set(name, { kind: \"criteria\" });\n }\n return { bag, sources, boundTags: ask.boundTags };\n }\n\n // --- the ask (schema 3.1 + 3.2) ------------------------------------------------\n\n /** The claims ledger, derived from THIS flow's board: card id -> holding\n * hands (schema 3.5). Claims are per flow - another flow holding the\n * card is another playthrough, not a rival hand. */\n private claims(): Map<string, number> {\n const counts = new Map<string, number>();\n for (const contents of this.boardContents.values()) {\n for (const id of contents) counts.set(id, (counts.get(id) ?? 0) + 1);\n }\n return counts;\n }\n\n /** @internal - every card id on THIS flow's board, one entry per holding\n * hand. The engine sums these across live flows for the shared ledger. */\n heldCardIds(): string[] {\n return [...this.boardContents.values()].flat();\n }\n\n\n\n /** The claims step (schema 3.1 step 6) for one card, as a verdict or null\n * for \"available\". Two caps apply to a shared card and they are different\n * statements, so they get different verdicts: `copies` is your own board\n * filling up, `sharedCopies` is somebody else already holding it, and a\n * participant told \"claimed\" about a card sitting on another person's\n * table would read it as an engine fault (design/shared-scarcity 9.3.1).\n *\n * `mine` counts this flow's holdings, `world` every live flow's, and `own`\n * what the hand being dealt holds already: those are the hand's to keep or\n * give up as it re-ranks, so they claim nothing against it. */\n private claimVerdict(\n card: Card<Expression>,\n shared: boolean,\n mine: Map<string, number>,\n world: Map<string, number>,\n own = 0,\n ): \"claimed\" | \"claimed-elsewhere\" | null {\n const id = card.id;\n if ((mine.get(id) ?? 0) - own >= acrossLimit(card)) return \"claimed\";\n if (shared && (world.get(id) ?? 0) - own >= sharedCap(card)) return \"claimed-elsewhere\";\n return null;\n }\n\n /** Tag matching (schema 3.1 step 3): for every bound group the card lists\n * the bound tag or omits the group (wildcard); the home group inverts -\n * a homed card requires a matching home binding (schema 2.4). */\n private tagsMatch(card: Card<Expression>, boundTags: Map<string, string>): boolean {\n const home = card.tags?.[PLACE_GROUP];\n if (home !== undefined && home.length > 0) {\n const bound = boundTags.get(PLACE_GROUP);\n if (bound === undefined || !home.includes(bound)) return false;\n }\n for (const [groupId, tagId] of boundTags) {\n if (groupId === PLACE_GROUP) continue;\n const tags = card.tags?.[groupId];\n if (tags === undefined) {\n // Omission is a wildcard unless the group says otherwise. A required\n // group inverts it, which is what `place` has always done per card.\n if (this.internals.requiredGroups.has(groupId)) return false;\n continue;\n }\n if (!tags.includes(tagId)) return false;\n }\n return true;\n }\n\n /** Run one ask: availability filter then ranking. `claimed` decides the\n * claims step (step 6) per card, returning the verdict that refused it or\n * null for available. `trace` (when a subscriber exists) collects the\n * per-card verdicts. */\n private runAsk(\n ask: AskDescriptor,\n claimed: (card: Card<Expression>, shared: boolean) => \"claimed\" | \"claimed-elsewhere\" | null,\n trace?: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[],\n /** The cards the hand being dealt holds: they win ties, so a re-ranking\n * deal never swaps one for an equal. */\n held?: ReadonlyMap<string, number>,\n ): { ordered: CardEntry[]; handEnv: HandEnv } {\n const { box } = ask;\n const handEnv = this.buildHandEnv(ask);\n // Identity on the trace is by gameId (4.4), so the helper takes the CARD\n // rather than an id: every call site had one in hand, and taking the id\n // was the whole of how the two vocabularies got mixed.\n const verdict = (card: Card<Expression>, v: TraceVerdict): void => {\n trace?.push({ id: effectiveGameId(card), verdict: v });\n };\n\n // The hand's condition: ask-constant, evaluated once (schema 3.1 step 4).\n if (!this.passes(ask.condition, this.evalCtx(box, undefined, handEnv), () => `hand ${ask.hand ? effectiveGameId(ask.hand) : \"\"}`)) {\n return { ordered: [], handEnv };\n }\n\n // Deck gates: evaluated once per ask, in deck (id) order (schema 2.5).\n const gateOk = new Map<string, boolean>();\n for (const deck of box.decks) {\n gateOk.set(deck.id, this.passes(deck.condition, this.evalCtx(box, deck, handEnv), () => `deck ${deck.gameId}`));\n }\n\n const turn = this.turnCounts.get(box.id) ?? 0;\n const scored: { entry: CardEntry; priority: number; spec: number }[] = [];\n for (const deck of box.decks) {\n // ONE context per deck, not per card. It is built from box, deck and\n // handEnv, none of which vary inside this loop, and a condition is a\n // read-only gate (schema 3.1), so nothing can write through it and make\n // sharing visible. Rebuilding it per card cost an EvalContext, a scopes\n // map and five bag wrappers each time: about half the garbage a peek over\n // a large box produced (port-review-2026-08.md, measured).\n const deckCtx = this.evalCtx(box, deck, handEnv);\n const deckShared = deck.shared ?? false;\n for (const card of deck.cards) {\n const shared = cardIsShared(card, deckShared);\n if (!gateOk.get(deck.id)) {\n verdict(card, \"deck-gate\");\n continue;\n }\n // Taken out of the world by somebody's shared one-shot. Checked\n // before the flow's own clock, because \"cooldown\" would point the\n // reader at a turn counter that has nothing to do with it.\n if (shared && this.engine.isTaken(card.id)) {\n verdict(card, \"taken\");\n continue;\n }\n if ((this.cooldowns[card.id] ?? 0) > turn) {\n verdict(card, \"cooldown\");\n continue;\n }\n if (!this.tagsMatch(card, handEnv.boundTags)) {\n verdict(card, \"tags\");\n continue;\n }\n // The label is only read when an eval THROWS and only when tracing, so\n // building it per card was pure waste on the path that matters. Built\n // when tracing is on, where the cost is already accepted.\n if (card.condition && !this.passes(card.condition, deckCtx, () => `card ${card.gameId}`)) {\n verdict(card, \"condition\");\n continue;\n }\n const refused = claimed(card, shared); // claims, last (schema 3.1 step 6)\n if (refused) {\n verdict(card, refused);\n continue;\n }\n\n let priority: number;\n if (typeof card.priority === \"number\") {\n priority = card.priority;\n } else {\n try {\n const v = this.eval(card.priority, deckCtx);\n // NaN is a number to `typeof` and no rank to a sort (ruling G):\n // Inf - Inf, an overflow or a host value gets here.\n if (typeof v !== \"number\" || Number.isNaN(v)) {\n verdict(card, \"priority\");\n continue;\n }\n priority = v;\n } catch (e) {\n if (this.tracing) {\n this.emit({ type: \"diagnostic\", where: `card ${card.gameId} priority`, message: e instanceof Error ? e.message : String(e) });\n }\n verdict(card, \"priority\");\n continue;\n }\n }\n let spec = 0;\n if (box.ranking.specificity && card.condition) {\n const node = this.node(card.condition);\n spec = matchedSpecificity(node, (n) => {\n try {\n return conditionPasses(evaluate(n, deckCtx, storyletsDialect));\n } catch {\n return false;\n }\n });\n }\n scored.push({ entry: { card, deck, box }, priority, spec });\n }\n }\n\n scored.sort((a, b) => b.priority - a.priority || b.spec - a.spec); // stable\n // Seeded shuffle of each maximal tie run; runs of 1 consume no draws.\n let i = 0;\n while (i < scored.length) {\n let j = i + 1;\n while (j < scored.length\n && scored[j]!.priority === scored[i]!.priority\n && scored[j]!.spec === scored[i]!.spec) j++;\n if (j - i > 1) {\n // Held cards first, in the order they were scored; then the rest,\n // shuffled, or drawn by weight when their weights differ. Held cards\n // take no draws, so a deal draws what it always drew.\n const run = orderRun(scored.slice(i, j), held, this.prng);\n // Written back element by element, as all three ports do. It used to be\n // `scored.splice(i, j - i, ...run)`, which SPREADS one argument per\n // element: with every card at the default priority 0 the tie run is the\n // whole list, and a box of 150 000 such cards threw `RangeError:\n // Maximum call stack size exceeded` rather than dealing. Far-fetched\n // for one box, but it was a crash where the ports had no limit, and the\n // reference is supposed to be the thing they are transliterated from.\n for (let k = 0; k < run.length; k++) scored[i + k] = run[k]!;\n }\n i = j;\n }\n for (const s of scored) {\n trace?.push({ id: effectiveGameId(s.entry.card), verdict: \"dealt\", priority: s.priority, specificity: s.spec });\n }\n return { ordered: scored.map((s) => s.entry), handEnv };\n }\n\n /** Flip eligible-but-not-taken trace entries to \"capped\". `taken` is keyed\n * by GAMEID, as the trace rows are (4.4): the two must move together or\n * every dealt card silently reads as capped. */\n private capTrace(\n trace: { id: string; verdict: TraceVerdict; priority?: number; specificity?: number }[],\n taken: ReadonlySet<string>,\n ): void {\n for (const entry of trace) {\n if (entry.verdict === \"dealt\" && !taken.has(entry.id)) entry.verdict = \"capped\";\n }\n }\n\n private view(entry: CardEntry): DealtCard {\n const { card } = entry;\n return {\n id: card.id,\n gameId: effectiveGameId(card),\n ...(card.title !== undefined ? { title: card.title } : {}),\n ...(card.purpose !== undefined ? { purpose: card.purpose } : {}),\n // Fields are copied: the game may change what it is handed, and the\n // bundle's own maps are every later deal's, in every engine on it (the\n // hands-on review, 2026-10, foundation 6). Extension values are NOT: they\n // are the extension runtime's data, read-only by contract, and can be\n // large, so a copy per deal would charge every game for a mistake good\n // readers don't make (a family ruling, 2026-10-28, as Patter's).\n ...(card.fields !== undefined ? { fields: structuredClone(card.fields) } : {}),\n ...(card.extensions !== undefined ? { extensions: card.extensions } : {}),\n };\n }\n\n private handCapacity(hand: Hand<Expression>): number {\n if (hand.slots !== undefined) return hand.slots;\n const declared = hand.template !== undefined\n ? this.internals.templatesById.get(hand.template)?.slots\n : hand.rule?.slots;\n return declared === undefined || declared === \"unbounded\" ? Infinity : declared;\n }\n\n private resolveHand(ref: string): { hand: Hand<Expression>; box: Box<Expression> } {\n const found = this.internals.handsByGameId.get(ref) ?? this.internals.handsById.get(ref);\n if (!found) throw new StoryletError(`unknown hand \"${ref}\"`);\n return found;\n }\n\n // --- host surface (schema 5) ---------------------------------------------------\n\n /** Look at the top of the stock through raw tag criteria (schema 3.1):\n * claims respected, nothing registered, nothing left behind but the\n * trace line. You can never play a card you only peeked. */\n peek(boxRef: string, criteria: Record<string, string> = {}, n?: number): RankedList {\n this.assertOpen();\n const box = this.resolveBox(boxRef);\n const ask = this.askForPeek(box, criteria);\n const claimCounts = this.claims();\n // Skipped outright when the bundle shares nothing, which is most bundles:\n // the ledger walks every live flow's whole board, and an empty map answers\n // every question the same way a computed one would.\n const worldClaims = this.internals.hasShared ? this.engine.sharedClaims() : new Map<string, number>();\n const trace = this.tracing ? [] : undefined;\n // A peek consumes NO draws (ruling A, 2026-10-06): its tie shuffle and any\n // random() in a condition run on a throwaway COPY of the flow's generator,\n // so looking at the stock any number of times leaves the next deal exactly\n // as it was, and the peek still answers as a deal would now. The host's\n // closures read `this.prng` live, which is what the swap relies on.\n const own = this.prng;\n this.prng = makePrng(own.state());\n let ordered: CardEntry[];\n try {\n ({ ordered } = this.runAsk(ask, (card, shared) => this.claimVerdict(card, shared, claimCounts, worldClaims), trace));\n } finally {\n this.prng = own;\n }\n // CLAMPED. `slice(0, -1)` drops the last card and returns the rest, so a\n // negative n answered with almost the whole list here while every port\n // returned nothing (2026-08-29).\n const listed = n === undefined ? ordered : ordered.slice(0, Math.max(n, 0));\n if (trace) {\n this.capTrace(trace, new Set(listed.map((e) => effectiveGameId(e.card))));\n this.emit({ type: \"peek\", box: effectiveGameId(box), criteria, cards: trace }, this.turnCounts.get(box.id) ?? 0);\n }\n return { box: effectiveGameId(box), cards: listed.map((e) => this.view(e)) };\n }\n\n /** Refresh one hand (schema 3.5); returns its new shape. */\n deal(handRef: string): DealtCard[] {\n this.assertOpen();\n const { hand } = this.resolveHand(handRef);\n return this.dealMany([handRef])[effectiveGameId(hand)] ?? [];\n }\n\n /** Drop the cards a hand holds that are no longer available to it, as a deal\n * does before it fills, and say which went and why: the deal's first pass,\n * and the load's check of what it put back (`dropUnqualified`). */\n private evictUnavailable(hand: Hand<Expression>, box: Box<Expression>): { card: string; reason: CheckReason }[] {\n const ask = this.askForHand(hand, box);\n const handEnv = this.buildHandEnv(ask);\n const conditionOk = this.passes(ask.condition, this.evalCtx(box, undefined, handEnv), () => `hand ${effectiveGameId(hand)}`);\n // ONE context per deck, as the fill builds them (see runAsk): the gate\n // and every surviving card of that deck are evaluated against it.\n const gateOk = new Map<string, boolean>();\n const deckCtx = new Map<string, EvalContext>();\n for (const deck of box.decks) {\n const ctx = this.evalCtx(box, deck, handEnv);\n deckCtx.set(deck.id, ctx);\n gateOk.set(deck.id, this.passes(deck.condition, ctx, () => `deck ${deck.gameId}`));\n }\n const turn = this.turnCounts.get(box.id) ?? 0;\n const evicted: { card: string; reason: CheckReason }[] = [];\n // The card is named by gameId (4.4). A card the build no longer has -\n // the `vanished` branch below - has no gameId left, so it is named by\n // the id the board carries, which is the rule a load report has always\n // used for the same reason.\n const evict = (cardId: string, reason: CheckReason): false => {\n const known = this.internals.cardsById.get(cardId);\n evicted.push({ card: known ? effectiveGameId(known.card) : cardId, reason });\n return false;\n };\n const survivors = (this.boardContents.get(hand.id) ?? []).filter((cardId) => {\n if (!conditionOk) return evict(cardId, \"hand-condition\");\n const entry = this.internals.cardsById.get(cardId);\n if (!entry) return evict(cardId, \"vanished\"); // edited content: dropped\n // Its deck is in ANOTHER box now (a load, resume or hot swap into an\n // edited build): gone from this hand's box, as a deleted card is\n // (ruling D). It was evicted as \"deck-gate\" before, by a missing map key.\n if (entry.box.id !== box.id) return evict(cardId, \"vanished\");\n if (!gateOk.get(entry.deck.id)) return evict(cardId, \"deck-gate\");\n // Taken out of the world by somebody's shared one-shot (ruling C):\n // checked where the fill checks it, after the gate and before the\n // flow's own clock, so a card held by one playthrough goes from its\n // hand once another has spent it, as a cooldown would evict it.\n if (cardIsShared(entry.card, entry.deck.shared ?? false) && this.engine.isTaken(cardId)) {\n return evict(cardId, \"taken\");\n }\n if ((this.cooldowns[cardId] ?? 0) > turn) return evict(cardId, \"cooldown\");\n if (!this.tagsMatch(entry.card, handEnv.boundTags)) return evict(cardId, \"tags\");\n // The label is read only when an eval throws AND tracing is on.\n if (!this.passes(entry.card.condition, deckCtx.get(entry.deck.id)!, () => `card ${entry.card.gameId}`)) {\n return evict(cardId, \"condition\");\n }\n return true;\n });\n this.boardContents.set(hand.id, survivors);\n return evicted;\n }\n\n /** @internal - after a load puts this flow's board back, drop every held\n * card that no longer qualifies, as a deal would (the hands-on review,\n * 2026-10, foundation 7: a save from before a content edit could put back a\n * card whose condition is now false, and nothing said so). Every hand, in\n * id order, on a copy of the flow's generator, so the check draws nothing\n * and the next deal is exactly what it would have been. */\n dropUnqualified(): { hand: string; card: string; reason: CheckReason }[] {\n const own = this.prng;\n this.prng = makePrng(own.state());\n try {\n const out: { hand: string; card: string; reason: CheckReason }[] = [];\n for (const handId of [...this.internals.handsById.keys()].sort()) {\n const { hand, box } = this.internals.handsById.get(handId)!;\n for (const e of this.evictUnavailable(hand, box)) out.push({ hand: effectiveGameId(hand), ...e });\n }\n return out;\n } finally {\n this.prng = own;\n }\n }\n\n /** Re-deal several / all hands (schema 3.5): seeded hand-order shuffle\n * (fairness), evict, seed the ledger from survivors, fill in order.\n * Returns the dealt slice - the new contents of exactly the hands this\n * call dealt, keyed by hand gameId (board() stays the whole-board read).\n *\n * The eviction pass evaluates each hand's condition and deck gates, and the\n * fill evaluates them again, asking for the hand twice. That is the draw\n * count of a deal and the corpus pins it: merging the two would move every\n * random() in a gate and the count of diagnostics (engine review 2026-10,\n * section 4). */\n dealMany(handRefs?: string[]): Record<string, DealtCard[]> {\n this.assertOpen();\n const dealt = (handRefs ?? [...this.internals.handsById.keys()].sort())\n .map((ref) => this.resolveHand(ref));\n shuffleInPlace(dealt, this.prng);\n\n // Eviction first: drop dealt cards no longer available to their hand\n // (minus the claims check against their own seat). Trace events fire after\n // the state they report has landed (a handler reading the board sees the\n // eviction).\n for (const { hand, box } of dealt) {\n const evicted = this.evictUnavailable(hand, box);\n if (this.tracing) {\n const turn = this.turnCounts.get(box.id) ?? 0;\n for (const e of evicted) this.emit({ type: \"evict\", hand: effectiveGameId(hand), card: e.card, reason: e.reason }, turn);\n }\n }\n\n const claimCounts = this.claims();\n // The world ledger is taken once for the whole batch and kept in step with\n // the local one below, so two hands in the SAME deal cannot both take the\n // last shared copy. Skipped outright when the bundle shares nothing (most\n // bundles): an empty map answers every question the same way.\n const worldClaims = this.internals.hasShared ? this.engine.sharedClaims() : new Map<string, number>();\n for (const { hand, box } of dealt) {\n // A deal makes the hand right (the hands-on review, 2026-10, ruling 1):\n // it ranks everything eligible, the cards it holds included, and the hand\n // becomes the top of that ranking, full or not. A held card wins a tie,\n // so an equal card never displaces it; one a better card displaces goes\n // back to the stock, \"outranked\".\n const held = this.boardContents.get(hand.id) ?? [];\n const own = new Map<string, number>();\n for (const id of held) own.set(id, (own.get(id) ?? 0) + 1);\n const ask = this.askForHand(hand, box);\n const trace = this.tracing ? [] : undefined;\n // At most `inOneHand` times in this hand; in one hand of this flow at a\n // time unless the card says any (or, from an older bundle, `copies`);\n // at most `sharedCopies` hands anywhere (schema 3.5, shared-scarcity 5).\n const { ordered } = this.runAsk(ask,\n (card, shared) => this.claimVerdict(card, shared, claimCounts, worldClaims, own.get(card.id) ?? 0), trace, own);\n // The top of the ranking, each card as many times as one hand may hold it.\n const want = new Map<string, number>();\n let room = this.handCapacity(hand);\n for (const e of ordered) {\n if (room <= 0) break;\n const n = Math.min(inOneHandOf(e.card), room);\n want.set(e.card.id, n);\n room -= n;\n }\n // Held cards keep their places; newcomers follow, in rank order.\n const next: string[] = [];\n const outranked: string[] = [];\n for (const id of held) {\n const n = want.get(id) ?? 0;\n if (n > 0) { next.push(id); want.set(id, n - 1); } else outranked.push(id);\n }\n for (const e of ordered) {\n for (let n = want.get(e.card.id) ?? 0; n > 0; n--) next.push(e.card.id);\n want.delete(e.card.id);\n }\n this.boardContents.set(hand.id, next);\n for (const id of held) {\n claimCounts.set(id, (claimCounts.get(id) ?? 1) - 1);\n worldClaims.set(id, (worldClaims.get(id) ?? 1) - 1);\n }\n for (const id of next) {\n claimCounts.set(id, (claimCounts.get(id) ?? 0) + 1);\n worldClaims.set(id, (worldClaims.get(id) ?? 0) + 1);\n }\n // Emitted after the hand is set: a handler reading board() sees the deal.\n if (trace) {\n const turn = this.turnCounts.get(box.id) ?? 0;\n for (const id of outranked) {\n this.emit({ type: \"evict\", hand: effectiveGameId(hand), card: effectiveGameId(this.internals.cardsById.get(id)!.card), reason: \"outranked\" }, turn);\n }\n this.capTrace(trace, new Set(next.map((id) => effectiveGameId(this.internals.cardsById.get(id)!.card))));\n this.emit({ type: \"deal\", hand: effectiveGameId(hand), cards: trace }, turn);\n }\n }\n\n return Object.fromEntries(dealt.map(({ hand }) => [\n effectiveGameId(hand),\n (this.boardContents.get(hand.id) ?? []).map((id) => this.view(this.internals.cardsById.get(id)!)),\n ]));\n }\n\n /** The board: current hand contents, in dealt order, keyed by hand gameId\n * (schema 5). Read it for what is out; peek the stock for what could\n * come.\n *\n * `boxRef` (a box gameId or id) narrows the read to that box's hands, in\n * the same shape and the same order: \"give me the barks hands\" is a\n * common host query, and boxes are how a game separates its storylet\n * systems, so the grouping belongs here rather than in every host. An\n * unknown box throws, as it does on turn() and peek(). */\n board(boxRef?: string): Record<string, DealtCard[]> {\n this.assertOpen();\n const keep = boxRef !== undefined ? this.resolveBox(boxRef).id : undefined;\n return Object.fromEntries([...this.boardContents.entries()]\n .filter(([handId]) => keep === undefined || this.internals.handsById.get(handId)!.box.id === keep)\n .map(([handId, ids]) => [\n effectiveGameId(this.internals.handsById.get(handId)!.hand),\n ids.map((id) => this.view(this.internals.cardsById.get(id)!)),\n ]));\n }\n\n /** Resolve a played/inspected card within a hand on the board. */\n private resolveDealt(cardId: string, handRef: string): { entry: CardEntry; ask: AskDescriptor } {\n const entry = this.internals.cardsById.get(cardId) ?? this.internals.cardsByGameId.get(cardId);\n if (!entry) throw new StoryletError(`unknown card \"${cardId}\"`);\n const { hand, box } = this.resolveHand(handRef);\n if (!(this.boardContents.get(hand.id) ?? []).includes(entry.card.id)) {\n throw new StoryletError(`card \"${effectiveGameId(entry.card)}\" is not dealt to hand \"${effectiveGameId(hand)}\"`);\n }\n return { entry, ask: this.askForHand(hand, box) };\n }\n\n /** Outcome availability, evaluated against CURRENT state on every ask\n * (schema 3.1/5) - never a deal-time snapshot. */\n outcomes(cardId: string, from: string): OutcomeView[] {\n this.assertOpen();\n const { entry, ask } = this.resolveDealt(cardId, from);\n const ctx = this.evalCtx(entry.box, entry.deck, this.buildHandEnv(ask));\n return entry.card.outcomes.map((o) => ({\n id: o.id,\n gameId: effectiveGameId(o),\n ...(o.title !== undefined ? { title: o.title } : {}),\n ...(o.purpose !== undefined ? { purpose: o.purpose } : {}),\n // Fields copied, as a dealt card's (`view`).\n ...(o.fields !== undefined ? { fields: structuredClone(o.fields) } : {}),\n ...(o.extensions !== undefined ? { extensions: o.extensions } : {}), // read-only, as a dealt card's\n available: this.passes(o.condition, ctx, () => `outcome ${effectiveGameId(entry.card)}/${effectiveGameId(o)}`),\n }));\n }\n\n /** Apply an outcome (schema 3.7): the card must sit in a hand on the\n * board (you never play a card from inside the deck). All-or-nothing:\n * throws before any mutation on a gated-shut outcome or a refused write\n * target, and a refusal changes nothing at all.\n *\n * A card with NO outcomes is played with none, named as \"\" (the\n * no-outcome-play brief, 2026-09-14): a masthead, a notice, a codex entry,\n * whose play means \"shown\". It is everything a play is except the writes:\n * the play log and the history functions count it, the box's turn moves by\n * the usual rule, the redraw rests it and it leaves its hand. \"\" is the one\n * spelling in all four runtimes, because a Blueprint pin cannot be absent.\n * Only the empty-for-empty case is new: \"\" on a card that has outcomes is\n * refused, and a named outcome on a card with none is refused as before. */\n play(cardId: string, outcomeGameId: string, from: string, opts: PlayOptions = {}): void {\n this.assertOpen();\n const { entry, ask } = this.resolveDealt(cardId, from);\n const bare = outcomeGameId === \"\";\n if (bare && entry.card.outcomes.length > 0) {\n throw new StoryletError(`card \"${effectiveGameId(entry.card)}\" has outcomes (${entry.card.outcomes.map((o) => effectiveGameId(o)).join(\", \")}); name the one played`);\n }\n const outcome = bare ? undefined : entry.card.outcomes.find((o) => effectiveGameId(o) === outcomeGameId);\n if (!bare && !outcome) throw new StoryletError(`card \"${effectiveGameId(entry.card)}\" has no outcome \"${outcomeGameId}\"`);\n\n const handEnv = this.buildHandEnv(ask);\n const ctx = this.evalCtx(entry.box, entry.deck, handEnv);\n if (outcome && !this.passes(outcome.condition, ctx, () => `outcome ${effectiveGameId(entry.card)}/${outcomeGameId}`)) {\n throw new StoryletError(`outcome \"${outcomeGameId}\" on \"${effectiveGameId(entry.card)}\" is gated shut`);\n }\n\n // The played card's box's clock advances (schema 3.4); computed up\n // front so the play and its writes log as one action, one turn stamp.\n //\n // A TIMED box (design/engine-server.md 4.8) defaults to advancing\n // NOTHING: its clock is time, the host ticks it, and a play is not a\n // tick. A call that names `advanceTurns` still gets what it asked for,\n // in either kind of box, because the call says otherwise. This is the\n // whole of `turn`'s effect on the engine: the seconds are never read.\n const perPlay = entry.box.turn !== undefined ? 0 : this.internals.bundle.settings.playAdvancesTurns;\n const newTurn = (this.turnCounts.get(entry.box.id) ?? 0) + (opts.advanceTurns ?? perPlay);\n\n // A play is ALL-OR-NOTHING (ruling B, 2026-10-06). Every target is\n // resolved and checked first, so a refusal the engine can know in advance\n // lands nothing and emits nothing; then every right-hand side evaluates\n // against PRE-play state (schema 3.7); then the writes land. A refusal\n // only the landing can meet puts back what the earlier writes replaced.\n // The write events fire once all of them have landed.\n const changes = Object.entries(outcome?.changes ?? {})\n .map(([target, expr]) => ({ target, expr, plan: this.planWrite(target, entry, handEnv) }));\n const writes = changes.map((c) => ({ ...c, value: this.eval(c.expr, ctx) }));\n const landed: { target: string; plan: WritePlan; value: ScalarValue; prev: ScalarValue | undefined }[] = [];\n try {\n for (const w of writes) landed.push({ target: w.target, plan: w.plan, value: w.value, prev: this.landWrite(w.plan, w.value) });\n } catch (e) {\n for (const l of [...landed].reverse()) this.undoWrite(l.plan, l.prev);\n throw e;\n }\n if (this.tracing) {\n for (const { target, plan, value, prev } of landed) {\n this.emit({ type: \"write\", target, path: plan.path, value, ...(prev !== undefined ? { prev } : {}) }, newTurn);\n }\n }\n\n const outcomeId = outcome ? effectiveGameId(outcome) : \"\";\n const record: PlayRecord = { card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn };\n this.playLog.push(record);\n this.indexPlay(record);\n if (entry.card.redraw === \"never\") {\n // A shared one-shot leaves the world rather than this flow: the engine\n // holds it, so every flow is refused. A finite redraw deliberately does\n // NOT share, whatever the deck says - a cooldown is an absolute turn of\n // this flow's box clock, and there is no shared clock to compare it\n // against (design/shared-scarcity.md 9.3.2).\n if (cardIsShared(entry.card, entry.deck.shared ?? false)) this.engine.markTaken(entry.card.id);\n else this.cooldowns[entry.card.id] = Number.MAX_SAFE_INTEGER;\n } else if (typeof entry.card.redraw === \"number\") {\n this.cooldowns[entry.card.id] = newTurn + entry.card.redraw;\n }\n // The card leaves its hand, releasing its claim (schema 3.5/3.7).\n const handId = ask.hand!.id;\n // One copy, for a card the hand holds more than once (`inOneHand`).\n const contents = this.boardContents.get(handId) ?? [];\n const at = contents.indexOf(entry.card.id);\n this.boardContents.set(handId, [...contents.slice(0, at), ...contents.slice(at + 1)]);\n this.turnCounts.set(entry.box.id, newTurn);\n // Emitted last: a handler reading the board and the clock sees the play.\n if (this.tracing) this.emit({ type: \"play\", card: effectiveGameId(entry.card), outcome: outcomeId, turn: newTurn }, newTurn);\n }\n\n /**\n * Take a card out of a hand without playing it: the player turned it down,\n * or the game wants the room (the hands-on review, 2026-10, ruling: discard).\n * No outcome, no play in the log, no move of the clock, no play count. It\n * starts its Redraw as a play would, so a re-ranking deal doesn't hand it\n * straight back: a finite Redraw waits that many turns from now, a \"never\"\n * card is gone for good (for everyone, when it's shared), and an \"always\"\n * card can come up at the next deal. One copy leaves, for a card the hand\n * holds more than once.\n */\n discard(cardId: string, from: string): void {\n this.assertOpen();\n const { entry, ask } = this.resolveDealt(cardId, from);\n const turn = this.turnCounts.get(entry.box.id) ?? 0;\n if (entry.card.redraw === \"never\") {\n if (cardIsShared(entry.card, entry.deck.shared ?? false)) this.engine.markTaken(entry.card.id);\n else this.cooldowns[entry.card.id] = Number.MAX_SAFE_INTEGER;\n } else if (typeof entry.card.redraw === \"number\") {\n this.cooldowns[entry.card.id] = turn + entry.card.redraw;\n }\n const handId = ask.hand!.id;\n const contents = this.boardContents.get(handId) ?? [];\n const at = contents.indexOf(entry.card.id);\n this.boardContents.set(handId, [...contents.slice(0, at), ...contents.slice(at + 1)]);\n if (this.tracing) this.emit({ type: \"discard\", hand: effectiveGameId(ask.hand!), card: effectiveGameId(entry.card), turn }, turn);\n }\n\n /** One owned property's address, owner segment and all (4.4). */\n private address(kind: OwnedScope, id: string): string {\n return addressOf(this.internals, kind, id);\n }\n\n /** Find the bag that declares one change's name: the flow's bag when the\n * property is per-flow, the shared bag when it is shared - the\n * union/partition invariant made executable. Read by OWN value, so a name\n * Object.prototype carries is no property. Throws, landing nothing, when\n * no bag declares it or the declaration is `writable: false`. */\n private planBag(kind: Exclude<PartitionKind, \"story\"> | \"story\", id: string | undefined, name: string, path: string): WritePlan {\n const own = kind === \"story\" ? this.stores.story : id !== undefined ? this.stores[kind].get(id) : undefined;\n const shared = kind === \"story\" ? this.internals.shared.story : id !== undefined ? this.internals.shared[kind].get(id) : undefined;\n const bag = ownValue(own, name) !== undefined ? own\n : ownValue(shared, name) !== undefined ? shared\n : undefined;\n if (bag === undefined) throw new StoryletError(`no property at \"${path}\"`);\n // The kernel would refuse this at `set`, with this text; asked here so the\n // refusal comes before any of the play's writes has landed.\n if (bag.declarations().some((d) => d.name === name && d.writable === false)) {\n throw new StoryletError(`'${name}' is read-only`);\n }\n return { kind: \"bag\", bag, name, path };\n }\n\n /** Resolve and check one change target, writing nothing: every refusal a\n * write can meet that the engine can know in advance is met HERE, so a\n * play checks all its targets before any write lands (ruling B). */\n private planWrite(target: string, entry: CardEntry, handEnv: HandEnv): WritePlan {\n const match = /^@([a-z][a-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_-]*)$/.exec(target);\n if (!match) throw new StoryletError(`bad change target \"${target}\"`);\n const [, scope, name] = match as unknown as [string, string, string];\n const host = this.internals.hostScopes.get(scope);\n if (host !== undefined) {\n if (!host.set) throw new StoryletError(`@${scope}.${name} cannot be written: the host bound @${scope} read-only`);\n // The story's own promise, kept HERE and only here, because here is where\n // the story does the writing: it is consulted before the seam, so an\n // outcome never reaches the bag (which would let a host write past it)\n // nor a bound resolver (which cannot tell the two apart). The host's\n // setProperty is its own path and never asks. Patter's runtime refuses the\n // same write through the shared kernel, so both read one way.\n if (hostReadOnly(host.spec, name)) throw new StoryletError(`'@${scope}.${name}' is read-only (writable: false)`);\n return { kind: \"host\", host, name, path: `${scope}.${name}` };\n }\n switch (scope) {\n case \"story\": return this.planBag(\"story\", undefined, name, `story.${name}`);\n case \"box\": return this.planBag(\"box\", entry.box.id, name, `${this.address(\"box\", entry.box.id)}.${name}`);\n case \"deck\": return this.planBag(\"deck\", entry.deck.id, name, `${this.address(\"deck\", entry.deck.id)}.${name}`);\n case \"hand\": {\n // Write-back routing (schema 3.6): the composed name remembers its\n // source store; writes to criteria/chosen-tag names are errors.\n const source = handEnv.sources.get(name);\n if (!source) throw new StoryletError(`@hand.${name} is not composed in this ask`);\n if (source.kind === \"criteria\") throw new StoryletError(`@hand.${name} is a chosen tag / criteria name and cannot be written`);\n return this.planBag(source.kind, source.id, name, `${this.address(source.kind, source.id)}.${name}`);\n }\n default: {\n // Another engine's game-wide scope (`@patter.x`): the family's shared\n // vocabulary lets a card write it, and the registry keeps that engine's\n // rules (a read-only property is refused when the write lands, and the\n // play then puts back what it had written). A story write, so no host flag.\n if (this.internals.registry.has(scope)) return { kind: \"scope\", scope, name, path: `${scope}.${name}` };\n if (this.internals.bundle.externalScopes?.includes(scope)) {\n throw new StoryletError(`@${scope}.${name} cannot be written: no engine on this registry registered @${scope}`);\n }\n throw new StoryletError(`bad change target scope \"@${scope}\"`);\n }\n }\n }\n\n /** Land one planned change; returns the value it replaced (for the log's\n * \"0 -> 1\" reading). An engine write: the bag's subscribers fire (the\n * firing rule). */\n private landWrite(plan: WritePlan, value: ScalarValue): ScalarValue | undefined {\n switch (plan.kind) {\n case \"host\": {\n const prev = plan.host.resolver.get(plan.name);\n plan.host.set!(plan.name, value);\n return prev;\n }\n case \"bag\": return plan.bag.set(plan.name, value).prev;\n case \"scope\": {\n const prev = this.internals.registry.get(plan.scope, plan.name);\n this.internals.registry.set(plan.scope, plan.name, value);\n return prev;\n }\n }\n }\n\n /** Put back what one landed change replaced, when a LATER change in the same\n * play was refused as it landed (a refusal no plan could see coming: another\n * engine's read-only property, a host resolver that throws). Silent and as\n * the host, since this is the engine undoing itself, not the story writing. */\n private undoWrite(plan: WritePlan, prev: ScalarValue | undefined): void {\n if (prev === undefined) return;\n switch (plan.kind) {\n case \"host\": plan.host.set!(plan.name, prev, true); return;\n case \"bag\": plan.bag.set(plan.name, prev, { silent: true, reason: \"play refused\", host: true }); return;\n case \"scope\": this.internals.registry.set(plan.scope, plan.name, prev, { host: true }); return;\n }\n }\n\n /** Advance one box's clock (schema 3.4): a turn is one draw-from-stock\n * session for that box, on THIS flow's clock. */\n advanceTurns(boxRef: string, n = 1): void {\n this.assertOpen();\n const box = this.resolveBox(boxRef);\n const next = (this.turnCounts.get(box.id) ?? 0) + n;\n this.turnCounts.set(box.id, next);\n if (this.tracing) this.emit({ type: \"turns\", box: effectiveGameId(box), turn: next }, next);\n }\n\n // --- state access (host surface + test tooling) ---------------------------------\n\n /** Every box, bundle order: identity + this flow's clock (the enumeration\n * surface examiners key their turns sections on; parity member). */\n listBoxes(): BoxView[] {\n this.assertOpen();\n return this.internals.bundle.boxes.map((b) => ({\n id: b.id,\n gameId: effectiveGameId(b),\n ...(b.title !== undefined ? { title: b.title } : {}),\n turn: this.turnCounts.get(b.id) ?? 0,\n // Read-only and uncopied, as a dealt card's extension values (`view`).\n ...(b.extensions !== undefined ? { extensions: b.extensions } : {}),\n }));\n }\n\n /** THIS flow's kernel bags with their store path prefixes (the state\n * logger's mount surface; parity member). The shared bags are the\n * Engine's listBags; the flows are rebuilt by loadGame, so consumers\n * re-enumerate after a load. */\n listBags(): BagMount[] {\n this.assertOpen();\n const mounts: BagMount[] = [{ prefix: \"story\", bag: this.stores.story }];\n for (const kind of OWNED_SCOPES) {\n for (const [id, bag] of this.stores[kind]) mounts.push({ prefix: addressOf(this.internals, kind, id), bag });\n }\n return mounts;\n }\n\n /** The flow's FULL merged view as examiner rows (the property examiner /\n * editor surface, parity across all runtimes): the host scopes read\n * through their resolvers, then per scope the shared values and this\n * flow's own. Bundle order: host scopes (@world first when the bundle\n * lists it first), story, then per-box / per-deck / per-hand / per-tag\n * stores. */\n listProperties(): PropertyRow[] {\n this.assertOpen();\n const out: PropertyRow[] = hostRows(this.internals);\n const add = (_prefix: string, shared: StateBag | undefined, own: StateBag | undefined): void => {\n for (const bag of [shared, own]) {\n if (bag === undefined) continue;\n for (const row of bag.rows()) out.push(row);\n }\n };\n add(\"story\", this.internals.shared.story, this.stores.story);\n for (const kind of OWNED_SCOPES) {\n const ids = new Set([...this.internals.shared[kind].keys(), ...this.stores[kind].keys()]);\n for (const id of ids) {\n add(addressOf(this.internals, kind, id), this.internals.shared[kind].get(id), this.stores[kind].get(id));\n }\n }\n return out;\n }\n\n /** Read by path: \"world.x\", \"story.gold\", \"value.docks.danger\",\n * \"box.village.heat\", \"deck.wares.n\", \"hand.the-elder.zone\" - the flow's\n * merged view, routed by the declaration's sharing.\n *\n * The owner segment is the entity's GAMEID, the name it is called by\n * everywhere else (4.4). Its internal id is accepted for this release and\n * earns a `diagnostic` naming the address to move to; the next lockstep\n * release refuses it. */\n getProperty(path: string): ScalarValue {\n this.assertOpen();\n const found = this.resolvePath(propertyPath(path));\n const value = found.kind === \"host\" ? found.host.resolver.get(found.name)\n : found.kind === \"scope\" ? this.internals.registry.get(found.token, found.name)\n : ownValue(found.own, found.name) ?? ownValue(found.shared, found.name);\n if (value === undefined) throw new StoryletError(`no property at \"${path}\"`);\n return value;\n }\n\n setProperty(path: string, value: ScalarValue): void {\n this.assertOpen();\n const found = this.resolvePath(propertyPath(path));\n checkHostWrite(this.internals, path, value);\n if (found.kind === \"host\") {\n if (!found.host.set) throw new StoryletError(`@${found.host.spec.token} is read-only here: the host bound no write`);\n found.host.set(found.name, value, true);\n return;\n }\n if (found.kind === \"scope\") {\n this.internals.registry.set(found.token, found.name, value, { host: true });\n return;\n }\n const bag = ownValue(found.own, found.name) !== undefined ? found.own\n : ownValue(found.shared, found.name) !== undefined ? found.shared\n : undefined;\n if (bag === undefined) throw new StoryletError(`no property at \"${path}\"`);\n // A host write: silent under the firing rule (no subscriber feedback\n // loop), visible to the bag's audit hook, and flagged HOST so a\n // `writable: false` does not refuse the game its own value.\n bag.set(found.name, value, { silent: true, reason: \"host setProperty\", host: true });\n }\n\n private resolvePath(path: string):\n | { kind: \"host\"; host: HostScopeState; name: string }\n | { kind: \"scope\"; token: string; name: string }\n | { kind: \"bag\"; own?: StateBag; shared?: StateBag; name: string } {\n const parts = path.split(\".\");\n const host = parts.length === 2 ? this.internals.hostScopes.get(parts[0]!) : undefined;\n if (host !== undefined) return { kind: \"host\", host, name: parts[1]! };\n if (parts.length === 2 && parts[0] !== \"story\" && this.internals.registry.has(parts[0]!)) {\n return { kind: \"scope\", token: parts[0]!, name: parts[1]! };\n }\n if (parts.length === 2 && parts[0] === \"story\") {\n return { kind: \"bag\", own: this.stores.story, shared: this.internals.shared.story, name: parts[1]! };\n }\n if (parts.length === 3 && isOwnedScope(parts[0])) {\n const kind = parts[0];\n const [, segment, name] = parts as unknown as [string, string, string];\n const owner = ownerOrThrow(this.internals, kind, segment, name);\n if (owner.legacy && this.tracing) {\n this.emit({ type: \"diagnostic\", where: \"property address\", message: legacyAddressMessage(this.internals, kind, segment, name) });\n }\n const own = this.stores[kind].get(owner.id);\n const shared = this.internals.shared[kind].get(owner.id);\n if (own === undefined && shared === undefined) throw new StoryletError(`no ${kind} store \"${segment}\"`);\n return { kind: \"bag\", ...(own !== undefined ? { own } : {}), ...(shared !== undefined ? { shared } : {}), name };\n }\n throw new StoryletError(`bad property path \"${path}\"`);\n }\n\n // --- persistence (schema 4) -------------------------------------------------\n\n /**\n * This flow's DURABLE HALF, the player's pocket (ruling H): every per-flow\n * `durable` property's value by its address, and every per-flow durable\n * one-shot this flow has spent, by card gameId. A later run opens the\n * player's flow with it, `openFlow(id, { durable })`; the shared half is\n * `engine.saveDurable()`.\n */\n saveDurable(): DurableSave {\n this.assertOpen();\n const { bundle, durable } = this.internals;\n return structuredClone({\n schema: DURABLE_SCHEMA,\n content: bundle.content,\n values: durableValues(this.stores, durable.flow),\n spent: durable.flowCards\n .filter((e) => this.cooldowns[e.card.id] === Number.MAX_SAFE_INTEGER)\n .map((e) => effectiveGameId(e.card)).sort(),\n });\n }\n\n /** @internal - write a planned pocket into this freshly opened flow\n * (openFlow's `durable`, which has already checked and planned it). Its\n * bags hold their defaults, so only what the plan carries is written. */\n writeDurable(plan: DurablePlan): void {\n for (const [prop, value] of plan.values) putDurable(durableBag(this.stores, prop), prop.decl.name, value);\n for (const cardId of plan.cards) this.cooldowns[cardId] = Number.MAX_SAFE_INTEGER;\n }\n\n /** @internal - this flow's blob: inside the engine's envelope without its\n * properties (the registry has them), or parked whole by saveFlow.\n * Engine-side plumbing; a host parks a flow with `engine.saveFlow`. Keyed\n * by Game ID (save version 3; see savedCard): the turns by box, the\n * cooldowns by card, the board by hand with its cards, in the order the\n * flow holds them. */\n snapshot(withProps: boolean): FlowSave {\n const internals = this.internals;\n const boxKey = (id: string): string => {\n const box = internals.boxesById.get(id);\n return box !== undefined ? effectiveGameId(box) : id;\n };\n const handKey = (id: string): string => {\n const found = internals.handsById.get(id);\n return found !== undefined ? effectiveGameId(found.hand) : id;\n };\n return {\n ...(withProps ? { props: partitionValues(this.stores) } : {}),\n turns: Object.fromEntries([...this.turnCounts].map(([id, turn]) => [boxKey(id), turn])),\n prng: this.prng.state(),\n cooldowns: Object.fromEntries(Object.entries(this.cooldowns).map(([id, turn]) => [cardKey(internals, id), turn])),\n board: Object.fromEntries([...this.boardContents].map(([id, cards]) => [handKey(id), cards.map((c) => cardKey(internals, c))])),\n playLog: this.playLog,\n };\n }\n\n /** @internal - restore a freshly opened flow from its blob (loadGame, and\n * openFlow's `restore`, both of which clean the blob first). Orphaned keys\n * (deleted entities) drop; new declarations keep defaults. Engine-side\n * plumbing that skips the checks a restore through `openFlow` makes, which\n * is why a host resumes a flow through `openFlow` and never through this. */\n restore(saved: FlowSave): void {\n if (saved.props !== undefined) loadPartition(this.stores, saved.props);\n this.turnCounts = new Map(this.internals.bundle.boxes.map((b) => [b.id, 0]));\n for (const [boxId, turn] of Object.entries(saved.turns ?? {})) {\n if (this.turnCounts.has(boxId)) this.turnCounts.set(boxId, turn);\n }\n this.prng = makePrng(saved.prng);\n this.cooldowns = saved.cooldowns ?? {};\n this.playLog = saved.playLog ?? [];\n this.rebuildPlayIndex();\n this.boardContents = new Map(Object.entries(saved.board ?? {})\n .filter(([handId]) => this.internals.handsById.has(handId))\n .map(([handId, ids]) => [handId, ids.filter((id) => this.internals.cardsById.has(id))]));\n for (const handId of this.internals.handsById.keys()) {\n if (!this.boardContents.has(handId)) this.boardContents.set(handId, []);\n }\n }\n}\n","// ---------------------------------------------------------------------------\n// The Storylet Engine version, and the one check against it.\n//\n// A game, a tool or an extension that ships apart from the engine checks that\n// the Storylet Engine it finds is one it was built for. Unity's and npm's\n// packages can declare a version range, but Unreal's .uplugin and Godot's\n// plugin.cfg can't, and no manifest is readable by game code in a shipped\n// build, so the check that works on every engine runs in code:\n// STORYLET_ENGINE_VERSION says what this runtime is, and versionInRange tests\n// it. The same pair as Patterplay's (PATTERPLAY_VERSION, versionInRange), held\n// to the same corpus cases, so both families answer alike.\n// ---------------------------------------------------------------------------\n\n/** This runtime's version, as in its package manifest. Written by `npm run bump:play`; never edit it by hand. */\nexport const STORYLET_ENGINE_VERSION = \"1.0.2\";\n\ntype Parsed = { core: [string, string, string]; pre: string[] };\n\nconst SEMVER = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$/;\n\nfunction parse(v: string): Parsed | null {\n const m = typeof v === \"string\" ? SEMVER.exec(v) : null;\n if (!m) return null;\n return { core: [m[1]!, m[2]!, m[3]!], pre: m[4] ? m[4].split(\".\") : [] };\n}\n\n/** Two runs of digits compared as whole numbers of any length: leading zeros dropped, then the longer is larger. */\nfunction compareDigits(x: string, y: string): number {\n x = x.replace(/^0+(?=\\d)/, \"\");\n y = y.replace(/^0+(?=\\d)/, \"\");\n if (x.length !== y.length) return x.length < y.length ? -1 : 1;\n return x === y ? 0 : x < y ? -1 : 1;\n}\n\n/** Semantic-version precedence: negative when a comes first, positive when b does, 0 when they're equal. */\nfunction compare(a: Parsed, b: Parsed): number {\n for (let i = 0; i < 3; i++) {\n const c = compareDigits(a.core[i]!, b.core[i]!);\n if (c) return c;\n }\n // A prerelease comes before its release.\n if (!a.pre.length || !b.pre.length) return b.pre.length - a.pre.length;\n for (let i = 0; i < Math.min(a.pre.length, b.pre.length); i++) {\n const x = a.pre[i]!, y = b.pre[i]!;\n if (x === y) continue;\n const xn = /^\\d+$/.test(x), yn = /^\\d+$/.test(y);\n if (xn && yn) {\n const c = compareDigits(x, y);\n if (c) return c;\n continue;\n }\n if (xn !== yn) return xn ? -1 : 1; // a numeric identifier comes before a word\n return x < y ? -1 : 1;\n }\n return a.pre.length - b.pre.length;\n}\n\n/**\n * Whether `version` is at least `min` and below `maxExclusive`, by semantic-version precedence (build metadata\n * after `+` is ignored, and a prerelease comes before its release). False when any of the three isn't a version.\n * Something built for the Storylet Engine 0.15 checks `versionInRange(STORYLET_ENGINE_VERSION, \"0.15.0\", \"0.16.0\")`.\n */\nexport function versionInRange(version: string, min: string, maxExclusive: string): boolean {\n const v = parse(version), lo = parse(min), hi = parse(maxExclusive);\n if (!v || !lo || !hi) return false;\n return compare(v, lo) >= 0 && compare(v, hi) < 0;\n}\n","// ---------------------------------------------------------------------------\n// describeBundle - the bundle inspector's runtime half (design/engine-runtimes.md\n// section 2, piece 6).\n//\n// A BUNDLE-level API, deliberately NOT a session method: it answers the\n// integrator's question - \"I dropped a .storyletsc into my project, what may\n// my game code call?\" - from the imported asset alone, with no session, no\n// state and no game running. That makes the boundary rule (design 4) visible:\n// hands are what deal() takes, tag groups + tags are what peek() criteria are\n// drawn from, declared properties are what expressions read and a host may\n// set. Card lists are deliberately absent: cards are the engine's business,\n// counts are the orientation an integrator needs.\n//\n// Everything is in bundle order, so the description is deterministic and two\n// runtimes render the same rows in the same sequence. The property scopes are\n// the static twin of session.listProperties(): the same stores, in the same\n// order, before anything is instantiated (a hand instance carries its\n// template's declarations, exactly as the session's hand bags do).\n// ---------------------------------------------------------------------------\n\nimport { defaultFor } from \"@wildwinter/scoperegistry\";\nimport { bundleHostScopes, effectiveGameId, groupsOfBox, isHoleRef } from \"@storylet-studio/model\";\nimport type {\n Box, Bundle, Expression, Hand, HostScopeDecl, PropertyDecl, PropertyType, ScalarValue,\n} from \"@storylet-studio/model\";\n\n/** What bundle this is: the staleness/identity triple plus the schema tag. */\nexport interface BundleIdentity {\n /** The bundle schema tag (\"storylets/bundle@2\"; \"@1\" from before host\n * scopes, \"@0\" from before the project map). */\n schema: string;\n /** content.project - the project name a save must agree with. */\n project: string;\n /** content.version - the authored bundle version. */\n version: string;\n /** content.hash - hash32 over the canonical source shards (schema 2.8). */\n hash: string;\n /** \"full\" | \"stripped\": whether authoring metadata (titles) survived. */\n metadata: string;\n}\n\n/** One hole this hand fills from a property rather than with a tag: the hand\n * MOVES when that property is written (design/engine-server.md 4.6). `group`\n * is the tag group's gameId, `from` the reference exactly as authored. */\nexport interface MovableHole {\n group: string;\n from: string;\n}\n\n/** One hand: the deal() surface. `gameId` is the name deal() is called with. */\nexport interface HandSummary {\n gameId: string;\n title?: string;\n /** The owning box's gameId (peek's first argument for the same stock). */\n box: string;\n /** The effective slot cap: the hand's override, else its template's or\n * rule's, else \"unbounded\". */\n slots: number | \"unbounded\";\n /** The hand template's gameId; absent for a standalone (inline-rule) hand. */\n template?: string;\n /**\n * The holes filled from a property, in bundle order. Absent when the hand\n * has none, which is the ordinary case.\n *\n * Reported because it is the one thing about a hand an integrator cannot see\n * from its name: a movable hole means writing that property MOVES the hand,\n * so it is the difference between a fixed kiosk and a performer who walks\n * about. `setProperty` is the whole verb; there is no other.\n */\n movable?: MovableHole[];\n}\n\n/** One tag group and its tags, by gameId: the peek() criteria surface (a\n * criteria entry is `{ [group gameId]: tag gameId }`). */\nexport interface TagGroupSummary {\n gameId: string;\n tags: string[];\n}\n\n/** One box: identity, its ranking policy, its tag groups, and counts. */\nexport interface BoxSummary {\n gameId: string;\n title?: string;\n /** The box is on the project map (design/project-map-contract.md 3.7): it\n * may name the map's group in peek criteria beside its own `tagGroups`,\n * which list the box's OWN groups only. Absent is \"not on the map\". */\n usesMap?: true;\n /** The only per-box ranking policy (Reboot 2.2). */\n ranking: { specificity: boolean };\n /** Present on a box whose clock the GAME moves, so a play advances nothing:\n * an integrator reading a bundle needs it to know which boxes their host\n * must tick. With `seconds`, a TIMED box (design/engine-server.md 4.8) and\n * how long one of its turns lasts; without, the game's own beat (a\n * calendar). Absent is the ordinary box, whose clock a play moves. */\n turn?: { seconds?: number };\n /** How many cards in this box are DURABLE (design/engine-server.md 4.2):\n * their `redraw: \"never\"` spend outlives the run, and a server has to lift\n * and restore it. A count rather than a list, like every other number here:\n * an integrator needs to know whether this box has any such cards at all,\n * and which ones is the authoring tool's question. Absent when there are\n * none, which is the ordinary bundle. */\n durableCards?: number;\n tagGroups: TagGroupSummary[];\n counts: {\n decks: number;\n cards: number;\n hands: number;\n templates: number;\n tagGroups: number;\n };\n}\n\n/** One declared property: what expressions read and what a host may set. */\nexport interface PropertySummary {\n name: string;\n type: PropertyType;\n default: ScalarValue;\n /** Enum / flags options, where declared. */\n values?: string[];\n /** Declared DURABLE (design/engine-server.md 4.2): the value survives a run,\n * and a server lifts and restores it across one. The engine never reads it;\n * it is reported because it is the difference between a value an integrator\n * may reset and one somebody is going to expect back. Absent = run-scoped. */\n durable?: true;\n purpose?: string;\n}\n\n/** The scope a declaration block belongs to. `tag` declarations compose into\n * @hand for any ask that binds the tag (schema 3.6). `host` is a host scope\n * other than @world (`@player`), its token the `owner`. */\nexport type PropertyScopeKind = \"world\" | \"host\" | \"story\" | \"box\" | \"deck\" | \"hand\" | \"tag\";\n\n/** One scope's declared properties. `owner` is the owning entity's gameId\n * (empty for world / story), or a host scope's token; `box` names its box;\n * `group` names a tag's group. A zone of the project map is a `tag` scope\n * with `group` and NO `box`: it belongs to none. */\nexport interface PropertyScopeSummary {\n scope: PropertyScopeKind;\n owner: string;\n /** A host scope that declares no names, so takes any (`world` or `host`\n * only). Absent for every other scope. */\n opaque?: true;\n box?: string;\n group?: string;\n properties: PropertySummary[];\n}\n\n/**\n * The project map (design/project-map-contract.md 3.7): its group, which boxes\n * are on it, and how much geometry the bundle carries.\n *\n * Counts rather than the geometry itself, which is the same judgement the rest\n * of this file makes: an inspector answers \"what is in here\", and a host that\n * wants the polygons reads `bundle.map.geometry` directly. The geometry counts\n * are zero when the build did not ask for geometry (`export.map`); the group is\n * there regardless, because hands and cards reference it.\n */\nexport interface MapSummary {\n /** The zone group's gameId: the name an opted-in box's peek criteria use. */\n group: string;\n /** Its tags (the zones), by gameId. */\n tags: string[];\n /** The opted-in boxes, by gameId, in bundle order. */\n boxes: string[];\n /** Drawn zones in the carried geometry. */\n zones: number;\n backgrounds: number;\n /** Box gameId -> placed hands standing on the map (design/engine-server.md\n * 4.3): where the kiosks are. Only boxes with a site have a key. */\n sites: Record<string, number>;\n}\n\n/** What a bundle offers a host, read from the asset alone. */\nexport interface BundleDescription {\n identity: BundleIdentity;\n /** Orientation, not inventory: no card lists (Reboot 2.1). */\n totals: {\n boxes: number;\n decks: number;\n cards: number;\n hands: number;\n templates: number;\n tagGroups: number;\n };\n boxes: BoxSummary[];\n /** Every hand in the bundle, box by box: the deal() surface. */\n hands: HandSummary[];\n /** The host scopes in bundle order (world, and any others such as player),\n * story, then per box: the box, its decks, its hands, its tags; then the\n * project map's zones, once. Scopes that declare nothing are omitted (world\n * and story always show, so their absence reads as \"this bundle declares\n * none\"; an undeclared world shows first, empty). */\n properties: PropertyScopeSummary[];\n /** The project map, when the bundle has one. Absent is the bundle with no\n * map at all. */\n map?: MapSummary;\n}\n\nconst summarise = (decls: readonly (PropertyDecl | HostScopeDecl)[]): PropertySummary[] =>\n decls.map((d: PropertyDecl) => ({\n name: d.name,\n type: d.type,\n default: defaultFor(d),\n ...(d.values !== undefined ? { values: d.values } : {}),\n ...(d.durable === true ? { durable: true as const } : {}),\n ...(d.purpose !== undefined ? { purpose: d.purpose } : {}),\n }));\n\n/** How many cards in a box are durable: the card's own flag, else its deck's\n * (design/engine-server.md 4.2, the same inheritance `shared` has). */\nconst durableCardCount = (box: Box<Expression>): number =>\n box.decks.reduce((n, deck) =>\n n + deck.cards.filter((card) => (card.durable ?? deck.durable) === true).length, 0);\n\n/** A hand's declared @hand state: a template instance inherits its template's\n * declarations, a standalone hand declares its own (schema 2.6) - the same\n * rule the session's hand bags are built on. */\nconst handDecls = (hand: Hand<Expression>, box: Box<Expression>): PropertyDecl[] => {\n if (hand.template !== undefined) {\n return box.handTemplates.find((t) => t.id === hand.template)?.properties ?? [];\n }\n return hand.properties ?? [];\n};\n\n/** The hand's movable holes, in the bundle's own key order: every `chosen` /\n * rule-binding value that is a property reference rather than a tag (4.6).\n * The group is looked up where the engine looks it up: the box's own groups\n * and, for a box on the project map, the map's group, so the roaming\n * character whose hole names a zone is reported rather than lost (3.7). A\n * group id neither place holds is a bundle the engine refuses or cannot\n * bind, and is skipped rather than reported under its raw id: the\n * description speaks gameIds throughout. */\nconst movableHoles = (bundle: Bundle, hand: Hand<Expression>, box: Box<Expression>): MovableHole[] => {\n const filled = hand.template !== undefined ? hand.chosen : hand.rule?.bindings;\n const out: MovableHole[] = [];\n for (const [groupId, value] of Object.entries(filled ?? {})) {\n if (!isHoleRef(value)) continue;\n const group = groupsOfBox(bundle, box).find((g) => g.id === groupId);\n if (group === undefined) continue;\n out.push({ group: effectiveGameId(group), from: value });\n }\n return out;\n};\n\n/** The effective slot cap, resolved the way the session resolves capacity. */\nconst handSlots = (hand: Hand<Expression>, box: Box<Expression>): number | \"unbounded\" => {\n if (hand.slots !== undefined) return hand.slots;\n const declared = hand.template !== undefined\n ? box.handTemplates.find((t) => t.id === hand.template)?.slots\n : hand.rule?.slots;\n return declared === undefined ? \"unbounded\" : declared;\n};\n\n/** Describe a compiled bundle: the callable surface of an imported asset, no\n * session required (design/engine-runtimes.md 2, piece 6). Bundle order\n * throughout; the same shape every runtime returns. */\nexport function describeBundle(bundle: Bundle): BundleDescription {\n const boxes: BoxSummary[] = [];\n const hands: HandSummary[] = [];\n const hosts = bundleHostScopes(bundle);\n const properties: PropertyScopeSummary[] = [\n ...(hosts.some((h) => h.token === \"world\") ? [] : [{ scope: \"world\" as const, owner: \"\", properties: [] }]),\n ...hosts.map((h): PropertyScopeSummary => ({\n ...(h.token === \"world\" ? { scope: \"world\" as const, owner: \"\" } : { scope: \"host\" as const, owner: h.token }),\n ...(h.declarations === undefined ? { opaque: true as const } : {}),\n properties: summarise(h.declarations ?? []),\n })),\n { scope: \"story\", owner: \"\", properties: summarise(bundle.story.properties) },\n ];\n const totals = { boxes: 0, decks: 0, cards: 0, hands: 0, templates: 0, tagGroups: 0 };\n\n for (const box of bundle.boxes) {\n const boxGameId = effectiveGameId(box);\n const cards = box.decks.reduce((n, deck) => n + deck.cards.length, 0);\n boxes.push({\n gameId: boxGameId,\n ...(box.title !== undefined ? { title: box.title } : {}),\n ...(box.usesMap === true ? { usesMap: true as const } : {}),\n ranking: { specificity: box.ranking.specificity },\n ...(box.turn !== undefined ? { turn: box.turn.seconds !== undefined ? { seconds: box.turn.seconds } : {} } : {}),\n ...(durableCardCount(box) > 0 ? { durableCards: durableCardCount(box) } : {}),\n tagGroups: box.tagGroups.map((group) => ({\n gameId: effectiveGameId(group),\n tags: group.tags.map((tag) => effectiveGameId(tag)),\n })),\n counts: {\n decks: box.decks.length,\n cards,\n hands: box.hands.length,\n templates: box.handTemplates.length,\n tagGroups: box.tagGroups.length,\n },\n });\n totals.boxes += 1;\n totals.decks += box.decks.length;\n totals.cards += cards;\n totals.hands += box.hands.length;\n totals.templates += box.handTemplates.length;\n totals.tagGroups += box.tagGroups.length;\n\n for (const hand of box.hands) {\n const template = hand.template !== undefined\n ? box.handTemplates.find((t) => t.id === hand.template)\n : undefined;\n const movable = movableHoles(bundle, hand, box);\n hands.push({\n gameId: effectiveGameId(hand),\n ...(hand.title !== undefined ? { title: hand.title } : {}),\n box: boxGameId,\n slots: handSlots(hand, box),\n ...(template !== undefined ? { template: effectiveGameId(template) } : {}),\n ...(movable.length > 0 ? { movable } : {}),\n });\n }\n\n // The property scopes, in the session's store order: box, decks, hands,\n // tags. Empty declaration blocks are dropped (nothing to read or set).\n const push = (scope: PropertyScopeKind, owner: string, decls: PropertyDecl[], group?: string): void => {\n if (decls.length === 0) return;\n properties.push({\n scope, owner, box: boxGameId, ...(group !== undefined ? { group } : {}),\n properties: summarise(decls),\n });\n };\n push(\"box\", boxGameId, box.properties);\n for (const deck of box.decks) push(\"deck\", effectiveGameId(deck), deck.properties);\n for (const hand of box.hands) push(\"hand\", effectiveGameId(hand), handDecls(hand, box));\n for (const group of box.tagGroups) {\n for (const tag of group.tags) {\n push(\"tag\", effectiveGameId(tag), tag.properties ?? [], effectiveGameId(group));\n }\n }\n }\n\n // The project map's zones, ONCE and after every box, whichever boxes use\n // them: the same order the engine's value bags are built in. A `tag` scope\n // with no `box`, because a zone belongs to none.\n const map = bundle.map;\n if (map !== undefined) {\n const group = effectiveGameId(map.group);\n for (const tag of map.group.tags) {\n const decls = tag.properties ?? [];\n if (decls.length > 0) properties.push({ scope: \"tag\", owner: effectiveGameId(tag), group, properties: summarise(decls) });\n }\n totals.tagGroups += 1;\n }\n\n return {\n identity: {\n schema: bundle.schema,\n project: bundle.content.project,\n version: bundle.content.version,\n hash: bundle.content.hash,\n metadata: bundle.metadata,\n },\n totals,\n boxes,\n hands,\n properties,\n ...(map !== undefined ? {\n map: {\n group: effectiveGameId(map.group),\n tags: map.group.tags.map((tag) => effectiveGameId(tag)),\n boxes: bundle.boxes.filter((box) => box.usesMap === true).map((box) => effectiveGameId(box)),\n zones: map.geometry?.zones.length ?? 0,\n backgrounds: map.geometry?.backgrounds?.length ?? 0,\n sites: Object.fromEntries(Object.entries(map.geometry?.sites ?? {}).map(([box, sites]) => [box, sites.length])),\n },\n } : {}),\n };\n}\n","// ---------------------------------------------------------------------------\n// Field read helpers: a box's card and outcome fields, and any node's Storylet\n// extension values, as a host reads them.\n//\n// The engine hands a card's fields (and an outcome's) to the host exactly as\n// the bundle wrote them: sparse, a field the card leaves out simply absent.\n// That is deliberate, because \"the card sets nothing\" and \"the card sets the\n// default\" are different facts a host may care about. Most hosts want the\n// filled-in picture, though, and the declared defaults ride in the bundle, so\n// these pure helpers do that resolution once rather than in every game.\n//\n// Patter's gameData helpers (gameDataFields, gameDataValue, effectiveGameData)\n// are the same idea, by the same rule (patterkit design/family-parity-2026-10.md\n// item 7), and every Storylet Engine runtime carries these four.\n//\n// Extension values follow the same rule (patterkit design/proposals/\n// patter-storylet-extensions.md, \"At runtime\"): raw on the node, declarations\n// with every default resolved in the bundle, and the SAME two helpers fill\n// them in, given `extensionDecls`. `extensionsOf` reads the values of a node\n// no view carries (a deck, a hand, a tag), by the address a game already uses.\n// ---------------------------------------------------------------------------\n\nimport type { Bundle, ExtensionFieldDecl, FieldDecl, NodeExtensions, ScalarValue } from \"@storylet-studio/model\";\n\ntype BundleBoxes = Pick<Bundle, \"boxes\">;\n\nconst boxOf = (bundle: BundleBoxes, box: string) =>\n bundle.boxes.find((b) => b.gameId === box) ?? bundle.boxes.find((b) => b.id === box);\n\nconst own = (record: object, name: string): boolean => Object.prototype.hasOwnProperty.call(record, name);\n\n/** A box's card template, by its gameId (or id). Empty when the box declares\n * none or is not in the bundle. */\nexport function fieldDecls(bundle: BundleBoxes, box: string): FieldDecl[] {\n return boxOf(bundle, box)?.fields ?? [];\n}\n\n/** A box's outcome fields, by its gameId (or id). Empty when it declares none. */\nexport function outcomeFieldDecls(bundle: BundleBoxes, box: string): FieldDecl[] {\n return boxOf(bundle, box)?.outcomeFields ?? [];\n}\n\n/** A declaration as the two helpers below read it: a field's, or an extension field's. */\ninterface Declared<V> { readonly name: string; readonly default?: V }\n\n/** One field's effective value: the card's (or outcome's) own when it sets\n * one, else the field's declared default; undefined for a name declared\n * nowhere and set nowhere. */\nexport function fieldValue<V = ScalarValue>(\n decls: readonly Declared<V>[], values: Readonly<Record<string, V>> | undefined, name: string,\n): V | undefined {\n if (values !== undefined && own(values, name)) return values[name];\n // The declaration's own default is the bundle's, shared by every card that\n // leaves the field out: a structured one is handed out as a copy, or a game\n // changing it would change it for all of them (the hands-on review, 2026-10,\n // foundation 6).\n const fallback = decls.find((f) => f.name === name)?.default;\n return fallback !== null && typeof fallback === \"object\" ? structuredClone(fallback) : fallback;\n}\n\n/** Every declared field resolved (the value set, else the default), plus any\n * key set with no declaration, kept as it is. */\nexport function effectiveFields<V = ScalarValue>(\n decls: readonly Declared<V>[], values: Readonly<Record<string, V>> | undefined,\n): Record<string, V> {\n const out: Record<string, V> = {};\n for (const f of decls) {\n const v = fieldValue(decls, values, f.name);\n if (v !== undefined) out[f.name] = v;\n }\n for (const [k, v] of Object.entries(values ?? {})) if (!own(out, k)) out[k] = v;\n return out;\n}\n\n/** One extension's declarations for a node kind (`card`, `outcome`, `box`,\n * `deck`, `tagGroup`, `tag`, `handTemplate`, `hand`, `project`), every default\n * resolved. Empty when the bundle has no such extension or it declares nothing\n * for the kind. */\nexport function extensionDecls(bundle: Pick<Bundle, \"extensions\">, extension: string, kind: string): ExtensionFieldDecl[] {\n const ext = bundle.extensions !== undefined && own(bundle.extensions, extension) ? bundle.extensions[extension] : undefined;\n return ext !== undefined && own(ext.fields, kind) ? ext.fields[kind] ?? [] : [];\n}\n\n/**\n * A node's extension values, by extension id, raw; `{}` when it has none or the\n * address names nothing. For the nodes no view carries, by the names a game\n * already holds: `project` (no address); `box`, `deck`, `card` and `hand` by\n * gameId; `handTemplate` as \"box/template\"; `outcome` as \"card/outcome\";\n * `tagGroup` and `tag` as \"box/name\", or the bare name for the project map's.\n */\nexport function extensionsOf(bundle: Pick<Bundle, \"boxes\" | \"map\" | \"extensions\">, kind: string, address = \"\"): NodeExtensions {\n // The node's values themselves, read-only by contract, as the engine's views\n // hand them out (a family ruling, 2026-10-28): the record round them is new.\n const values = (node: { extensions?: NodeExtensions } | undefined): NodeExtensions => ({ ...(node?.extensions ?? {}) });\n const [first, second] = address.includes(\"/\") ? [address.slice(0, address.indexOf(\"/\")), address.slice(address.indexOf(\"/\") + 1)] : [address, undefined];\n const named = <T extends { id: string; gameId?: string }>(items: readonly T[], name: string): T | undefined =>\n items.find((x) => (x.gameId ?? x.id) === name);\n const cards = () => bundle.boxes.flatMap((b) => b.decks.flatMap((d) => d.cards));\n switch (kind) {\n case \"project\": {\n const out: NodeExtensions = {};\n for (const [id, ext] of Object.entries(bundle.extensions ?? {})) if (ext.project !== undefined) out[id] = ext.project;\n return out;\n }\n case \"box\": return values(named(bundle.boxes, address));\n case \"deck\": return values(named(bundle.boxes.flatMap((b) => b.decks), address));\n case \"card\": return values(named(cards(), address));\n case \"outcome\": return second === undefined ? {} : values(named(named(cards(), first)?.outcomes ?? [], second));\n case \"hand\": return values(named(bundle.boxes.flatMap((b) => b.hands), address));\n case \"handTemplate\": return second === undefined ? {} : values(named(named(bundle.boxes, first)?.handTemplates ?? [], second));\n case \"tagGroup\":\n if (second === undefined) return bundle.map !== undefined && (bundle.map.group.gameId ?? bundle.map.group.id) === first ? values(bundle.map.group) : {};\n return values(named(named(bundle.boxes, first)?.tagGroups ?? [], second));\n case \"tag\":\n if (second === undefined) return values(bundle.map !== undefined ? named(bundle.map.group.tags, first) : undefined);\n return values(named((named(bundle.boxes, first)?.tagGroups ?? []).flatMap((g) => g.tags), second));\n default: return {};\n }\n}\n","// ---------------------------------------------------------------------------\n// The storylets state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry (design/engine-runtimes.md 3.4 is the design of\n// record). The kernel does the work - push-based property logging on the\n// PropertyBag audit hook, so a write logs the moment it lands, plus a diff of\n// everything that has no audit hook - and this supplies the two product-shaped\n// pieces: which bags to watch, and the non-property state (turns / cooldowns /\n// board) as flattened paths.\n//\n// The core lived here, marked \"moves into the kernel package wholesale when the\n// vendor-sync slice lands\". It has. Patterplay's logger, which diffed save\n// snapshots and so could not see a value that changed and changed back, is an\n// adapter over the same core now.\n//\n// Flattened path scheme:\n// world.x / story.x / box.<gameId>.x / deck.<gameId>.x / hand.<gameId>.x /\n// value.<tagGameId>.x, and value.<boxGameId>/<tagGameId>.x for a tag gameId\n// two boxes share (4.4)\n// turn:<boxGameId> per-box clocks\n// cooldown:<cardGameId> next-eligible turns\n// board:<handGameId> hand contents (card Game IDs, dealt order)\n// (Game IDs as the save keys them from version 3; internal ids before.)\n// Line format: `${label}${path}: ${from} -> ${to}`, `<unset>` for undefined.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@storylet-studio/runtime\";\nimport type { FlowSave, ScalarValue } from \"@storylet-studio/model\";\nimport {\n createStateLogger as createKernelStateLogger, diffState,\n} from \"@wildwinter/scoperegistry\";\nimport type {\n StateSnapshot, StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions,\n} from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @storylet-studio/play-helpers should not have to care that they moved.\nexport { createKernelStateLogger, diffState };\nexport type { StateSnapshot, StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions };\n\n/** The full flattened snapshot of ONE FLOW's view - the shared partitions\n * plus that flow's own - plus its turns / cooldowns / board. @world is not\n * here for the same reason it is not in a save envelope: the host owns that\n * container and mounts/saves it itself (createWorldContainer).\n *\n * Taken off the BAGS, which is what a save envelope is made of, rather than\n * off the envelope itself. The two used to be interchangeable; from 4.4 they\n * are not, because a property ADDRESS names its owner by gameId while the\n * envelope stays keyed by internal id (a save has to survive a rename). The\n * bags carry the address, so reading them is what keeps this snapshot and\n * the live logger's lines in ONE path space - which is the invariant the\n * whole diff rests on. */\nexport function snapshotState(engine: Engine, flow: Flow): StateSnapshot {\n const out: StateSnapshot = {};\n // Shared under the flow's own: names are disjoint (shared XOR per-flow by\n // declaration), so one path space holds both without collision.\n for (const { bag } of [...engine.listBags(), ...flow.listBags()]) {\n for (const row of bag.rows()) {\n if (row.value !== undefined) out[row.path] = row.value as ScalarValue;\n }\n }\n Object.assign(out, extraState(flowBlob(engine, flow.id)));\n return out;\n}\n\n/** ONE flow's blob, or undefined for a flow that is not open (a just-closed\n * one: no paths). `saveFlow` clones that flow alone, where reading it out of\n * `saveGame()` cloned the whole engine, every flow and every property, on\n * each capture. */\nfunction flowBlob(engine: Engine, id: string): FlowSave | undefined {\n return engine.getFlow(id) !== undefined ? engine.saveFlow(id) : undefined;\n}\n\n/** The storylets path-provider adapter for non-property state (design 3.4):\n * one flow's turns / cooldowns / board as flattened paths, off its blob\n * (absent for a just-closed flow: no paths). */\nfunction extraState(saved: FlowSave | undefined): StateSnapshot {\n const out: StateSnapshot = {};\n if (saved === undefined) return out;\n for (const [boxId, turn] of Object.entries(saved.turns)) out[`turn:${boxId}`] = turn;\n for (const [cardId, at] of Object.entries(saved.cooldowns)) out[`cooldown:${cardId}`] = at;\n for (const [handId, cards] of Object.entries(saved.board)) out[`board:${handId}`] = [...cards];\n return out;\n}\n\n/** The storylets state logger: the kernel core mounted on the SHARED bags\n * (engine.listBags()) and one flow's own (flow.listBags()) - the same\n * prefixes, one path space, names disjoint - plus the flow's turns /\n * cooldowns / board adapter. A host that wants @world lines mounts its\n * world container's bag through createKernelStateLogger itself. */\nexport function createStateLogger(engine: Engine, flow: Flow, opts: StateLoggerOptions = {}): StateLogger {\n // By NAME, not by handle: loadGame rebuilds every flow and the handle we\n // were given goes inert; capture()'s re-mount picks up the rebuilt one.\n const id = flow.id;\n const live = (): Flow | undefined => engine.getFlow(id);\n return createKernelStateLogger({\n // A BagMount's `prefix` (\"story\", \"deck.<id>\") is the engine's label for the mount;\n // the kernel composes paths from the BAG's own pathPrefix (\"story.\", \"deck.<id>.\")\n // and needs none passed. Same strings, one owner.\n mounts: () => [...engine.listBags(), ...(live()?.listBags() ?? [])].map(({ bag }) => ({ bag })),\n extra: () => extraState(flowBlob(engine, id)),\n }, opts);\n}\n","// ---------------------------------------------------------------------------\n// Save-file plumbing over the .storyletsave file (storylets/savefile@1): the\n// HOST's file - the engine's envelope (storylets/save@3; @2 and @1 still read) plus,\n// when the host keeps one, its @world container. That is\n// \"host saves its container once, each engine saves its own envelope\"\n// (design/flows.md) folded into one file for the single-host case. These\n// helpers are the string boundary - a foreign or malformed blob throws\n// rather than corrupting a run.\n// ---------------------------------------------------------------------------\n\nimport { SAVEFILE_SCHEMA, SAVE_SCHEMA, SAVE_SCHEMA_V1, SAVE_SCHEMA_V2 } from \"@storylet-studio/model\";\nimport type { LoadReport, PropertyBag, SaveFile } from \"@storylet-studio/model\";\nimport type { Engine } from \"@storylet-studio/runtime\";\nimport type { WorldContainer } from \"./world.js\";\n\n/** The current engine state (and the host's @world values, if given) as\n * pretty-printed .storyletsave JSON. */\nexport function serializeState(engine: Engine, world?: PropertyBag): string {\n return JSON.stringify(saveState(engine, world), null, 2);\n}\n\n/**\n * Capture the whole engine (and the host's @world values, if it keeps any) as\n * the tagged save-file OBJECT.\n *\n * Four verbs, in Patterplay's pairing (`patter` play-helpers `save.ts`, and\n * the same in all four of its runtimes): saveState / loadState work on the\n * PARSED object, serializeState / deserializeState work on TEXT.\n *\n * This reference had a different shape until 2026-08-29 - `deserializeState`\n * parsed and did not restore, `loadState` took text - so one name meant two\n * things across the four Storylets runtimes, and neither matched the family.\n * Godot and Unreal already had Patter's shape; these two were brought to it.\n */\nexport function saveState(engine: Engine, world?: PropertyBag): SaveFile {\n return {\n schema: SAVEFILE_SCHEMA,\n engine: engine.saveGame(),\n ...(world !== undefined ? { world } : {}),\n };\n}\n\n/** What a load may be told to do besides restoring. */\nexport interface LoadStateOptions {\n /** Handed the engine's LoadReport for this file as the load happens: what\n * the save cost against the running build (cards evicted, properties\n * dropped, defaulted or retyped), the same report `engine.loadGame` and\n * `engine.previewLoad` return. A callback rather than a return value\n * because these functions already return the file's @world values. */\n onReport?: (report: LoadReport) => void;\n /** The host's @world container (`createWorldContainer`, or anything with\n * the same `values` and `load`). Given, the file's @world values go into it\n * BEFORE the engine loads, so the load's check of each held card reads the\n * saved world rather than the one the game had; a refused load puts the\n * container back as it was. Without it the values are only returned, and\n * the game must apply them before loading for the same check. */\n world?: Pick<WorldContainer, \"values\" | \"load\">;\n}\n\n/** Restore a {@link saveState} file into an engine. EVERY FLOW IS REBUILT, so\n * the Flow handles you held before are inert: re-take them with\n * `engine.getFlow(id)`, NOT `engine.openFlow(id)`. `openFlow` on an existing\n * id REPLACES it, which here throws away the hand the file just restored, and\n * the failure lands later, as `play()` refusing a card as \"not dealt\". (The\n * engine's `onReplacedFlow` hook reports exactly this.) Throws on a foreign or malformed\n * file, and the runtime's own project check still applies. Returns the file's\n * @world values, if any. Pass the host's container as `opts.world` and they\n * are applied before the load (see LoadStateOptions.world); the engine itself\n * never touches them. */\nexport function loadState(engine: Engine, file: SaveFile, opts: LoadStateOptions = {}): PropertyBag | undefined {\n if (!file || typeof file !== \"object\"\n || file.schema !== SAVEFILE_SCHEMA\n || (file.engine?.schema !== SAVE_SCHEMA && file.engine?.schema !== SAVE_SCHEMA_V2 && file.engine?.schema !== SAVE_SCHEMA_V1)) {\n throw new Error(`not a storylets save (expected schema \"${SAVEFILE_SCHEMA}\")`);\n }\n const container = opts.world;\n // A copy: a container's values can be its live record, which the load below writes.\n const before = container !== undefined ? structuredClone(container.values()) : undefined;\n if (container !== undefined && file.world !== undefined) container.load(file.world);\n let report: LoadReport;\n try {\n report = engine.loadGame(file.engine);\n } catch (e) {\n if (container !== undefined && before !== undefined) container.load(before);\n throw e;\n }\n opts.onReport?.(report);\n return file.world;\n}\n\n/** Parse + restore a {@link serializeState} string: the TEXT twin of\n * loadState, as Patterplay pairs them. Throws on malformed JSON, a foreign\n * file or a project mismatch. Returns the file's @world values for the host. */\nexport function deserializeState(engine: Engine, json: string, opts: LoadStateOptions = {}): PropertyBag | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"not valid JSON\");\n }\n return loadState(engine, parsed as SaveFile, opts);\n}\n","// ---------------------------------------------------------------------------\n// A single themed tooltip the family controls, replacing native `title`\n// rollovers: those are unstyled OS chrome and appear on the platform's slow\n// (~1s) delay, which is exactly the seam design-language section 4 (\"coherent\n// to the edges\") says not to leave showing.\n//\n// ONE floating bubble, document-DELEGATED: any element carrying a `data-tip`\n// attribute gets it, so a caller just sets `data-tip=\"...\"` anywhere and wires\n// nothing. The bubble anchors above the element (flipping below near the top\n// edge), uses our own snappy delay and the shared panel-enter motion.\n// `initTooltips()` is idempotent, so it is safe to call from more than one\n// place in an app that has several entry points.\n//\n// Lifted from Patterpad's patterpad-surface/web/tooltip.ts, which proved it,\n// with the one app-specific rule (suppressed in Writing View) generalised into\n// the `suppressed` option so the shell knows nothing about either app's modes.\n// ---------------------------------------------------------------------------\n\n// Bold markers: a producer wraps a span in these PRIVATE control chars (U+0002 STX / U+0003 ETX,\n// never present in user text) and the renderer turns it into a <strong>. Lets a data-tip bold part\n// of itself (a name, a \"VO:\" prefix) without any HTML, and each segment is still set via\n// textContent, so there is no injection risk from user content.\nconst B_OPEN = \"\\u0002\";\nconst B_CLOSE = \"\\u0003\";\n\n/** Wrap `s` so it renders bold inside a tooltip (see the marker note above). */\nexport function tipBold(s: string): string { return `${B_OPEN}${s}${B_CLOSE}`; }\n\n/** Render `text` into `host`, turning `\\u0002…\\u0003`-marked spans into <strong>; all set as text (safe). */\nfunction renderTip(host: HTMLElement, text: string): void {\n host.textContent = \"\";\n const re = /\\u0002([^\\u0003]*)\\u0003/g;\n let last = 0;\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n if (m.index > last) host.append(text.slice(last, m.index));\n const strong = document.createElement(\"strong\");\n strong.textContent = m[1] ?? \"\";\n host.append(strong);\n last = m.index + m[0].length;\n }\n if (last < text.length) host.append(text.slice(last));\n}\n\nexport interface TooltipOptions {\n /** Called before every show: return true to keep the bubble down. For a mode\n * that wants no chrome at all (Patterpad's Writing View). */\n suppressed?: () => boolean;\n}\n\nlet inited = false;\nlet checked = false; // the one-shot no-host warning below has been scheduled\nlet suppressed: (() => boolean) | undefined;\nconst SHOW_DELAY = 350; // ms - snappier than the OS title delay, slow enough not to flicker on pass-through\nconst EDGE = 6; // viewport inset so the bubble never touches the window edge\nconst GAP = 7; // px between the anchor and the bubble\n\nlet tip: HTMLDivElement | null = null;\nlet timer = 0;\nlet active: HTMLElement | null = null;\n/** The subject of a rect-anchored tip (see `tipAt`), when there is one. */\nlet activeKey: string | null = null;\n\nfunction ensureEl(): HTMLDivElement {\n if (tip) return tip;\n const el = document.createElement(\"div\");\n el.className = \"tooltip\";\n el.setAttribute(\"role\", \"tooltip\");\n el.hidden = true;\n document.body.appendChild(el);\n tip = el;\n return el;\n}\n\n/** A rectangle in VIEWPORT coordinates, for anchoring a tip to something that is\n * not a DOM element: a shape on a canvas, a run of text inside one. */\nexport interface TipRect { left: number; top: number; width: number; height: number }\n\nconst rectOf = (anchor: HTMLElement | TipRect): TipRect =>\n (anchor instanceof HTMLElement ? anchor.getBoundingClientRect() : anchor);\n\n/** Anchor the (already-visible, so measurable) bubble above the anchor, flipping below near the top. */\nfunction place(anchor: HTMLElement | TipRect): void {\n const t = ensureEl();\n const r = rectOf(anchor);\n const tr = t.getBoundingClientRect();\n let top = r.top - tr.height - GAP;\n if (top < EDGE) top = r.top + r.height + GAP; // not enough room above -> below\n let left = r.left + r.width / 2 - tr.width / 2;\n left = Math.max(EDGE, Math.min(left, window.innerWidth - tr.width - EDGE));\n t.style.left = `${Math.round(left)}px`;\n t.style.top = `${Math.round(top)}px`;\n}\n\nfunction show(anchor: HTMLElement): void {\n const text = anchor.dataset[\"tip\"];\n if (!text) return;\n paint(anchor, text);\n}\n\n/** Fill, place and reveal the bubble. The one path: both the delegated\n * `data-tip` route and `tipAt` end up here, so a canvas tip and a button tip\n * are the same bubble with the same look and the same motion. */\nfunction paint(anchor: HTMLElement | TipRect, text: string): void {\n if (suppressed?.()) return;\n const t = ensureEl();\n // A modal <dialog> (settings, a confirm) renders in the browser TOP LAYER, above every normal-flow\n // element whatever its z-index. If the anchor is inside one, move the bubble into that dialog so it\n // shares the top layer; otherwise keep it on <body>. Re-parenting per show also moves it back out\n // once the modal closes.\n const host: HTMLElement = (anchor instanceof HTMLElement ? anchor.closest<HTMLElement>(\"dialog[open]\") : null)\n ?? document.body;\n if (t.parentElement !== host) host.appendChild(t);\n active = anchor instanceof HTMLElement ? anchor : null;\n renderTip(t, text);\n t.hidden = false;\n t.classList.remove(\"show\"); void t.offsetWidth; // restart the enter animation\n place(anchor); // measure + position now it has content + is visible\n t.classList.add(\"show\");\n}\n\nfunction hide(): void {\n if (timer) { window.clearTimeout(timer); timer = 0; }\n active = null;\n activeKey = null;\n if (tip) { tip.hidden = true; tip.classList.remove(\"show\"); }\n}\n\nfunction schedule(anchor: HTMLElement): void {\n if (anchor === active) return;\n if (timer) window.clearTimeout(timer);\n timer = window.setTimeout(() => show(anchor), SHOW_DELAY);\n}\n\n/**\n * Show a tip for something that has no DOM node of its own: a card on a canvas,\n * a zone, a pin. The caller says WHERE (a viewport rectangle) and WHAT, and\n * identifies the subject with `key`.\n *\n * The key is what makes this usable from a canvas. Re-calling with the same key\n * re-places the bubble WITHOUT restarting the delay, so a tip can follow its\n * shape while the camera moves; calling with a different key starts the wait\n * again, so sweeping the pointer across a dense board does not strobe.\n *\n * The bubble is ordinary DOM, so it holds its size whatever the canvas is\n * zoomed to. That is the point of it: the label it stands in for is the one\n * thing on a canvas that must not shrink with everything else.\n */\nexport function tipAt(key: string, rect: TipRect, text: string): void {\n if (key === activeKey) {\n // Already up for this subject: move it, do not re-animate it.\n if (tip && !tip.hidden) { renderTip(tip, text); place(rect); }\n return;\n }\n // A different subject: down first, then wait again. Leaving the old bubble up\n // during the wait would put one card's name over another card, which is worse\n // than no name at all - and it matches the delegated route, where leaving an\n // element hides the tip before the next one is scheduled.\n hide();\n activeKey = key;\n timer = window.setTimeout(() => { timer = 0; paint(rect, text); }, SHOW_DELAY);\n}\n\n/** Take any tip down: the pointer has left, or a gesture has started. */\nexport function hideTip(): void { hide(); }\n\n/**\n * Show `text` above `anchor` after the usual wait, as if it carried that `data-tip`, without giving it one: a\n * field's help shown when its control takes keyboard focus, where hovering the control should show nothing.\n * Anchored to the element, so a tip inside a modal dialog is drawn in the dialog's top layer. Taken down by the\n * usual blur, pointer, and Escape handling.\n */\nexport function tipFor(anchor: HTMLElement, text: string): void {\n if (text === \"\") return;\n hide();\n timer = window.setTimeout(() => { timer = 0; if (anchor.isConnected) paint(anchor, text); }, SHOW_DELAY);\n}\n\n/**\n * Mount the renderer because something is about to speak the grammar.\n *\n * The themed tooltip is not a feature a host turns on; it is the renderer for a grammar the shell's own\n * components already speak, and `data-tip` with nothing listening draws NOTHING - not the themed\n * bubble, not the platform one. That silence cost Patterpad two releases of a pin with no tooltip, and\n * cost it again when a `title` was converted to `data-tip` in a window that had never mounted this.\n *\n * So `el` calls this whenever it sets a tip. Mounting a delegated listener is the same class of act as\n * `confirmDialog` appending to `document.body`, which this package has always done.\n */\nexport function ensureTooltipHost(): void { initTooltips(); }\n\n/**\n * One deferred check that an app writing `data-tip` ITSELF has a host, warning if not.\n *\n * `ensureTooltipHost` covers anything built through `el`. It cannot cover a `data-tip` written straight\n * into markup or set by hand, which is the case that leaves a whole window of dead tips with no shell\n * component involved - so that one gets a warning rather than silence. Scheduled once, after the\n * current task, so it sees a rendered document rather than an empty one.\n */\nexport function checkTooltipHost(): void {\n if (checked) return;\n checked = true;\n setTimeout(() => {\n if (inited || typeof document === \"undefined\") return;\n if (document.querySelector(\"[data-tip]\")) {\n console.warn(\"app-shell: something set `data-tip` but no tooltip host is mounted, so those rollovers will not draw. Call initTooltips() once per window.\");\n }\n }, 0);\n}\n\nconst tipAncestor = (n: EventTarget | null): HTMLElement | null =>\n (n instanceof Element ? n.closest<HTMLElement>(\"[data-tip]\") : null);\n\n/**\n * Wire the one delegated controller. Mounting happens once; OPTIONS do not.\n *\n * An explicit call is always authoritative about its options, even after something else has already\n * mounted the listener. That ordering is not hypothetical, and Storyletter caught it before it shipped:\n * once components self-mount (see `ensureTooltipHost`), a host's own\n * `initTooltips({ suppressed })` could land second and be dropped by an early return, silently taking\n * a rule like \"no tooltips in Writing View\" with it. Nothing would fail and nothing would show.\n *\n * Components therefore call this with NO arguments: they mount the renderer without claiming the\n * options, and the host keeps the last word on behaviour.\n */\nexport function initTooltips(opts: TooltipOptions = {}): void {\n if (opts.suppressed !== undefined) suppressed = opts.suppressed; // an explicit call always wins\n if (inited) return;\n inited = true;\n\n document.addEventListener(\"pointerover\", (e) => {\n const el = tipAncestor(e.target);\n if (el) schedule(el);\n });\n document.addEventListener(\"pointerout\", (e) => {\n const el = tipAncestor(e.target);\n if (!el) return;\n const to = (e as PointerEvent).relatedTarget;\n if (to instanceof Node && el.contains(to)) return; // still inside the same anchor\n hide();\n });\n // Keyboard parity: reveal on focus, dismiss on blur.\n document.addEventListener(\"focusin\", (e) => { const el = tipAncestor(e.target); if (el) schedule(el); });\n document.addEventListener(\"focusout\", () => hide());\n // It's ANCHORED (not cursor-following), so anything that moves the layout or context dismisses it.\n document.addEventListener(\"pointerdown\", () => hide(), true);\n document.addEventListener(\"scroll\", () => { if (active) hide(); }, true);\n document.addEventListener(\"keydown\", (e) => { if (e.key === \"Escape\") hide(); });\n window.addEventListener(\"blur\", () => hide());\n}\n","// ---------------------------------------------------------------------------\n// `el`: the tiny tag-typed element factory both apps build their imperative,\n// idempotent views with.\n//\n// A SUPERSET of the two apps' prior signatures, so neither rewrites call sites:\n// - Patterpad: el(tag, cls?, text?) (positional)\n// - Storylet Studio: el(tag, { className, text, ... }, ...kids) (options bag)\n// A string second argument is the className; an object is the props bag; string\n// children become text nodes. So `el(\"div\", \"c\", \"t\")` and\n// `el(\"div\", { className: \"c\" }, \"t\")` produce identical DOM.\n// ---------------------------------------------------------------------------\n\nimport { ensureTooltipHost, checkTooltipHost } from \"./tooltip.js\";\nimport { iconNode, iconNameOfGlyph, isIconName, type IconName } from \"./icons.js\";\n\nexport type Child = Node | string | null | undefined;\n\nexport interface ElProps {\n className?: string;\n text?: string;\n /** A NATIVE rollover. Prefer `tip`: `title` is OS chrome on the platform's own\n * slow delay, which is the seam the themed tooltip exists to close. */\n title?: string;\n /** A themed rollover (`data-tip`, picked up by the delegated controller that\n * `initTooltips()` wires). Also the accessible name, since the elements that\n * want one are usually icon buttons with no text of their own. */\n tip?: string;\n onClick?: (event: MouseEvent) => void;\n}\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n classNameOrProps?: string | ElProps,\n ...children: Child[]\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n const props: ElProps = typeof classNameOrProps === \"string\"\n ? { className: classNameOrProps }\n : classNameOrProps ?? {};\n if (props.className) node.className = props.className;\n if (props.text !== undefined && props.text !== null) node.textContent = props.text;\n if (props.title !== undefined) node.title = props.title;\n if (props.tip !== undefined) {\n node.dataset[\"tip\"] = props.tip;\n node.setAttribute(\"aria-label\", props.tip);\n // Setting a tip mounts the renderer. Without this, `data-tip` draws nothing at all in a window whose\n // host never called `initTooltips()`, which is a silent failure rather than a degraded one. No\n // options passed: the host stays authoritative about behaviour (see tooltip.ts).\n ensureTooltipHost();\n }\n if (props.onClick) node.addEventListener(\"click\", props.onClick as EventListener);\n for (const child of children) {\n if (child === null || child === undefined) continue;\n node.append(child);\n }\n checkTooltipHost(); // one-shot: warns if the APP wrote data-tip and nothing mounted a host\n return node;\n}\n\n/** A small square icon button (the move/delete controls list editors share).\n * Its rollover is the THEMED one (`data-tip`), so an app that has called\n * `initTooltips()` gets our bubble rather than the platform's.\n *\n * `name` is a word from the vocabulary (\"up\", \"close\"), drawn at 14px. A\n * typed glyph from the deprecated `icon` table (\"↑\", \"✕\") is accepted for\n * now and drawn as the word it stood for, so an app that has not moved its\n * call sites yet still gets the drawn set; any other string is set as text,\n * which is the old behaviour and on its way out. */\nexport function iconBtn(name: IconName | (string & {}), title: string, onClick: () => void, disabled = false, danger = false): HTMLButtonElement {\n const b = el(\"button\", `shell-icon${danger ? \" danger\" : \"\"}`);\n b.type = \"button\"; b.dataset[\"tip\"] = title; b.setAttribute(\"aria-label\", title);\n const word = isIconName(name) ? name : iconNameOfGlyph(name);\n if (word) b.append(iconNode(word)); else b.textContent = name;\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n}\n\n/** The sentence under a settings row: plain text, or nodes when it carries\n * markup (a `code` for a file name, a `b` for an option's name). */\nexport type FieldHint = string | Node | Array<string | Node>;\n\n/**\n * A captioned field, the family's settings row (the 2026-10 review, ruling C):\n * the caption on the left and the control inline beside it,\n * `<label class=\"shell-labelled\"><span class=\"shell-fieldcap\">…</span>control</label>`.\n *\n * The label is pointed at the field it captions, EXPLICITLY, and never at a button. A `<label>` with no\n * `for` forwards a click to its first labelable descendant, and buttons are labelable - so a caption\n * wrapped around a control containing buttons (`tagChips`, whose every chip carries a remove button)\n * turns every click on the row's dead space into a press of the FIRST button in it. That shipped:\n * patterkit/patter#44, where clicking a Game Data list value anywhere but its ✕ deleted the first value\n * in the list. Reported as \"clicking a value removes the wrong one\", which is not a wrong-index bug at\n * all - the click never reached the chip. The same rule makes a control GROUP safe to caption: a field\n * with its own Choose… or Import… button beside it (`.shell-inline`) captions the field, and the button\n * stays a button.\n *\n * When the control holds no labelable field (a chips editor with the add input removed, say), this is a\n * plain `<div>`: no caption behaviour is better than a caption that presses something.\n *\n * With a `hint`, the row and the sentence under it come back together in a `.shell-field`, the hint as\n * the field's description (`aria-describedby`) rather than inside the label, where it would become part\n * of the field's NAME and be read out every time it takes focus. Without one, the row is exactly what it\n * always was.\n */\nexport function labelled(label: string, control: HTMLElement, hint?: FieldHint): HTMLElement {\n const target = control.matches(\"input, select, textarea\")\n ? control\n : control.querySelector<HTMLElement>(\"input:not([type=button]):not([type=submit]), select, textarea\");\n const w = el(target ? \"label\" : \"div\", \"shell-labelled\");\n w.append(el(\"span\", \"shell-fieldcap\", label), control);\n if (target) {\n if (!target.id) target.id = uid();\n (w as HTMLLabelElement).htmlFor = target.id;\n }\n if (hint === undefined) return w;\n const note = el(\"p\", \"shell-fieldhint\");\n note.append(...(Array.isArray(hint) ? hint : [hint]));\n note.id = uid();\n if (target) {\n const by = target.getAttribute(\"aria-describedby\");\n target.setAttribute(\"aria-describedby\", by ? `${by} ${note.id}` : note.id);\n }\n return el(\"div\", \"shell-field\", w, note);\n}\n\nconst uid = (): string => `shell-f-${Math.random().toString(36).slice(2, 9)}`;\n\nexport interface LabelledToggleOptions {\n checked?: boolean;\n /** The sentence under the row: what turning it on does. */\n hint?: FieldHint;\n /** An option that only means something while the row above it is set (Patterpad's \"Embed source\n * language for debug\" under an IDs-only build): indented under that row with a rule, so it reads as\n * belonging to it rather than as a peer. */\n sub?: boolean;\n disabled?: boolean;\n /** Fired on every flip, with the new state. The input is handed back too, for a caller that reads it\n * on Save instead. */\n onChange?: (checked: boolean) => void;\n}\n\n/**\n * The settings toggle, `labelled`'s partner (ruling C): the caption on the left, the checkbox beside it\n * where any other row has its control, and the optional hint under the row. One shape in both apps, so\n * a switch in one app's settings looks like a switch in the other's. Clicking the caption flips it, as\n * the label is the checkbox's.\n *\n * Returns the outermost row (to append, hide, or indent) and the input (to read or listen to).\n */\nexport function labelledToggle(label: string, opts: LabelledToggleOptions = {}): { row: HTMLElement; input: HTMLInputElement } {\n const input = el(\"input\", \"shell-toggle-input\");\n input.type = \"checkbox\";\n input.checked = opts.checked ?? false;\n input.disabled = opts.disabled ?? false;\n const { onChange } = opts;\n if (onChange) input.addEventListener(\"change\", () => onChange(input.checked));\n const row = labelled(label, input, opts.hint);\n // On the caption-and-box line itself, whether or not a hint wraps it.\n (row.matches(\".shell-labelled\") ? row : row.querySelector(\".shell-labelled\"))?.classList.add(\"shell-toggle\");\n if (opts.sub) row.classList.add(\"shell-suboption\");\n return { row, input };\n}\n\n/** Swap item `i` with its neighbour `i + delta` IN PLACE (the up/down reorder list editors share);\n * a no-op when the target is out of range. Returns whether anything moved. */\nexport function moveItem<T>(arr: T[], i: number, delta: number): boolean {\n const j = i + delta;\n if (j < 0 || j >= arr.length) return false;\n [arr[i], arr[j]] = [arr[j]!, arr[i]!];\n return true;\n}\n\n/** The icon size inside a chip's remove / move buttons: the chip is set at\n * 0.76rem, so its controls draw smaller than a toolbar button's 14px. */\nconst CHIP_ICON = 10;\n\n/** A tag-style editor for a string-list field (an enum's allowed values / flags): removable chips + an\n * add input (Enter or \",\" commits; blank / duplicate ignored). Mutates `holder.values` in place (read\n * back on save). `onChange` fires after any add / remove so callers can refresh a dependent control. */\nexport function tagChips(holder: { values?: string[] }, onChange?: () => void): HTMLElement {\n const wrap = el(\"div\", \"shell-tags\");\n const input = el(\"input\", \"shell-tag-input\");\n // Sentence-cased like every other hint (the microcopy ruling, 2026-08-28):\n // lowercase fragments read as adrift, and a placeholder is a hint speaking.\n input.type = \"text\"; input.placeholder = \"Add value\"; input.spellcheck = false;\n const makeChip = (v: string): HTMLElement => {\n const chip = el(\"span\", \"shell-tag\", v);\n const x = el(\"button\", \"shell-tag-x\", iconNode(\"close\", CHIP_ICON));\n x.type = \"button\"; x.dataset[\"tip\"] = `Remove ${v}`; x.setAttribute(\"aria-label\", `Remove ${v}`);\n x.addEventListener(\"click\", () => { holder.values = (holder.values ?? []).filter((o) => o !== v); chip.remove(); onChange?.(); });\n chip.append(x);\n return chip;\n };\n const commit = (): void => {\n const v = input.value.trim();\n if (v && !(holder.values ?? []).includes(v)) { (holder.values ??= []).push(v); wrap.insertBefore(makeChip(v), input); onChange?.(); }\n input.value = \"\";\n };\n input.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \",\") { e.preventDefault(); commit(); } });\n input.addEventListener(\"blur\", commit);\n for (const v of holder.values ?? []) wrap.append(makeChip(v));\n wrap.append(input);\n return wrap;\n}\n\n/** What `stageChips` tells its caller beyond \"something changed\". */\nexport interface StageChipsOptions {\n /**\n * A stage was renamed in place, `from` to `to`. Given, a rename calls this\n * INSTEAD of `onChange`: values are stored by stage name, so whoever knows\n * where those values live has to carry them, and doing it as one edit is\n * theirs to arrange. Absent, a rename is an ordinary change.\n */\n onRename?: (from: string, to: string) => void;\n /** The stages were reordered (after `onChange`): the one edit that changes\n * what every ordering gate means without changing any value. */\n onReorder?: () => void;\n}\n\n/**\n * An ORDERED chips editor for a string-list field whose order means something\n * (a quality's stages: `advance()` walks them, so 2 comes after 1): numbered\n * chips, each with move-earlier / move-later / remove, plus an add input\n * (Enter or \",\" commits; blank / duplicate ignored). A stage's name renames\n * in place when clicked (Enter or blur commits, Escape cancels; blank, the same\n * name or another stage's is ignored). Mutates `holder.stages` in place;\n * `onChange` fires after any add / move / remove, and after a rename unless\n * `opts.onRename` takes it.\n *\n * Patterpad's, lifted (ui-review-2026-09, finding 3); Storyletter had been\n * faking it with `tagChips` over a `values` holder, which loses the order\n * controls. Same class family as `tagChips` (`.shell-tags`), so it is styled by\n * settings.css already.\n */\nexport function stageChips(holder: { stages?: string[] }, onChange?: () => void, opts: StageChipsOptions = {}): HTMLElement {\n const wrap = el(\"div\", \"shell-tags shell-stages\");\n const input = el(\"input\", \"shell-tag-input\");\n input.type = \"text\"; input.placeholder = \"Add stage\"; input.spellcheck = false;\n const control = (name: IconName, tip: string, disabled: boolean, onClick: () => void): HTMLButtonElement => {\n const b = el(\"button\", \"shell-tag-x shell-tag-move\", iconNode(name, CHIP_ICON));\n b.type = \"button\"; b.dataset[\"tip\"] = tip; b.setAttribute(\"aria-label\", tip);\n b.disabled = disabled;\n b.addEventListener(\"click\", onClick);\n return b;\n };\n // The name, editable in place. A span with a button's role rather than a\n // <button>, so the chip's own controls stay its only buttons.\n const nameLabel = (chip: HTMLElement, v: string, i: number): HTMLElement => {\n const label = el(\"span\", \"shell-tag-label\", `${i + 1}. ${v}`);\n label.setAttribute(\"role\", \"button\"); label.tabIndex = 0;\n label.dataset[\"tip\"] = `Rename ${v}`;\n const edit = (): void => {\n const field = el(\"input\", \"shell-tag-input shell-stage-rename\");\n field.type = \"text\"; field.value = v; field.spellcheck = false;\n field.setAttribute(\"aria-label\", `Rename ${v}`);\n let done = false;\n const finish = (keep: boolean): void => {\n if (done) return;\n done = true;\n const to = field.value.trim();\n const stages = holder.stages ?? [];\n if (keep && to !== \"\" && to !== v && !stages.includes(to)) {\n stages[stages.indexOf(v)] = to;\n rebuild();\n if (opts.onRename) opts.onRename(v, to); else onChange?.();\n } else {\n rebuild();\n }\n };\n field.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\") { e.preventDefault(); finish(true); }\n else if (e.key === \"Escape\") { e.preventDefault(); e.stopPropagation(); finish(false); }\n });\n field.addEventListener(\"blur\", () => finish(true));\n label.replaceWith(field);\n chip.classList.add(\"editing\");\n field.focus(); field.select();\n };\n // The chips usually sit inside a <label> for the add input (`labelled`),\n // whose activation would move focus there and close the rename at once.\n label.addEventListener(\"click\", (e) => { e.preventDefault(); edit(); });\n label.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); edit(); } });\n return label;\n };\n const moved = (): void => { rebuild(); onChange?.(); opts.onReorder?.(); };\n const rebuild = (): void => {\n for (const c of Array.from(wrap.children)) if (c !== input) c.remove();\n const stages = holder.stages ?? [];\n stages.forEach((v, i) => {\n const chip = el(\"span\", \"shell-tag\");\n chip.dataset[\"stage\"] = v;\n chip.append(\n nameLabel(chip, v, i),\n control(\"back\", `Move ${v} earlier`, i === 0, () => { if (moveItem(stages, i, -1)) moved(); }),\n control(\"forward\", `Move ${v} later`, i === stages.length - 1, () => { if (moveItem(stages, i, 1)) moved(); }),\n );\n const x = el(\"button\", \"shell-tag-x\", iconNode(\"close\", CHIP_ICON));\n x.type = \"button\"; x.dataset[\"tip\"] = `Remove ${v}`; x.setAttribute(\"aria-label\", `Remove ${v}`);\n x.addEventListener(\"click\", () => { holder.stages = (holder.stages ?? []).filter((o) => o !== v); rebuild(); onChange?.(); });\n chip.append(x);\n wrap.insertBefore(chip, input);\n });\n };\n const commit = (): void => {\n const v = input.value.trim();\n if (v && !(holder.stages ?? []).includes(v)) { (holder.stages ??= []).push(v); rebuild(); onChange?.(); }\n input.value = \"\";\n };\n input.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\" || e.key === \",\") { e.preventDefault(); commit(); } });\n input.addEventListener(\"blur\", commit);\n wrap.append(input);\n rebuild();\n return wrap;\n}\n\n// --- drag-to-reorder ---------------------------------------------------------\n// One drag at a time per window, shared across every wired list, so the\n// midpoint test knows what is being carried without a dataTransfer round trip.\nlet reorderDragId: string | null = null;\nconst clearDropMarks = (host: ParentNode): void =>\n host.querySelectorAll(\".drop-before, .drop-after, .drop-into\").forEach((e) => e.classList.remove(\"drop-before\", \"drop-after\", \"drop-into\"));\n\nexport interface ReorderOptions {\n /** Makes `el` a container too (a folder row): its middle half takes a drop\n * INTO it, wearing `.drop-into`, and a drop there calls this instead of\n * `onMove`. Its top and bottom quarters still mean before and after. */\n onInto?: (id: string, targetId: string) => void;\n}\n\n/**\n * Make `el` a drag source and a drop target for HTML5 drag-to-reorder among\n * its siblings. While carried it wears `.dragging`; a hovered target wears\n * `.drop-before` or `.drop-after` by the midpoint test on `axis` (\"y\" for\n * rows, \"x\" for a wrapping grid); a drop calls `onMove(draggedId, before,\n * targetId)` and the caller reorders its model and re-renders.\n *\n * Storyletter's `wireCardDrag`, lifted (ui-review-2026-09, finding 30);\n * Patterpad's scene list uses it too, with `onInto` for its folders. The\n * marks are classes only: the app draws them (Patterpad's `box-shadow: 0 -2px\n * 0 0 var(--accent)` pair is the family's look).\n */\nexport function wireReorder(el: HTMLElement, id: string, axis: \"x\" | \"y\", onMove: (id: string, before: boolean, targetId: string) => void, opts: ReorderOptions = {}): void {\n el.draggable = true;\n el.dataset[\"reorderId\"] = id;\n const host = (): ParentNode => el.parentElement ?? el;\n el.addEventListener(\"dragstart\", (e) => {\n reorderDragId = id; el.classList.add(\"dragging\");\n if (e.dataTransfer) { e.dataTransfer.effectAllowed = \"move\"; e.dataTransfer.setData(\"text/plain\", id); }\n });\n el.addEventListener(\"dragend\", () => { reorderDragId = null; el.classList.remove(\"dragging\"); clearDropMarks(host()); });\n el.addEventListener(\"dragover\", (e) => {\n if (!reorderDragId || reorderDragId === id) return;\n e.preventDefault();\n if (e.dataTransfer) e.dataTransfer.dropEffect = \"move\";\n const r = el.getBoundingClientRect();\n const at = axis === \"y\" ? (e.clientY - r.top) / (r.height || 1) : (e.clientX - r.left) / (r.width || 1);\n clearDropMarks(host());\n el.classList.add(opts.onInto && at >= 0.25 && at <= 0.75 ? \"drop-into\" : at < 0.5 ? \"drop-before\" : \"drop-after\");\n });\n el.addEventListener(\"dragleave\", () => el.classList.remove(\"drop-before\", \"drop-after\", \"drop-into\"));\n el.addEventListener(\"drop\", (e) => {\n e.preventDefault();\n const into = el.classList.contains(\"drop-into\");\n const before = el.classList.contains(\"drop-before\");\n clearDropMarks(host());\n if (!reorderDragId || reorderDragId === id) return;\n if (into) opts.onInto?.(reorderDragId, id); else onMove(reorderDragId, before, id);\n });\n}\n\n// --- metadata and breadcrumbs ---------------------------------------------------\n// \"Icons are drawn, and so are separators\" (design-language.md section 4,\n// 2026-09-16): metadata is never joined with \"·\" and a breadcrumb is never a\n// string with \"›\" in it. Both apps had been building exactly those strings\n// (ui-review-2026-09, marker A); these two helpers make the same lines as DOM,\n// with the separator drawn by CSS (controls.css) or by the icon vocabulary.\n\n/**\n * A metadata line: `parts` as spans in a flex row, separated by a drawn\n * 3px disc (a `::before` on every part after the first, in controls.css),\n * so `metaLine([\"6 runs\", \"200 max steps\", \"seed 4\"])` is what\n * \"6 runs · 200 max steps · seed 4\" used to be, without the typed dot.\n * Empty, null and undefined parts are skipped, so a caller can pass an\n * optional segment without branching.\n */\nexport function metaLine(parts: (string | Node | null | undefined)[]): HTMLElement {\n const row = el(\"span\", \"shell-meta\");\n for (const part of parts) {\n if (part === null || part === undefined || part === \"\") continue;\n const seg = el(\"span\", \"shell-meta-part\");\n seg.append(part);\n row.append(seg);\n }\n return row;\n}\n\nexport interface Crumb {\n label: string;\n /** Makes the crumb a button. Ignored on the last crumb, which is where the\n * reader already is. */\n onClick?: (event: MouseEvent) => void;\n}\n\n/**\n * A breadcrumb trail: each crumb a span, or a button when it carries\n * `onClick` and is not the last, with `iconNode(\"forward\", 12)` drawn between\n * them. `breadcrumb([\"Box\", \"Deck\", \"Card\"])` is what \"Box › Deck › Card\"\n * used to be; the chevrons are the family's own drawing, not a symbol-font\n * character.\n */\nexport function breadcrumb(parts: (string | Crumb)[]): HTMLElement {\n const trail = el(\"nav\", \"shell-crumbs\");\n trail.setAttribute(\"aria-label\", \"Where this is\");\n parts.forEach((part, i) => {\n const crumb: Crumb = typeof part === \"string\" ? { label: part } : part;\n const last = i === parts.length - 1;\n if (i > 0) {\n const sep = iconNode(\"forward\", 12);\n sep.classList.add(\"shell-crumb-sep\");\n trail.append(sep);\n }\n if (crumb.onClick && !last) {\n const b = el(\"button\", { className: \"shell-crumb shell-crumb-link\", text: crumb.label, onClick: crumb.onClick });\n b.type = \"button\";\n trail.append(b);\n } else {\n const s = el(\"span\", \"shell-crumb\", crumb.label);\n if (last) s.setAttribute(\"aria-current\", \"location\");\n trail.append(s);\n }\n });\n return trail;\n}\n","// A declared field's help, kept out of the way: the field's caption, whose rollover is the help,\n// and the same help above the control when it takes keyboard focus (Pattershot's brief field-help-as-a-tooltip,\n// ruled by Ian 2026-11-10, and settings-tables-tidy: no mark, and the chosen value's help in the tooltip too). The\n// help stays in the page, visually hidden, as the control's description, so a screen reader still reads it. Only a\n// field's problem is drawn under the control: it's a fault to fix.\n\nimport { ensureTooltipHost, hideTip, tipBold, tipFor } from \"./tooltip.js\";\n\n/** A field's tooltip: its own help, then what the chosen value means, the value named in bold. Empty when there's\n * neither. */\nexport function helpText(fieldHelp: string, value?: string, valueHelp?: string): string {\n const said = valueHelp && value ? `${tipBold(value)}: ${valueHelp}` : valueHelp ?? \"\";\n return [fieldHelp, said].filter((t) => t !== \"\").join(\"\\n\");\n}\n\n/** Give `caption` the help as its rollover (the themed tooltip, on hovering the caption; a help cursor says it's\n * there), so the caption's own text is unchanged. None: taken off. */\nexport function captionHelp(caption: HTMLElement, help: string): void {\n caption.classList.toggle(\"has-help\", help !== \"\");\n if (help === \"\") { delete caption.dataset[\"tip\"]; return; }\n caption.dataset[\"tip\"] = help;\n ensureTooltipHost();\n}\n\n/** Show the field's help above whichever control in `row` takes keyboard focus (not a click: a pointer has the\n * mark to hover). `help` is read at the moment, so a row that updates keeps it current. */\nexport function helpOnKeyboardFocus(row: HTMLElement, help: () => string): void {\n row.addEventListener(\"focusin\", (e) => {\n const target = e.target as HTMLElement | null;\n if (!target || !keyboardFocus(target)) return;\n // A part of a list or a map has its own row: its own help, not the list's.\n if (target.closest(\".shell-fieldrow\") !== row) return;\n tipFor(target, help());\n });\n row.addEventListener(\"focusout\", () => hideTip());\n}\n\n/** Whether `el` has focus the way a keyboard gives it (`:focus-visible`); false where that can't be told. */\nfunction keyboardFocus(el: HTMLElement): boolean {\n try { return el.matches(\":focus-visible\"); } catch { return false; }\n}\n","// ---------------------------------------------------------------------------\n// Typed value controls: how both apps show and set a value of a declared type.\n//\n// The first is the quality's. A quality is a step on a ladder, so its value is\n// shown as the LADDER, with the current rung marked, rather than a dropdown that\n// shows one name and hides where it stands (patterkit design/proposals/\n// patter-qualities.md section 4). Storyletter's Board drew it this way first;\n// it lives here so Storyletter's Fields tab, Patterpad's play panel and Game\n// Data rows, and the play inspectors all draw the same strip.\n//\n// An UNSET value (a field or game-data value the node leaves out) names what it\n// falls back to, \"default (x)\", by the family's one rule: the declaration's own\n// default, else the type's (a quality's first stage). A value that is no longer\n// a stage (a deleted one) is shown as such, so it can be put back on the ladder.\n//\n// No Electron, no app state: this module is DOM only, and is its own entry point\n// (`@wildwinter/app-shell/value-control`) so a play inspector outside the apps\n// can take it.\n// ---------------------------------------------------------------------------\n\nimport { captionHelp, helpOnKeyboardFocus, helpText } from \"./field-help.js\";\nimport { el, tagChips } from \"./dom.js\";\n\n/** A declaration as these controls read it: a property's, a field's or a game-data field's. */\nexport interface ValueDeclLike {\n type: string;\n default?: unknown;\n /** enum / flags */\n values?: readonly string[];\n /** quality: the ladder, in order */\n stages?: readonly string[];\n}\n\n/**\n * What an unset value of `decl` falls back to, as the words a control shows: its own default where it has\n * one, else its type's (False, 0, the first value, no flags, the first stage, empty text). The kernel's\n * `defaultFor`, in words; a host reads the same value from the bundle.\n */\nexport function fallbackText(decl: ValueDeclLike): string {\n const d = decl.default;\n if (d !== undefined && d !== null) {\n if (typeof d === \"boolean\") return d ? \"True\" : \"False\";\n if (Array.isArray(d)) return d.length === 0 ? \"none\" : d.join(\", \");\n return d === \"\" ? \"none\" : String(d);\n }\n switch (decl.type) {\n case \"boolean\": return \"False\";\n case \"number\": return \"0\";\n case \"enum\": return decl.values?.[0] ?? \"none\";\n case \"quality\": return decl.stages?.[0] ?? \"first stage\";\n case \"flags\": return \"none\";\n default: return \"empty\";\n }\n}\n\n/** The label an unset value shows: \"default (x)\", naming what it falls back to. */\nexport const defaultLabel = (decl: ValueDeclLike): string => `default (${fallbackText(decl)})`;\n\n/**\n * The quiet line under a ladder that has just been reordered: how many gates order against it. A reorder\n * changes no value, and is the one edit that changes what a gate MEANS without saying so (patter-qualities.md\n * section 2), so the editor says so. Empty when no gate orders the quality.\n */\nexport function stageGateNote(gates: number): string {\n if (gates <= 0) return \"\";\n return gates === 1\n ? \"1 gate compares against this ladder, and it now reads the new order.\"\n : `${gates} gates compare against this ladder, and each now reads the new order.`;\n}\n\nexport interface RungStripOptions {\n /** The ladder, in order. */\n stages: readonly string[];\n /** The value held, or undefined when unset: a field or game-data value the node leaves out. */\n value: string | undefined;\n /** The declaration, for what an unset value falls back to (its default, else the first stage). */\n decl?: ValueDeclLike;\n /** Pick a stage. Absent: the strip is read-only. */\n onPick?: (stage: string) => void;\n /** Clear back to unset. With it, the strip leads with \"default (x)\", which clears the value. */\n onClear?: () => void;\n /** The rollover on a rung that is not the current one (default: `Set to \"x\"`). */\n pickTip?: (stage: string) => string;\n /**\n * The document to build in, for a host outside the apps (a play inspector in a page or a frame of its\n * own). Given, rollovers are plain `title`s and the family's themed tooltips are not mounted; the host\n * styles `.seg`, `.seg-opt` and `.shell-rung` itself. Absent: the apps' document and themed rollovers.\n */\n doc?: Document;\n}\n\n/**\n * A quality's value as its ladder: one rung per stage, in order, the current one marked (`on`). Unset, the\n * rung it falls back to is marked as the fallback (`fallback`), and with `onClear` a leading \"default (x)\"\n * option is the current choice. A value that is not a stage (a deleted one) is shown after the ladder,\n * marked `stray`, until a rung is picked.\n */\nexport function rungStrip(opts: RungStripOptions): HTMLElement {\n const make = opts.doc === undefined ? el : (tag: \"span\" | \"button\", props: { className: string; text?: string; tip?: string; onClick?: () => void }) => {\n const node = opts.doc!.createElement(tag);\n node.className = props.className;\n if (props.text !== undefined) node.textContent = props.text;\n if (props.tip !== undefined) { node.title = props.tip; node.setAttribute(\"aria-label\", props.tip); }\n if (props.onClick) node.addEventListener(\"click\", props.onClick);\n if (tag === \"button\") (node as HTMLButtonElement).type = \"button\";\n return node;\n };\n const strip = make(\"span\", { className: \"seg shell-rungs\" });\n const decl: ValueDeclLike = opts.decl ?? { type: \"quality\", stages: opts.stages };\n const unset = opts.value === undefined;\n const fallback = typeof decl.default === \"string\" && opts.stages.includes(decl.default) ? decl.default : opts.stages[0];\n const readOnly = opts.onPick === undefined;\n if (opts.onClear) {\n strip.append(make(\"button\", {\n className: `seg-opt shell-rung shell-rung-default${unset ? \" on\" : \"\"}`, text: defaultLabel(decl),\n tip: unset ? \"Not set here: the declared default holds\" : \"Clear the value, back to the default\",\n onClick: () => { if (!unset) opts.onClear!(); },\n }));\n }\n for (const stage of opts.stages) {\n const current = opts.value === stage;\n const falls = unset && stage === fallback;\n const rung = make(readOnly ? \"span\" : \"button\", {\n className: `seg-opt shell-rung${current ? \" on\" : \"\"}${falls ? \" fallback\" : \"\"}`, text: stage,\n tip: current ? \"The current stage\" : falls ? \"The stage it falls back to\" : readOnly ? stage : (opts.pickTip?.(stage) ?? `Set to \"${stage}\"`),\n ...(readOnly ? {} : { onClick: () => { if (!current) opts.onPick!(stage); } }),\n });\n strip.append(rung);\n }\n if (!unset && !opts.stages.includes(opts.value!)) {\n strip.append(make(\"span\", {\n className: \"seg-opt shell-rung stray on\", text: opts.value!,\n tip: `\"${opts.value}\" is not one of the stages: pick the one it belongs at now`,\n }));\n }\n return strip;\n}\n\n// ---------------------------------------------------------------------------\n// The field row: ONE control for a node's value of a declared field, driven by\n// the declared type, for the family's seven types (text, multiline, number,\n// boolean, enum, flags, quality). Storyletter's card and outcome Fields and\n// Patterpad's Game Data rows both draw it, which settles the disagreements the\n// parity review found (patterkit design/family-parity-2026-10.md item 10):\n//\n// - the declared purpose is shown, under the control, not hidden in a rollover;\n// - unset says what it falls back to, \"default (x)\", by `defaultLabel`;\n// - flags are a picker of the declared values, not comma-separated text;\n// - multiline is a text area;\n// - a value the declaration refuses (not a number, not one of the values, not\n// a stage) is said under the control, and the app may add its own message;\n// - text commits as typed, and Escape puts back what the field held when it\n// took focus (the family's field rule).\n//\n// Committing as typed means a host that redraws on every change (Patterpad's\n// inspector rebuilds its whole stack) would drop the focus at every keystroke.\n// `fieldRows()` is the answer: it hands back the SAME element for the same key\n// across a redraw, updated in place, and gives the focus (and the caret) back\n// to it once the redraw has put it in the page again.\n// ---------------------------------------------------------------------------\n\n/** A value a field row commits: a flags value is the list of names set. */\nexport type FieldValue = string | number | boolean | string[];\n\n/** A field's declaration as the row reads it: a card field's, an outcome field's or a game-data field's. */\nexport interface FieldDeclLike extends ValueDeclLike {\n name: string;\n /** What the field is for, shown under the control. */\n purpose?: string;\n /** A sentence about each value (an enum's values, the flags, a quality's stages, a pick's fixed words), as an\n * extension's manifest gives them: the chosen value's is shown under the control, and each chosen flag's. */\n valueHelp?: Readonly<Record<string, string>>;\n}\n\n/**\n * The help for what `decl` holds: the chosen enum value's, stage's or pick word's (the one it falls back to, when\n * unset), or each chosen flag's, \"name: sentence\" a line each. Empty when the declaration describes none of them.\n */\n/** The word the tooltip names the value by: the chosen enum value or stage, else the one an unset field falls\n * back to (as `valueHelpText` reads it). Nothing for another type, whose value help names its own words. */\nfunction valueWord(decl: FieldDeclLike, value: unknown): string | undefined {\n if (decl.type !== \"enum\" && decl.type !== \"quality\") return undefined;\n if (typeof value === \"string\" && value !== \"\") return value;\n if (typeof decl.default === \"string\" && decl.default !== \"\") return decl.default;\n return decl.type === \"enum\" ? decl.values?.[0] : decl.stages?.[0];\n}\n\nexport function valueHelpText(decl: FieldDeclLike, value: unknown): string {\n const help = decl.valueHelp;\n if (help === undefined) return \"\";\n const said = (v: string): string | undefined => (Object.prototype.hasOwnProperty.call(help, v) ? help[v] : undefined);\n if (decl.type === \"enum\" || decl.type === \"quality\") {\n const held = typeof value === \"string\" && value !== \"\" ? value\n : typeof decl.default === \"string\" && decl.default !== \"\" ? decl.default\n : decl.type === \"enum\" ? decl.values?.[0] : decl.stages?.[0];\n return held === undefined ? \"\" : said(held) ?? \"\";\n }\n if (decl.type === \"flags\") {\n const held = Array.isArray(value) ? value.map(String) : Array.isArray(decl.default) ? decl.default.map(String) : [];\n return held.flatMap((v) => { const h = said(v); return h === undefined ? [] : [`${v}: ${h}`]; }).join(\"\\n\");\n }\n if (decl.type === \"pick\") {\n // A pick's help describes its fixed words only; empty is automatic, so nothing is chosen to describe.\n const held = typeof value === \"string\" && value !== \"\" ? value : typeof decl.default === \"string\" ? decl.default : \"\";\n return held === \"\" ? \"\" : said(held) ?? \"\";\n }\n return \"\";\n}\n\nexport interface FieldRowOptions {\n decl: FieldDeclLike;\n /** The node's own value, typed (a flags value is a string list), or undefined where the node sets none. */\n value: unknown;\n /** Commit a value; `undefined` clears it, back to the default. Absent: the row is read-only. */\n onCommit?: (value: FieldValue | undefined) => void;\n /** Drawn but not editable, as when there is no node to write to. */\n disabled?: boolean;\n /** A message of the app's own about this value (a check the row cannot make), shown under the control. */\n problem?: string;\n /** The app's own sentence INSTEAD of the row's (a renamed stage said better than \"not one of the stages\").\n * Given, it is the whole message; `problem` is ignored. */\n replaceProblem?: string;\n /** The caption; default the field's name. */\n label?: string | Node;\n /** Added to the row and its caption, so a row sits in its app's layout. */\n className?: string;\n labelClassName?: string;\n /** The app's class for a text field or select, used instead of the shell's `.field`. */\n controlClassName?: string;\n}\n\nexport interface FieldRowHandle {\n /** The row: its caption, then the control with the purpose and any message under it. */\n readonly el: HTMLElement;\n /** Redraw for a new value, declaration or message, keeping the element. A field being typed in keeps its\n * text: what the author is typing is newer than what the app last stored. */\n update(opts: FieldRowOptions): void;\n}\n\n/** The type's fallback as a value, for a flags picker to start from when the node sets none. */\nconst fallbackFlags = (decl: ValueDeclLike): string[] => (Array.isArray(decl.default) ? decl.default.filter((v): v is string => typeof v === \"string\") : []);\n\n/** What the row says about a value the declaration refuses, or \"\" when it fits. */\nexport function fieldValueProblem(decl: ValueDeclLike, value: unknown): string {\n if (value === undefined || value === null || value === \"\") return \"\";\n switch (decl.type) {\n case \"number\":\n return typeof value === \"number\" && Number.isFinite(value) ? \"\" : \"Not a number.\";\n case \"boolean\":\n return typeof value === \"boolean\" ? \"\" : \"Not True or False.\";\n case \"enum\":\n return decl.values && !decl.values.includes(String(value)) ? `\"${String(value)}\" is not one of the values.` : \"\";\n case \"quality\":\n return decl.stages && !decl.stages.includes(String(value)) ? `\"${String(value)}\" is not one of the stages.` : \"\";\n case \"flags\": {\n if (!Array.isArray(value)) return \"Not a list of flags.\";\n const strays = decl.values ? value.filter((v) => !decl.values!.includes(String(v))) : [];\n if (strays.length === 0) return \"\";\n return strays.length === 1 ? `\"${String(strays[0])}\" is not one of the values.` : `${strays.map((s) => `\"${String(s)}\"`).join(\", \")} are not among the values.`;\n }\n default:\n return \"\";\n }\n}\n\nconst isTextual = (type: string): boolean => type !== \"boolean\" && type !== \"enum\" && type !== \"flags\" && type !== \"quality\";\n\n/** Everything that decides which control is drawn: a change here rebuilds it, anything else updates it. */\nconst controlShape = (o: FieldRowOptions): string => JSON.stringify([\n o.decl.type, o.decl.values ?? null, o.decl.stages ?? null, o.decl.default ?? null,\n o.onCommit === undefined, o.disabled === true, o.controlClassName ?? \"\",\n o.decl.type === \"quality\" && (o.decl.stages?.length ?? 0) === 0,\n]);\n\n/**\n * One declared field's value as a row: the caption, then the control the declared type calls for, with the\n * purpose and any message under it. See the section comment above for the rules it carries.\n */\nexport function fieldRow(initial: FieldRowOptions): FieldRowHandle {\n let opts = initial;\n const row = el(\"div\", `shell-fieldrow${opts.className ? ` ${opts.className}` : \"\"}`);\n const caption = el(\"span\", `shell-fieldrow-label${opts.labelClassName ? ` ${opts.labelClassName}` : \"\"}`);\n caption.id = `shell-fr-${Math.random().toString(36).slice(2, 9)}`;\n const body = el(\"div\", \"shell-fieldrow-body\");\n // The field's help is the control's description, read by a screen reader, and shown by the caption's \"?\" and on\n // keyboard focus rather than drawn under every field (field-help.ts).\n const purpose = el(\"p\", \"shell-fieldrow-purpose shell-fieldrow-described\");\n // What the chosen value means is said with the field's help, in the tooltip, not under the control.\n const valueHelp = el(\"p\", \"shell-fieldrow-purpose shell-fieldrow-valuehelp shell-fieldrow-described\");\n const problem = el(\"p\", \"shell-fieldrow-problem\");\n problem.setAttribute(\"role\", \"status\");\n row.append(caption, body);\n const tip = (): string => helpText(opts.decl.purpose ?? \"\", valueWord(opts.decl, opts.value), valueHelpText(opts.decl, opts.value));\n helpOnKeyboardFocus(row, tip);\n\n let shape = \"\";\n /** Redraw the control for the current value; set by whichever control is built. */\n let show: (value: unknown) => void = () => {};\n /** A local problem the control found while typing (a number that is not one yet). */\n let typing = \"\";\n\n const commit = (v: FieldValue | undefined): void => { opts.onCommit?.(v); };\n const editable = (): boolean => opts.onCommit !== undefined && opts.disabled !== true;\n // An app's own field class replaces the shell's `.field`, so its controls look like its other fields.\n const ctl = (base: string): string => (opts.controlClassName ? `${base.replace(/^field /, \"\")} ${opts.controlClassName}` : base);\n const labelledBy = (node: HTMLElement): void => {\n node.setAttribute(\"aria-labelledby\", caption.id);\n const described = [purpose.id, valueHelp.id, problem.id].filter(Boolean).join(\" \");\n if (described) node.setAttribute(\"aria-describedby\", described);\n };\n purpose.id = `${caption.id}-p`; valueHelp.id = `${caption.id}-v`; problem.id = `${caption.id}-m`;\n\n const sayProblem = (): void => {\n // The chosen value's own help travels with every change of value, as the message does.\n const help = valueHelpText(opts.decl, opts.value);\n valueHelp.textContent = help;\n captionHelp(caption, tip());\n const words = opts.replaceProblem !== undefined\n ? [opts.replaceProblem].filter((w) => w !== \"\")\n : [typing || fieldValueProblem(opts.decl, opts.value), opts.problem ?? \"\"].filter((w) => w !== \"\");\n problem.textContent = words.join(\" \");\n problem.hidden = words.length === 0;\n row.classList.toggle(\"has-problem\", words.length > 0);\n row.classList.toggle(\"shell-fieldrow-textual\", isTextual(opts.decl.type));\n };\n\n const build = (): HTMLElement => {\n const decl = opts.decl;\n typing = \"\";\n if (decl.type === \"quality\" && (decl.stages?.length ?? 0) > 0) {\n const holder = el(\"span\", \"shell-fieldrow-rungs\");\n show = (value) => {\n holder.replaceChildren(rungStrip({\n stages: decl.stages!, value: typeof value === \"string\" && value !== \"\" ? value : undefined, decl,\n ...(editable() ? { onPick: (stage: string) => { opts = { ...opts, value: stage }; show(stage); sayProblem(); commit(stage); }, onClear: () => { opts = { ...opts, value: undefined }; show(undefined); sayProblem(); commit(undefined); } } : {}),\n }));\n const strip = holder.firstElementChild as HTMLElement | null;\n if (strip) { strip.setAttribute(\"role\", \"group\"); labelledBy(strip); }\n };\n return holder;\n }\n if (decl.type === \"boolean\" || decl.type === \"enum\") {\n const sel = el(\"select\", ctl(\"field shell-fieldrow-select\"));\n labelledBy(sel);\n sel.disabled = !editable();\n const choices: Array<[string, string]> = decl.type === \"boolean\"\n ? [[\"true\", \"True\"], [\"false\", \"False\"]]\n : (decl.values ?? []).map((v): [string, string] => [v, v]);\n show = (value) => {\n const current = value === undefined || value === null || value === \"\" ? \"\" : String(value);\n sel.replaceChildren();\n const add = (v: string, label: string): void => { const o = el(\"option\", undefined, label); o.value = v; sel.append(o); };\n add(\"\", defaultLabel(decl));\n for (const [v, label] of choices) add(v, label);\n // A value the declaration no longer offers stays visible, so the author sees what is held.\n if (current !== \"\" && !choices.some(([v]) => v === current)) add(current, current);\n sel.value = current;\n };\n sel.addEventListener(\"change\", () => {\n const raw = sel.value;\n const v: FieldValue | undefined = raw === \"\" ? undefined : decl.type === \"boolean\" ? raw === \"true\" : raw;\n opts = { ...opts, value: v }; sayProblem(); commit(v);\n });\n return sel;\n }\n if (decl.type === \"flags\" && (decl.values?.length ?? 0) > 0) {\n const holder = el(\"span\", \"shell-flags\");\n holder.setAttribute(\"role\", \"group\");\n labelledBy(holder);\n show = (value) => {\n const unset = !Array.isArray(value);\n const held = unset ? fallbackFlags(decl) : (value as unknown[]).map(String);\n holder.replaceChildren();\n const chip = (text: string, cls: string, pressed: boolean | undefined, tip: string, onClick?: () => void): void => {\n const b = el(onClick && editable() ? \"button\" : \"span\", { className: `shell-flag${cls}`, text, tip });\n b.setAttribute(\"aria-label\", `${text}: ${tip}`); // the tip alone would hide the value's name\n if (b instanceof HTMLButtonElement) { b.type = \"button\"; b.addEventListener(\"click\", onClick!); }\n if (pressed !== undefined) b.setAttribute(\"aria-pressed\", String(pressed));\n holder.append(b);\n };\n const set = (next: string[] | undefined): void => { opts = { ...opts, value: next }; show(next); sayProblem(); commit(next); };\n chip(defaultLabel(decl), ` shell-flag-default${unset ? \" on\" : \"\"}`, undefined,\n unset ? \"Not set here: the declared default holds\" : \"Clear the value, back to the default\",\n unset ? undefined : () => set(undefined));\n for (const v of decl.values!) {\n const on = held.includes(v);\n chip(v, `${on ? \" on\" : \"\"}${on && unset ? \" fallback\" : \"\"}`, on, on ? `Unset ${v}` : `Set ${v}`,\n () => set(on ? held.filter((x) => x !== v) : decl.values!.filter((x) => x === v || held.includes(x)).concat(held.filter((x) => !decl.values!.includes(x)))));\n }\n // A held value the declaration does not offer: shown, struck, and a click drops it.\n for (const v of held.filter((x) => !decl.values!.includes(x))) {\n chip(v, \" stray on\", true, `\"${v}\" is not one of the values: click to drop it`, () => set(held.filter((x) => x !== v)));\n }\n };\n return holder;\n }\n if (decl.type === \"flags\") {\n // No values declared to pick from: the shell's chip editor, each name typed.\n const holder = el(\"span\", \"shell-fieldrow-chips\");\n show = (value) => {\n const box = { values: Array.isArray(value) ? (value as unknown[]).map(String) : [] };\n const chips = tagChips(box, () => {\n const next = box.values.length > 0 ? [...box.values] : undefined;\n opts = { ...opts, value: next }; sayProblem(); commit(next);\n });\n const input = chips.querySelector(\"input\");\n if (input) { labelledBy(input); input.placeholder = Array.isArray(value) ? \"Add value\" : defaultLabel(decl); input.disabled = !editable(); }\n chips.querySelectorAll(\"button\").forEach((b) => { (b as HTMLButtonElement).disabled = !editable(); });\n holder.replaceChildren(chips);\n };\n return holder;\n }\n // text, multiline, number, and any type the row does not know: typed, committed as typed.\n const multiline = decl.type === \"multiline\";\n const field = multiline ? el(\"textarea\", ctl(\"field shell-fieldrow-text\")) : el(\"input\", ctl(\"field shell-fieldrow-text\"));\n if (field instanceof HTMLInputElement) {\n field.type = \"text\";\n if (decl.type === \"number\") field.inputMode = \"decimal\";\n } else field.rows = 3;\n labelledBy(field);\n field.placeholder = defaultLabel(decl);\n field.disabled = !editable();\n const asText = (value: unknown): string =>\n value === undefined || value === null ? \"\" : Array.isArray(value) ? value.join(\", \") : String(value);\n const read = (raw: string): { value?: FieldValue; bad?: string } => {\n if (raw.trim() === \"\") return {};\n if (decl.type !== \"number\") return { value: raw };\n const n = Number(raw.trim());\n return Number.isFinite(n) ? { value: n } : { bad: \"Not a number.\" };\n };\n let atFocus = \"\";\n show = (value) => {\n // What the author is typing is newer than the stored value, so a focused field keeps its text.\n // So does one the author was in when a redraw took the row out of the page, until they leave it.\n if (field.ownerDocument.activeElement === field || keeper.restoring(field) || keeper.holds(row)) return;\n field.value = asText(value);\n };\n const take = (): void => {\n const r = read(field.value);\n typing = r.bad ?? \"\";\n if (r.bad === undefined) opts = { ...opts, value: r.value };\n sayProblem();\n if (r.bad === undefined) commit(r.value);\n };\n field.addEventListener(\"focus\", () => { if (!keeper.restoring(field)) atFocus = field.value; });\n field.addEventListener(\"input\", take);\n field.addEventListener(\"keydown\", (e) => {\n if ((e as KeyboardEvent).key !== \"Escape\" || field.value === atFocus) return;\n field.value = atFocus;\n take();\n });\n return field;\n };\n\n const draw = (next: FieldRowOptions): void => {\n const decl = next.decl;\n const label = next.label ?? decl.name;\n if (typeof label === \"string\") caption.textContent = label; else caption.replaceChildren(label);\n caption.className = `shell-fieldrow-label${next.labelClassName ? ` ${next.labelClassName}` : \"\"}`;\n row.className = `shell-fieldrow${next.className ? ` ${next.className}` : \"\"}`;\n const nextShape = controlShape(next);\n opts = next;\n if (nextShape !== shape) {\n shape = nextShape;\n body.replaceChildren(build());\n }\n show(next.value);\n purpose.textContent = decl.purpose ?? \"\";\n if (decl.purpose) body.append(purpose); else purpose.remove();\n body.append(valueHelp, problem);\n sayProblem();\n };\n\n draw(initial);\n keeper.watch(row);\n return { el: row, update: draw };\n}\n\n// --- keeping the focus across a redraw -------------------------------------\n//\n// A host that rebuilds its view takes the row out of the page and puts it back.\n// Taking a focused element out drops its focus, which is the same thing as\n// the author losing their place mid-word. So a row remembers whether the\n// author was in it, and where the caret was, and the cache gives both back\n// once the row is in the page again (a microtask: after the host's redraw).\n\ninterface FocusMemo { field: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement; start: number | null; end: number | null }\n\nconst keeper = (() => {\n const memos = new WeakMap<HTMLElement, FocusMemo>();\n const restoringNow = new WeakSet<Element>();\n /** When each row was last handed back by the cache, to tell a move's late blur from the author's. */\n const redrawn = new WeakMap<HTMLElement, number>();\n const put = (row: HTMLElement): void => {\n const memo = memos.get(row);\n if (!memo) return;\n const doc = row.ownerDocument;\n const active = doc.activeElement;\n if (!row.isConnected || !memo.field.isConnected || !row.contains(memo.field)) return;\n if (active && active !== doc.body && !row.contains(active)) { memos.delete(row); return; }\n if (active === memo.field && memo.field.matches(\":focus\")) return;\n restoringNow.add(memo.field);\n try {\n memo.field.focus({ preventScroll: true });\n if (!(memo.field instanceof HTMLSelectElement) && memo.start !== null) memo.field.setSelectionRange(memo.start, memo.end ?? memo.start);\n } finally { restoringNow.delete(memo.field); }\n };\n return {\n watch(row: HTMLElement): void {\n const note = (e: Event): void => {\n const t = e.target;\n if (!(t instanceof HTMLInputElement || t instanceof HTMLTextAreaElement || t instanceof HTMLSelectElement)) return;\n const text = !(t instanceof HTMLSelectElement);\n memos.set(row, { field: t, start: text ? (t as HTMLInputElement).selectionStart : null, end: text ? (t as HTMLInputElement).selectionEnd : null });\n };\n row.addEventListener(\"focusin\", note);\n row.addEventListener(\"input\", note);\n row.addEventListener(\"keyup\", note);\n row.addEventListener(\"mouseup\", note);\n // A blur the author made (the row still in the page) forgets; one caused by the row leaving the page\n // does not, and is what the cache puts back. Chromium drops the focus of a moved element LATE: the\n // blur arrives after the redraw, the row already back in the page, so a blur to nowhere just after a\n // redraw is the move's, and the focus goes straight back.\n // Chromium may also fire that blur synchronously, as the host clears its view and before the cache\n // has handed the row back: so the decision waits for the microtask, by which time a row the redraw\n // asked for again is marked as redrawn since the blur.\n row.addEventListener(\"focusout\", (e) => {\n const at = performance.now();\n const late = (e as FocusEvent).relatedTarget === null && at - (redrawn.get(row) ?? -Infinity) < 250;\n if (late && memos.has(row)) { queueMicrotask(() => put(row)); return; }\n queueMicrotask(() => {\n if ((redrawn.get(row) ?? -Infinity) >= at) { put(row); return; }\n if (row.isConnected && !row.contains(row.ownerDocument.activeElement)) memos.delete(row);\n });\n });\n },\n /** Give the focus back to the row, if the author was in it and has not gone anywhere else since. */\n restore(row: HTMLElement): void {\n if (!memos.has(row)) return;\n redrawn.set(row, performance.now());\n // At the microtask, not now: the event that caused the redraw may still note the caret.\n queueMicrotask(() => put(row));\n },\n restoring: (field: Element): boolean => restoringNow.has(field),\n /** Forget the row's focus if the author has since gone somewhere else (before the row is redrawn). */\n settle(row: HTMLElement): void {\n const active = row.ownerDocument.activeElement;\n if (active && active !== row.ownerDocument.body && !row.contains(active)) memos.delete(row);\n },\n /** The row the author is in (before a redraw takes it out). */\n holds: (row: HTMLElement): boolean => memos.has(row),\n };\n})();\n\n/**\n * The focus keeping `fieldRows` does, for a host (or a structured row) that keeps row elements itself: call\n * `settle` before it updates a row and `restore` once the row is back in the page.\n */\nexport const fieldRowFocus = {\n settle: (row: HTMLElement): void => keeper.settle(row),\n restore: (row: HTMLElement): void => keeper.restore(row),\n};\n\nexport interface FieldRows {\n /**\n * The row for `key` (a node and a field, say `${nodeId}:${field}`): the same element as last time when\n * there was one, updated for these options, else a new one. A row the author was typing in gets its focus\n * and caret back once the redraw has put it in the page.\n */\n row(key: string, opts: FieldRowOptions): HTMLElement;\n /** Forget every row not asked for since the last sweep (call at the end of a redraw). */\n sweep(): void;\n}\n\n/** A cache of field rows for a host that redraws its whole view on every change. */\nexport function fieldRows(): FieldRows {\n const rows = new Map<string, { handle: FieldRowHandle; used: boolean }>();\n return {\n row(key, opts) {\n const had = rows.get(key);\n if (had) {\n had.used = true;\n keeper.settle(had.handle.el);\n had.handle.update(opts);\n keeper.restore(had.handle.el);\n return had.handle.el;\n }\n const handle = fieldRow(opts);\n rows.set(key, { handle, used: true });\n return handle.el;\n },\n sweep() {\n for (const [key, entry] of rows) { if (!entry.used) rows.delete(key); else entry.used = false; }\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// The property examiner/editor, JS idiom: a self-styled DOM panel (the\n// parity member Unity renders as an EditorWindow, Unreal as a Slate tab,\n// Godot as an in-game panel). Rows come from live().listProperties() and\n// are built once (declared properties are fixed for a bundle); values\n// refresh on a poll that SKIPS the focused widget; every row has a\n// reset-to-default that disables itself at the default. Edits commit via\n// flow.setProperty, which is a silent host write under the firing rule.\n// Save state / Load state carry the whole run over the .storyletsave string\n// boundary (save.ts); a filter narrows the property rows; the read-only\n// turns and board sections mirror the engine examiners (design 2.4).\n// The JS game runs in-process, so the engine and a flow are passed directly\n// (no debug registry needed here).\n//\n// The log panel (design 2.3: the flow's retained log surfaced in every\n// examiner; the old port's Unreal log panel is the high-water mark): the\n// lines of live().log() behind per-kind filters (a peek files under Deal -\n// both are asks), with Autoscroll, Copy and Clear. Empty until the engine\n// is created with the log option.\n// ---------------------------------------------------------------------------\n/// <reference lib=\"dom\" />\n\nimport { rungStrip } from \"@wildwinter/app-shell/value-control\";\nimport type { Engine, EngineLogEntry, Flow, LogEntry, PropertyRow } from \"@storylet-studio/runtime\";\nimport type { ScalarValue } from \"@storylet-studio/model\";\nimport { serializeState, deserializeState } from \"./save.js\";\n\nexport interface PropertyInspectorOptions {\n /** Mount point; defaults to document.body. */\n container?: HTMLElement;\n title?: string;\n /** Value-refresh poll; 0 disables polling. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n el: HTMLElement;\n refresh(): void;\n destroy(): void;\n}\n\nconst STYLE_ID = \"sl-inspector-style\";\nconst CSS = `\n.sl-insp { font: 12px system-ui, sans-serif; color: var(--ink, #222); background: var(--surface, #fafafa);\n border: 1px solid var(--line, #ccc); border-radius: 8px; padding: 10px 12px; max-width: 26rem; }\n.sl-insp h3 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--muted, #666); }\n.sl-insp .sl-head { display: flex; align-items: baseline; gap: 6px; }\n.sl-insp .sl-head h3 { flex: 1; }\n.sl-insp .sl-save, .sl-insp .sl-load { font: inherit; font-size: 11px; padding: 1px 6px; cursor: pointer; }\n.sl-insp .sl-filter { display: block; width: 100%; box-sizing: border-box; margin: 0 0 6px; }\n.sl-insp .sl-group { margin: 8px 0 2px; font-weight: 600; font-size: 11px; color: var(--muted, #666); }\n.sl-insp .sl-section { margin: 10px 0 2px; font-weight: 600; font-size: 11px; text-transform: uppercase;\n letter-spacing: 0.06em; color: var(--muted, #666); }\n.sl-insp .sl-line { padding: 1px 0; }\n.sl-insp .sl-row { display: flex; align-items: center; gap: 6px; padding: 2px 0; }\n.sl-insp .sl-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n.sl-insp input[type=\"text\"], .sl-insp input[type=\"number\"], .sl-insp select {\n font: inherit; width: 9rem; padding: 1px 4px; }\n.sl-insp .sl-reset { border: 0; background: none; cursor: pointer; color: var(--muted, #666); }\n.sl-insp .sl-reset:disabled { opacity: 0.3; cursor: default; }\n.sl-insp .sl-logbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 2px 0; }\n.sl-insp .sl-logbar label { display: inline-flex; align-items: center; gap: 2px; }\n.sl-insp .sl-logbar button { font: inherit; font-size: 11px; padding: 1px 6px; cursor: pointer; }\n.sl-insp .sl-log { font-family: ui-monospace, monospace; font-size: 11px; max-height: 12rem;\n overflow: auto; white-space: pre; border: 1px solid var(--line, #ccc); padding: 4px 6px; }\n.sl-insp details.sl-fold > summary { cursor: pointer; margin: 10px 0 2px; font-weight: 600;\n font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted, #666); }\n.sl-insp .sl-ident { font-family: ui-monospace, monospace; font-size: 11px; }\n.sl-insp .sl-ident b { font-weight: 600; }\n.sl-insp .sl-note { color: var(--muted, #666); }\n.sl-insp .seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--line, #ccc); border-radius: 4px; overflow: hidden; }\n.sl-insp .seg-opt { background: none; border: 0; cursor: pointer; color: var(--muted, #666);\n font: 400 11px ui-monospace, monospace; padding: 1px 6px; }\n.sl-insp .seg-opt + .seg-opt { border-left: 1px solid var(--line, #ccc); }\n.sl-insp .seg-opt.on { color: var(--ink, #222); font-weight: 600; background: var(--accent-soft, #e6eef0); cursor: default; }\n.sl-insp .shell-rung.stray { color: #9a3b2e; text-decoration: line-through; }\n`;\n\n/** Inject the shared panel stylesheet once. Exported so the bundle inspector\n * (bundle-inspector.ts) renders in the same CSS grammar. */\nexport function ensureInspectorStyle(): void {\n if (document.getElementById(STYLE_ID)) return;\n const style = document.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = CSS;\n document.head.append(style);\n}\n\nconst eq = (a: ScalarValue | undefined, b: ScalarValue | undefined): boolean =>\n JSON.stringify(a) === JSON.stringify(b);\n\n// --- the log panel (design 2.3) ---------------------------------------------\n\n/** The filterable kinds; a peek files under \"deal\" (both are asks). */\nconst LOG_KINDS = [\"deal\", \"play\", \"write\", \"evict\", \"turns\", \"diagnostic\"] as const;\nconst LOG_KIND_LABELS: Record<(typeof LOG_KINDS)[number], string> = {\n deal: \"Deal\", play: \"Play\", write: \"Write\", evict: \"Evict\", turns: \"Turns\", diagnostic: \"Diag\",\n};\n\nconst logKindOf = (e: LogEntry): (typeof LOG_KINDS)[number] =>\n e.type === \"peek\" ? \"deal\" : e.type === \"discard\" ? \"play\" : e.type;\n\nconst showVal = (v: ScalarValue | undefined): string =>\n v === undefined ? \"<unset>\" : JSON.stringify(v);\n\n/** One line per entry, `[turn]`-stamped where the event has a box context\n * (write lines share the state logger's `path: from -> to` reading). */\nexport function formatLogEntry(e: LogEntry | EngineLogEntry): string {\n // The run's log names the flow that acted, after the turn stamp and in the\n // same place all four examiners put it; a flow's own log omits it, because\n // its section heading already says whose it is.\n return formatLogBody(e, \"flow\" in e && e.flow ? `${e.flow} ` : \"\");\n}\n\nfunction formatLogBody(e: LogEntry, flow: string): string {\n const stamp = (e.turn !== undefined ? `[${e.turn}] ` : \"[-] \") + flow;\n switch (e.type) {\n case \"deal\": {\n const dealt = e.cards.filter((c) => c.verdict === \"dealt\").map((c) => c.id);\n return `${stamp}deal ${e.hand}: ${dealt.length > 0 ? dealt.join(\", \") : \"(none)\"} (${e.cards.length} considered)`;\n }\n case \"peek\": {\n const crit = Object.entries(e.criteria).map(([g, t]) => `${g}=${t}`).join(\", \");\n const listed = e.cards.filter((c) => c.verdict === \"dealt\").map((c) => c.id);\n return `${stamp}peek ${e.box}${crit ? ` [${crit}]` : \"\"}: `\n + `${listed.length > 0 ? listed.join(\", \") : \"(none)\"} (${e.cards.length} considered)`;\n }\n case \"evict\": return `${stamp}evict ${e.card} from ${e.hand} (${e.reason})`;\n // A card played with none (\"\") has no outcome to name.\n case \"play\": return `${stamp}play ${e.card}${e.outcome === \"\" ? \"\" : ` -> ${e.outcome}`}`;\n case \"write\": return `${stamp}write ${e.path}: ${showVal(e.prev)} -> ${showVal(e.value)}`;\n case \"turns\": return `${stamp}turns ${e.box} -> ${e.turn}`;\n // A card the game took out of its hand unplayed: filed with the plays.\n case \"discard\": return `${stamp}discard ${e.card} from ${e.hand}`;\n default: return `${stamp}diagnostic ${e.where}: ${e.message}`;\n }\n}\n\n/** The group label a row files under (\"world\", \"story\", \"box <id>\", ...). */\nconst groupOf = (path: string): string => {\n const parts = path.split(\".\");\n return parts.length === 3 ? `${parts[0]} ${parts[1]}` : parts[0]!;\n};\n\nexport function createPropertyInspector(engine: Engine, flow: Flow, opts: PropertyInspectorOptions = {}): PropertyInspector {\n ensureInspectorStyle();\n\n // loadGame rebuilds every flow and the handle we were given goes inert\n // (the runtime's stale-handle rule), so every read goes through this\n // accessor and Load state re-takes the same-named flow.\n let liveFlow = flow;\n const live = (): Flow => liveFlow;\n\n const el = document.createElement(\"div\");\n el.className = \"sl-insp\";\n\n // Header: the title plus Save state / Load state (the .storyletsave\n // string boundary, in every examiner - the parity rule, design 2.4).\n const head = document.createElement(\"div\");\n head.className = \"sl-head\";\n const h = document.createElement(\"h3\");\n h.textContent = opts.title ?? \"Runtime state\";\n head.append(h);\n\n const saveBtn = document.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.className = \"sl-save\";\n saveBtn.textContent = \"Save state\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = \"save.storyletsave\";\n a.click();\n URL.revokeObjectURL(url);\n });\n head.append(saveBtn);\n\n const filePicker = document.createElement(\"input\");\n filePicker.type = \"file\";\n filePicker.accept = \".storyletsave,application/json\";\n filePicker.hidden = true;\n filePicker.addEventListener(\"change\", () => {\n const file = filePicker.files?.[0];\n if (!file) return;\n const reader = new FileReader();\n reader.onload = () => {\n // A foreign or malformed blob is refused by deserializeState, never applied.\n try {\n deserializeState(engine, String(reader.result));\n liveFlow = engine.getFlow(flow.id) ?? engine.openFlow(flow.id);\n refresh();\n } catch (e) {\n console.error(\"storylets inspector: load failed:\", e instanceof Error ? e.message : e);\n }\n filePicker.value = \"\";\n };\n reader.readAsText(file);\n });\n\n const loadBtn = document.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.className = \"sl-load\";\n loadBtn.textContent = \"Load state\";\n loadBtn.addEventListener(\"click\", () => filePicker.click());\n head.append(loadBtn, filePicker);\n el.append(head);\n\n // The property filter (name/path substring, case-blind - the parity\n // member Unreal renders as an SSearchBox).\n const filter = document.createElement(\"input\");\n filter.type = \"text\";\n filter.className = \"sl-filter\";\n filter.placeholder = \"Filter properties\";\n el.append(filter);\n\n // Rows are built once: the declared surface is fixed for a bundle. The\n // filter only toggles visibility (a group hides with its last row).\n const editors: { row: PropertyRow; read: () => void }[] = [];\n const groups: { el: HTMLElement; rows: { el: HTMLElement; text: string }[] }[] = [];\n let lastGroup = \"\";\n for (const row of live().listProperties()) {\n const group = groupOf(row.path);\n if (group !== lastGroup) {\n const g = document.createElement(\"div\");\n g.className = \"sl-group\";\n g.textContent = group;\n el.append(g);\n groups.push({ el: g, rows: [] });\n lastGroup = group;\n }\n const rowEl = buildRow(live, row, editors);\n groups[groups.length - 1]!.rows.push({ el: rowEl, text: `${row.name} ${row.path}`.toLowerCase() });\n el.append(rowEl);\n }\n\n filter.addEventListener(\"input\", () => {\n const q = filter.value.trim().toLowerCase();\n for (const group of groups) {\n let any = false;\n for (const row of group.rows) {\n const show = q === \"\" || row.text.includes(q);\n row.el.style.display = show ? \"\" : \"none\";\n any = any || show;\n }\n group.el.style.display = any ? \"\" : \"none\";\n }\n });\n\n // Read-only: each box's clock, then the board's current hands (title or\n // gameId, never internal ids) - the same sections as the engine examiners.\n const turnsHead = document.createElement(\"div\");\n turnsHead.className = \"sl-section\";\n turnsHead.textContent = \"Turns (per box)\";\n const turnsBody = document.createElement(\"div\");\n turnsBody.className = \"sl-turns\";\n const boardHead = document.createElement(\"div\");\n boardHead.className = \"sl-section\";\n boardHead.textContent = \"Board\";\n const boardBody = document.createElement(\"div\");\n boardBody.className = \"sl-board\";\n el.append(turnsHead, turnsBody, boardHead, boardBody);\n\n // A retained log (design 2.3), behind per-kind filters, with Autoscroll,\n // Copy and Clear - the JS rendering of the engine examiners' log panel.\n // Built twice: once for the RUN (every flow's events in one order, each line\n // naming its flow) and once for this flow's own. Both exist because a flow's\n // log cannot show a story action in another flow moving shared state\n // (design/shared-scarcity.md 8.2). Empty until the engine had the log option.\n const check = (text: string, onChange: (on: boolean) => void, cls: string): HTMLLabelElement => {\n const label = document.createElement(\"label\");\n label.className = cls;\n const box = document.createElement(\"input\");\n box.type = \"checkbox\";\n box.checked = true;\n box.addEventListener(\"change\", () => onChange(box.checked));\n label.append(box, text);\n return label;\n };\n\n interface LogPanel { render: (force?: boolean) => void }\n const buildLogPanel = (\n which: \"flow\" | \"run\",\n caption: string,\n entriesOf: () => readonly (LogEntry | EngineLogEntry)[],\n clear: () => void,\n empty: string,\n ): LogPanel => {\n // Both panels carry the same controls, so each element takes a `sl-flow` /\n // `sl-run` modifier: without one, a selector for \"the Clear button\" is\n // ambiguous, which is exactly what the inspector test caught.\n const head = document.createElement(\"div\");\n head.className = `sl-section sl-${which}`;\n head.textContent = caption;\n const bar = document.createElement(\"div\");\n bar.className = `sl-logbar sl-${which}`;\n const body = document.createElement(\"div\");\n body.className = `sl-log sl-${which}`;\n\n const kindOn = new Map<string, boolean>();\n const visibleLines = (): string[] =>\n entriesOf().filter((e) => kindOn.get(logKindOf(e)) !== false).map(formatLogEntry);\n let autoscroll = true;\n let stampSeen = \"\";\n const render = (force = false): void => {\n const entries = entriesOf();\n const stamp = `${entries.length}:${entries.length > 0 ? entries[entries.length - 1]!.seq : -1}`;\n if (!force && stamp === stampSeen) return;\n stampSeen = stamp;\n const lines = visibleLines();\n body.textContent = lines.length > 0 ? lines.join(\"\\n\") : empty;\n if (autoscroll) body.scrollTop = body.scrollHeight;\n };\n\n for (const kind of LOG_KINDS) {\n kindOn.set(kind, true);\n bar.append(check(LOG_KIND_LABELS[kind], (on) => {\n kindOn.set(kind, on);\n render(true);\n }, \"sl-logkind\"));\n }\n bar.append(check(\"Autoscroll\", (on) => { autoscroll = on; }, \"sl-logscroll\"));\n const copyBtn = document.createElement(\"button\");\n copyBtn.type = \"button\";\n copyBtn.className = \"sl-logcopy\";\n copyBtn.textContent = \"Copy\";\n copyBtn.title = \"Copy the visible (filtered) log to the clipboard\";\n copyBtn.addEventListener(\"click\", () => { void navigator.clipboard?.writeText(visibleLines().join(\"\\n\")); });\n const clearBtn = document.createElement(\"button\");\n clearBtn.type = \"button\";\n clearBtn.className = \"sl-logclear\";\n clearBtn.textContent = \"Clear\";\n clearBtn.title = \"Drop the retained log entries (cosmetic - no game state changes)\";\n clearBtn.addEventListener(\"click\", () => { clear(); render(true); });\n bar.append(copyBtn, clearBtn);\n el.append(head, bar, body);\n return { render };\n };\n\n // This flow's own log first: it is what the panel was mounted on. The run's\n // log follows, because it is the wider view and it only earns its space once\n // a second flow exists.\n const flowLog = buildLogPanel(\"flow\", \"Log\", () => live().log(), () => live().clearLog(),\n \"(empty - new Engine(bundle, { log: true }) retains the flow log)\");\n const runLog = buildLogPanel(\"run\", \"Run log (every flow)\", () => engine.log(), () => engine.clearLog(),\n \"(empty - new Engine(bundle, { log: true }) retains the run log)\");\n const renderLog = (force = false): void => { runLog.render(force); flowLog.render(force); };\n\n const line = (parent: HTMLElement, text: string): void => {\n const div = document.createElement(\"div\");\n div.className = \"sl-line\";\n div.textContent = text;\n parent.append(div);\n };\n const readLive = (): void => {\n turnsBody.textContent = \"\";\n for (const box of live().listBoxes()) {\n line(turnsBody, `${box.title ?? box.gameId}: turn ${box.turn}`);\n }\n boardBody.textContent = \"\";\n for (const [hand, cards] of Object.entries(live().board())) {\n const names = cards.map((c) => c.title ?? c.gameId);\n line(boardBody, `${hand}: ${names.length > 0 ? names.join(\", \") : \"(empty)\"}`);\n }\n };\n\n const refresh = (): void => {\n for (const e of editors) e.read();\n readLive();\n renderLog();\n };\n readLive();\n renderLog(true);\n\n let timer: ReturnType<typeof setInterval> | undefined;\n const pollMs = opts.pollMs ?? 250;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n (opts.container ?? document.body).append(el);\n return {\n el,\n refresh,\n destroy(): void {\n if (timer !== undefined) clearInterval(timer);\n el.remove();\n },\n };\n}\n\nfunction buildRow(\n live: () => Flow,\n row: PropertyRow,\n editors: { row: PropertyRow; read: () => void }[],\n): HTMLElement {\n const div = document.createElement(\"div\");\n div.className = \"sl-row\";\n const name = document.createElement(\"span\");\n name.className = \"sl-name\";\n name.textContent = row.name;\n name.title = row.path;\n\n const current = (): ScalarValue | undefined => {\n try { return live().getProperty(row.path); } catch { return undefined; }\n };\n // Forward-declared so commit can refresh the whole row (widget + reset\n // state) once everything below is built.\n let sync: () => void = () => {};\n const commit = (value: ScalarValue): void => { live().setProperty(row.path, value); sync(); };\n\n const reset = document.createElement(\"button\");\n reset.type = \"button\";\n reset.className = \"sl-reset\";\n reset.textContent = \"↺\";\n reset.title = \"Reset to default\";\n reset.addEventListener(\"click\", () => commit(row.default));\n\n let widget: HTMLElement;\n let read: () => void;\n const focused = (w: HTMLElement): boolean => document.activeElement === w;\n\n switch (row.type) {\n case \"boolean\": {\n const input = document.createElement(\"input\");\n input.type = \"checkbox\";\n input.addEventListener(\"change\", () => commit(input.checked));\n widget = input;\n read = () => { if (!focused(input)) input.checked = current() === true; };\n break;\n }\n case \"number\": {\n const input = document.createElement(\"input\");\n input.type = \"number\";\n input.addEventListener(\"change\", () => commit(Number(input.value)));\n widget = input;\n read = () => { if (!focused(input)) input.value = String(current() ?? 0); };\n break;\n }\n case \"quality\": {\n // A quality shows as its LADDER, the current stage marked, as in both\n // editors: the family's one strip (@wildwinter/app-shell/value-control).\n // Closed like an enum: an unknown stage is not a harmless typo, since the\n // evaluator refuses it, so there is no free text to slip in.\n const holder = document.createElement(\"span\");\n const draw = (): void => {\n const v = current();\n holder.replaceChildren(rungStrip({ stages: row.stages ?? [], value: typeof v === \"string\" ? v : undefined, decl: row,\n onPick: (stage) => { commit(stage); draw(); }, doc: document }));\n };\n widget = holder;\n read = draw;\n break;\n }\n case \"enum\": {\n const select = document.createElement(\"select\");\n for (const v of row.values ?? []) {\n const o = document.createElement(\"option\");\n o.value = v;\n o.textContent = v;\n select.append(o);\n }\n select.addEventListener(\"change\", () => commit(select.value));\n widget = select;\n read = () => { if (!focused(select)) select.value = String(current() ?? \"\"); };\n break;\n }\n case \"flags\": {\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.placeholder = \"comma, separated, flags\";\n input.addEventListener(\"change\", () =>\n commit(input.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n widget = input;\n read = () => { if (!focused(input)) input.value = ((current() as string[] | undefined) ?? []).join(\", \"); };\n break;\n }\n default: { // string\n const input = document.createElement(\"input\");\n input.type = \"text\";\n input.addEventListener(\"change\", () => commit(input.value));\n widget = input;\n read = () => { if (!focused(input)) input.value = String(current() ?? \"\"); };\n }\n }\n\n const readAll = (): void => { read(); reset.disabled = eq(current(), row.default); };\n sync = readAll;\n readAll();\n editors.push({ row, read: readAll });\n div.append(name, widget, reset);\n return div;\n}\n","// ---------------------------------------------------------------------------\n// The bundle inspector, JS idiom (design/engine-runtimes.md 2, piece 6).\n//\n// describeBundle() IS the JS half of the parity member - JS has no engine\n// asset pipeline to hang an editor view off - so this is the optional DOM\n// rendering of it: a read-only panel over a compiled bundle, in the property\n// examiner's CSS grammar (inspector.ts), with NO session anywhere. Identity\n// first, then collapsible sections: hands (the deal() surface), tags by box\n// (the peek() criteria surface), declared properties, counts.\n//\n// Read-only by construction: there is no state to edit here, only the shape\n// that shipped.\n// ---------------------------------------------------------------------------\n/// <reference lib=\"dom\" />\n\nimport { describeBundle } from \"@storylet-studio/runtime\";\nimport type {\n BundleDescription, PropertyScopeSummary, PropertySummary,\n} from \"@storylet-studio/runtime\";\nimport type { Bundle, ScalarValue } from \"@storylet-studio/model\";\nimport { ensureInspectorStyle } from \"./inspector.js\";\n\nexport interface BundleInspectorOptions {\n /** Mount point; defaults to document.body. */\n container?: HTMLElement;\n title?: string;\n /** Start the collapsible sections open (default true). */\n open?: boolean;\n}\n\nexport interface BundleInspector {\n el: HTMLElement;\n /** The description this panel rendered (the API is the parity member). */\n description: BundleDescription;\n destroy(): void;\n}\n\nconst showVal = (v: ScalarValue | undefined): string =>\n v === undefined ? \"<unset>\" : JSON.stringify(v);\n\n/** \"name: type = default\", plus enum/flags options where declared, plus\n * \"(durable)\" where the declaration says the value outlives a run\n * (design/engine-server.md 4.2). Nothing is added for the ordinary\n * run-scoped property: that is what a property is. */\nexport function formatPropertySummary(p: PropertySummary): string {\n const options = p.values !== undefined && p.values.length > 0 ? ` [${p.values.join(\", \")}]` : \"\";\n const durable = p.durable === true ? \" (durable)\" : \"\";\n return `${p.name}: ${p.type} = ${showVal(p.default)}${options}${durable}`;\n}\n\n/** The scope label a declaration block files under (\"world\", \"player\", \"box box\",\n * \"tag docks (zone)\"). A host scope is named by its token, as @world is. */\nexport function formatScopeLabel(scope: PropertyScopeSummary): string {\n if (scope.scope === \"world\" || scope.scope === \"story\") return scope.scope;\n if (scope.scope === \"host\") return scope.owner;\n const group = scope.group !== undefined ? ` (${scope.group})` : \"\";\n return `${scope.scope} ${scope.owner}${group}`;\n}\n\nconst line = (parent: HTMLElement, text: string, cls = \"sl-line\"): HTMLElement => {\n const div = document.createElement(\"div\");\n div.className = cls;\n div.textContent = text;\n parent.append(div);\n return div;\n};\n\n/** A collapsible section in the examiner's grammar. */\nconst fold = (parent: HTMLElement, label: string, open: boolean): HTMLElement => {\n const details = document.createElement(\"details\");\n details.className = \"sl-fold\";\n details.open = open;\n const summary = document.createElement(\"summary\");\n summary.textContent = label;\n details.append(summary);\n const body = document.createElement(\"div\");\n details.append(body);\n parent.append(details);\n return body;\n};\n\n/** Render a read-only summary of a compiled bundle: what an integrator may\n * call, with no session and no game running. */\nexport function createBundleInspector(\n bundle: Bundle,\n opts: BundleInspectorOptions = {},\n): BundleInspector {\n ensureInspectorStyle();\n const description = describeBundle(bundle);\n const open = opts.open ?? true;\n\n const el = document.createElement(\"div\");\n el.className = \"sl-insp\";\n\n const head = document.createElement(\"div\");\n head.className = \"sl-head\";\n const h = document.createElement(\"h3\");\n h.textContent = opts.title ?? \"Bundle\";\n head.append(h);\n el.append(head);\n\n // --- identity (always visible: which bundle is this?) --------------------\n const { identity, totals } = description;\n const ident = document.createElement(\"div\");\n ident.className = \"sl-ident\";\n el.append(ident);\n line(ident, `${identity.project} ${identity.version}`, \"sl-line\");\n line(ident, `schema ${identity.schema}`, \"sl-line sl-note\");\n line(ident, `hash ${identity.hash === \"\" ? \"(none)\" : identity.hash} - metadata ${identity.metadata}`,\n \"sl-line sl-note\");\n\n // --- hands: the deal() surface -------------------------------------------\n const handsBody = fold(el, \"Hands (deal)\", open);\n handsBody.className = \"sl-hands\";\n if (description.hands.length === 0) {\n line(handsBody, \"(no hands - this bundle is peek-only)\", \"sl-line sl-note\");\n }\n for (const hand of description.hands) {\n const template = hand.template !== undefined ? `, template ${hand.template}` : \"\";\n // A movable hole is the one thing about a hand its name cannot say: write\n // that property and the hand moves (4.6).\n const moves = hand.movable === undefined ? \"\"\n : `, moves ${hand.movable.map((m) => `${m.group} from ${m.from}`).join(\" and \")}`;\n line(handsBody, `${hand.gameId}: box ${hand.box}, slots ${hand.slots}${template}${moves}`\n + (hand.title !== undefined ? ` - ${hand.title}` : \"\"));\n }\n\n // --- tag groups by box: the peek() criteria surface ----------------------\n const tagsBody = fold(el, \"Tags by box (peek criteria)\", open);\n tagsBody.className = \"sl-tags\";\n for (const box of description.boxes) {\n line(tagsBody, `${box.title ?? box.gameId}`, \"sl-group\");\n if (box.tagGroups.length === 0) {\n line(tagsBody, \" (no tag groups)\", \"sl-line sl-note\");\n }\n for (const group of box.tagGroups) {\n line(tagsBody, ` ${group.gameId}: ${group.tags.length > 0 ? group.tags.join(\", \") : \"(no tags)\"}`);\n }\n }\n\n // --- declared properties: what expressions read, what a host may set ----\n const propsBody = fold(el, \"Properties (declared)\", open);\n propsBody.className = \"sl-props\";\n for (const scope of description.properties) {\n line(propsBody, formatScopeLabel(scope), \"sl-group\");\n if (scope.properties.length === 0) {\n line(propsBody, \" (none declared)\", \"sl-line sl-note\");\n }\n for (const p of scope.properties) {\n line(propsBody, ` ${formatPropertySummary(p)}`);\n }\n }\n\n // --- the project map: which boxes are on it, and any geometry carried ---\n //\n // Only when there IS one. An empty section on every ordinary bundle would\n // teach the reader to skip a section that only ever matters when it is not\n // empty. The zones themselves are already under Properties and the opted-in\n // boxes' peek criteria; this says where they live and what the build carried\n // besides, since the geometry is inert and would otherwise go unmentioned.\n const map = description.map;\n if (map !== undefined) {\n const mapBody = fold(el, \"Project map\", open);\n mapBody.className = \"sl-maps\";\n line(mapBody, `${map.group}: ${map.tags.join(\", \")}`);\n line(mapBody, `boxes on the map: ${map.boxes.length > 0 ? map.boxes.join(\", \") : \"none\"}`);\n const sites = Object.entries(map.sites).map(([box, n]) => `${box} ${n}`).join(\", \");\n line(mapBody, `geometry carried (the engine ignores it): zones ${map.zones}, pictures ${map.backgrounds}`\n + `, hands ${sites === \"\" ? \"0\" : sites}`, \"sl-line sl-note\");\n }\n\n // --- counts: orientation, not inventory ---------------------------------\n const countsBody = fold(el, \"Counts\", open);\n countsBody.className = \"sl-counts\";\n line(countsBody, `boxes ${totals.boxes} - decks ${totals.decks} - cards ${totals.cards}`);\n line(countsBody, `hands ${totals.hands} - templates ${totals.templates} - tag groups ${totals.tagGroups}`);\n for (const box of description.boxes) {\n // A timed box says its unit here (design/engine-server.md 4.8), because\n // this is the line an integrator reads to find out what their host has to\n // tick. Nothing is added for an ordinary box: the answer \"a turn is a\n // play\" belongs in the docs, not on every line of every bundle.\n line(countsBody, `${box.gameId}: decks ${box.counts.decks}, cards ${box.counts.cards}, `\n + `hands ${box.counts.hands}, templates ${box.counts.templates}, `\n + `tag groups ${box.counts.tagGroups}, ranking.specificity ${box.ranking.specificity}`\n + (box.turn === undefined ? \"\" : box.turn.seconds !== undefined ? `, turn = ${box.turn.seconds}s` : \", turn moved by the game\")\n // Only when there are any: a box whose cards all come back with the run\n // has nothing for a server to lift, and the zero would be noise.\n + (box.durableCards !== undefined ? `, durable cards ${box.durableCards}` : \"\"));\n }\n\n (opts.container ?? document.body).append(el);\n return {\n el,\n description,\n destroy(): void {\n el.remove();\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live Link (design/live-link.md): the game-side client.\n//\n// Joins a running game to Storyletter over a loopback WebSocket. Two things\n// travel on it: the flow's trace stream and board snapshots go UP, so the\n// editor's Board can show the game's run instead of its own (observe-only: the\n// editor never drives the game); freshly compiled bundles come DOWN after a\n// save, so the run picks up the edit without restarting (applyLiveBundle in\n// refresh.ts does the swap).\n//\n// Wire protocol `storyletengine/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:2, build, project?, boxes?, flows:[id...] }\n// - on open, and again on setBuild\n// flowOpen / flowClose : { t:\"flowOpen\"|\"flowClose\", flow }\n// - a flow appeared or went\n// trace : { t:\"trace\", flow, event } - every TraceEvent any flow emits\n// board : { t:\"board\", flow, hands:{ hand: [card...] }, turns:{ box: n } }\n// - after hello, and after every deal /\n// play / evict / turns event\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: the full .storyletsc\n// JSON as a string\n// Identity in frames is by gameId (hands, boxes, cards), and since 4.4 that\n// holds for the trace event too: it is forwarded verbatim, and the runtime's\n// own ids are gameIds now, so the rule has no exception left.\n//\n// Patterpad's createDebugLink is the template (Patter play-helpers/debug.ts):\n// hello first, frames queue until the first socket opens, a missing editor is\n// a silent no-op, nothing here ever throws into the game, and no WebSocket\n// implementation at all degrades to a no-op handle.\n//\n// It reconnects, by the rule Patter's debug link shares: until the game closes\n// it, a socket that closes or never opens is tried again after 1 s, doubling to\n// 10 s, and back to 1 s once a hello goes. Each new socket says hello again with\n// the build and the open flows, then every flow's board. One line is logged when\n// an open link is lost and one when it is back; a link that never opened logs\n// nothing. Frames queue only while the first socket connects: after any attempt\n// fails, nothing queues until a socket is open. The editor closes a game it\n// replaced (another game connected) with code 4000, and that one does not\n// retry, so two games do not take turns. `observe(...)` became a\n// trace subscription, which is why this one takes an ENGINE: attach(engine)\n// subscribes, detach() stops, and a live refresh replaces the flow\n// (detach the old one, attach the new one, then setBuild).\n//\n// const link = createLiveLink({ build: bundle.content.hash, onBundle: ... });\n// link.attach(engine); // the ENGINE: the link discovers your flows itself\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow, TraceEvent } from \"@storylet-studio/runtime\";\n\n/** A minimal structural type for a WebSocket implementation (browsers and\n * Node 22+ have a global one). */\nexport interface LiveSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"error\", listener: () => void): void;\n /** The close event's `code` says why: 4000 is the editor replacing this\n * game with another, which is not retried. */\n addEventListener(type: \"close\", listener: (ev?: { code?: number }) => void): void;\n /** Incoming editor messages (the pushed bundle). Optional so a bare\n * send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype LiveSocketCtor = new (url: string) => LiveSocketLike;\n\nexport interface LiveLinkOptions {\n /** The running bundle's build identity: pass `bundle.content.hash`. The\n * editor compares it with its own compiled hash (in sync / stale). */\n build: string;\n /** Optional project name, shown in the editor's connect-chip tooltip. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4472`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (tests with a\n * fake socket, or a host without a global WebSocket). */\n WebSocket?: LiveSocketCtor;\n /** Live refresh: the editor pushed a freshly compiled bundle. `data` is\n * the .storyletsc JSON; hand it (with your current Engine) to\n * `applyLiveBundle`, `attach` the engine it returns, then call\n * `link.setBuild(build)`. Never called with a malformed frame. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\n/** Where a link is: the same three words Unity's LiveLinkState, Godot's\n * `link_state()` and Unreal's `LinkState` carry. */\nexport type LiveLinkState = \"connecting\" | \"connected\" | \"closed\";\n\nexport interface LiveLink {\n /** Where the link is, read at the moment of asking: \"connecting\" until a\n * socket opens and while it retries after one closed or failed, \"connected\"\n * while one is open, \"closed\" once `close()` was called, the editor replaced\n * this game with another (close code 4000), or there was no WebSocket to\n * open. An\n * examiner shows it so a host can tell \"the editor is not listening\" from\n * \"I never attached\". */\n readonly state: LiveLinkState;\n /** Start forwarding this ENGINE's trace: every flow's events, each frame\n * naming the flow it came from, so the editor can follow one participant\n * and switch. An earlier engine is detached first. Sends a board snapshot\n * per open flow straight away, queued behind the hello if the socket is\n * not open yet.\n *\n * Flows are discovered rather than declared: the link diffs `engine.flows()`\n * whenever anything happens and emits `flowOpen` / `flowClose` itself. That\n * is a deliberate departure from Patterplay, whose host calls `FlowOpened`\n * by hand - it has no engine-level trace tap to hang the diff on and we do,\n * so the host has nothing to remember and cannot get the editor's flow list\n * wrong. The one cost: a flow that opens and then does nothing at all is not\n * announced until the next event anywhere in the run. */\n attach(engine: Engine): void;\n /** Stop forwarding. A refresh replaces the engine, so attach the new one\n * afterwards. */\n detach(): void;\n /** After applying a pushed bundle: report the build now running (re-hellos\n * with the new build and a fresh board snapshot, so the editor's chip goes\n * back to in sync and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link and stop retrying; every later call is a no-op. */\n close(): void;\n}\n\n/** One game-to-editor frame, as the client serialises it. Exported for the\n * fixture test; hosts never build these by hand. */\nexport type LiveFrame =\n | { t: \"hello\"; v: 2; build: string; project?: string; boxes?: string[]; flows: string[] }\n | { t: \"flowOpen\"; flow: string }\n | { t: \"flowClose\"; flow: string }\n | { t: \"trace\"; flow: string; event: TraceEvent }\n | { t: \"board\"; flow: string; hands: Record<string, string[]>; turns: Record<string, number> };\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/** How many frames may wait for a socket that has not opened yet. Generous:\n * the point of queueing is that a game's first moments are not lost while the\n * editor's socket is still connecting. */\nconst QUEUE_CAP = 512;\nconst DEFAULT_URL = \"ws://127.0.0.1:4472\";\n/** The editor closed this game because another connected: do not retry. */\nconst LIVE_LINK_REPLACED = 4000;\n/** The retry's first wait and its longest, in ms. */\nconst RETRY_FIRST = 1000;\nconst RETRY_MAX = 10000;\n\n/** The trace kinds that move the board, and so are followed by a snapshot. */\n// A discard changes the board as a play does.\nconst BOARD_EVENTS: ReadonlySet<TraceEvent[\"type\"]> = new Set([\"deal\", \"play\", \"discard\", \"evict\", \"turns\"]);\n\n/** The cheap snapshot: hands by gameId holding card gameIds in dealt order,\n * and every box's clock by gameId. */\nexport function boardFrame(flow: Flow): Extract<LiveFrame, { t: \"board\" }> {\n const id = flow.id;\n const hands: Record<string, string[]> = {};\n for (const [hand, cards] of Object.entries(flow.board())) hands[hand] = cards.map((c) => c.gameId);\n const turns: Record<string, number> = {};\n for (const box of flow.listBoxes()) turns[box.gameId] = box.turn;\n return { t: \"board\", flow: id, hands, turns };\n}\n\n/**\n * Open a Live Link to Storyletter. Returns a handle whose calls are no-ops once\n * the editor disconnects or if it was never listening: safe to leave wired into\n * a shipping build behind a flag.\n */\nexport function createLiveLink(opts: LiveLinkOptions): LiveLink {\n const url = opts.url ?? DEFAULT_URL;\n const Ctor: LiveSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: LiveSocketCtor }).WebSocket;\n let queue: string[] = [];\n let sock: LiveSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live refresh lands\n let engine: Engine | null = null;\n let unsubscribe: (() => void) | null = null;\n // The flows the EDITOR believes are open. Diffed against engine.flows() so\n // flowOpen / flowClose are the link's own business, not the host's.\n let announced = new Set<string>();\n let dropped = false; // an attempt has failed or closed: queue nothing until a socket opens\n let wasOpen = false; // a socket has been open, so losing it is worth a line\n let lost = false; // logged as lost, waiting to log it back\n let wait = RETRY_FIRST;\n let retry: ReturnType<typeof setTimeout> | null = null;\n\n if (!Ctor) {\n // No WebSocket available (no global, none passed): a silent no-op link.\n return { state: \"closed\", attach() {}, detach() {}, setBuild() {}, close() { closed = true; } };\n }\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n const post = (frame: LiveFrame): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n // After an attempt has failed, the editor is away: nothing queues until a\n // socket is open again, and the hello and boards stand in for what was\n // missed (the 2026-08-29 fix, kept across retries).\n if (dropped && sock.readyState !== OPEN) return;\n queue.push(JSON.stringify(frame));\n // A cap for the CONNECTING window, where queueing is the point: the hello\n // and the frames a game emits during those first milliseconds have to\n // land. Beyond that many, the editor is not coming: drop the oldest, so\n // what survives is the most recent story rather than the first moments of\n // it. Once a socket has closed or failed the queue is dropped outright\n // (see the close listener); this is only the first connecting window.\n if (queue.length > QUEUE_CAP) queue.splice(0, queue.length - QUEUE_CAP);\n flush();\n };\n\n // The handshake goes straight to the socket, never through the queue: it\n // must be the first thing the editor reads, ahead of anything queued while\n // the socket was still connecting.\n const liveFlows = (): Flow[] => {\n try { return engine ? engine.flows() : []; } catch { return []; }\n };\n const sendHello = (): void => {\n const flows = liveFlows();\n const hello: LiveFrame = { t: \"hello\", v: 2, build, flows: flows.map((f) => f.id) };\n if (opts.project !== undefined) hello.project = opts.project;\n const first = flows[0];\n if (first) {\n try { hello.boxes = first.listBoxes().map((b) => b.gameId); } catch { /* mid-swap: no boxes */ }\n }\n // The editor's list starts from the hello, so the diff starts there too.\n announced = new Set(flows.map((f) => f.id));\n try { sock?.send(JSON.stringify(hello)); wait = RETRY_FIRST; } catch { /* race: closed immediately */ }\n };\n const postBoard = (flow: Flow): void => {\n try { post(boardFrame(flow)); } catch { /* never into the game */ }\n };\n /** Announce anything that opened or closed since the last look. Runs before\n * each forwarded event, so a frame never names a flow the editor has not\n * been told about. */\n const syncFlows = (): void => {\n const now = liveFlows();\n const ids = new Set(now.map((f) => f.id));\n for (const f of now) {\n if (announced.has(f.id)) continue;\n announced.add(f.id);\n post({ t: \"flowOpen\", flow: f.id });\n postBoard(f);\n }\n for (const id of [...announced]) {\n if (ids.has(id)) continue;\n announced.delete(id);\n post({ t: \"flowClose\", flow: id });\n }\n };\n const onTrace = (flowId: string, event: TraceEvent): void => {\n try {\n syncFlows();\n post({ t: \"trace\", flow: flowId, event });\n if (BOARD_EVENTS.has(event.type)) {\n const f = engine?.getFlow(flowId);\n if (f) postBoard(f);\n }\n } catch { /* never into the game */ }\n };\n\n const connect = (): void => {\n retry = null;\n if (closed) return;\n let s: LiveSocketLike;\n try { s = new Ctor(url); } catch { sock = null; closed = true; return; } // malformed URL etc.: never throw into the game\n sock = s;\n s.addEventListener(\"open\", () => {\n if (sock !== s) return;\n wasOpen = true;\n if (lost) { lost = false; console.info(`[storylets] Live Link is back (${url}).`); }\n sendHello();\n // A reconnect: nothing was queued while the editor was away, so every\n // flow's board follows the hello and the editor's Board comes back as\n // the game has it. (The first socket's boards are already queued.)\n if (dropped) for (const f of liveFlows()) postBoard(f);\n flush();\n });\n // Live refresh: the editor pushed a new bundle. The shape is checked here\n // so the host's handler never sees a malformed frame; anything else the\n // editor might send is ignored.\n s.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n s.addEventListener(\"error\", () => { /* editor not listening: the close that follows retries */ });\n // The socket is gone: drop the queue (nothing queues until the next socket\n // opens, so a game that shipped with the link wired in does no work per\n // event while the editor is away), then retry unless the game closed the\n // link or the editor replaced this game with another.\n s.addEventListener(\"close\", (ev?: { code?: number }) => {\n if (sock !== s) return;\n sock = null; queue = []; dropped = true;\n if (closed) return;\n if (ev?.code === LIVE_LINK_REPLACED) {\n closed = true;\n console.info(`[storylets] Live Link: another game took the editor's link (${url}).`);\n return;\n }\n if (wasOpen && !lost) { lost = true; console.info(`[storylets] Live Link lost (${url}); retrying.`); }\n retry = setTimeout(connect, wait);\n wait = Math.min(wait * 2, RETRY_MAX);\n });\n };\n connect();\n\n const detach = (): void => {\n unsubscribe?.();\n unsubscribe = null;\n engine = null;\n announced = new Set();\n };\n\n return {\n get state(): LiveLinkState {\n if (closed) return \"closed\";\n // Between attempts there is no socket, but the link is still trying.\n return sock !== null && sock.readyState === OPEN ? \"connected\" : \"connecting\";\n },\n attach(next: Engine): void {\n if (closed) return;\n detach();\n engine = next;\n try { unsubscribe = next.subscribeTrace(onTrace); } catch { engine = null; return; }\n // Every open flow's board up front: the editor can show any of them the\n // moment it connects, without waiting for that participant to move.\n announced = new Set(liveFlows().map((f) => f.id));\n for (const f of liveFlows()) postBoard(f);\n },\n detach,\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n // Re-handshake: the editor re-reads the build, then gets every flow's\n // table as the new engine has it.\n if (sock && sock.readyState === OPEN) {\n sendHello();\n for (const f of liveFlows()) postBoard(f);\n }\n },\n close(): void {\n closed = true;\n detach();\n queue = [];\n if (retry) { clearTimeout(retry); retry = null; }\n try { sock?.close(); } catch { /* already gone */ }\n sock = null;\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live refresh (design/live-link.md): the game-side applier. The editor pushes\n// a freshly compiled bundle over the Live Link (createLiveLink's `onBundle`);\n// this swaps it in under the running engine through engine.hotSwap: a new\n// Engine over the new bundle, carrying the old one's run, on the same registry. The runtime's loadGame() already\n// tolerates edited content (a deleted card leaves the table, orphaned\n// cooldowns and hand contents drop, a new property takes its default), so the\n// run carries across - every flow of it; it refuses only a save from another\n// project.\n//\n// Patter's applyLiveBundle has two tiers (strings-only vs hot swap) because it\n// has string tables and a cursor to re-find; we have neither, so this is the\n// one tier. Wire-up:\n//\n// let engine = new Engine(bundle, { seed: 7, log: true });\n// let flow = engine.openFlow(\"main\");\n// const link = createLiveLink({\n// build: bundle.content.hash,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, data, { log: true });\n// if (!r.ok) return console.warn(r.error);\n// engine = r.engine; // re-bind your handles: loadGame\n// flow = engine.getFlow(\"main\") // rebuilt every flow, so the old\n// ?? engine.openFlow(\"main\"); // Flow objects are inert\n// link.attach(engine); // re-attach the ENGINE: loadGame rebuilt every flow\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport type { Bundle, LoadReport } from \"@storylet-studio/model\";\nimport { Engine } from \"@storylet-studio/runtime\";\nimport type { EngineOptions } from \"@storylet-studio/runtime\";\n\nexport type LiveBundleResult =\n /** The new engine, carrying the old one's run (all flows), the bundle it\n * runs, and the report its load produced: what the edit cost the run\n * (cards evicted, properties dropped, defaulted or retyped). */\n | { ok: true; engine: Engine; bundle: Bundle; report: LoadReport }\n /** Nothing changed: keep the engine you have. */\n | { ok: false; error: string };\n\n/**\n * Apply a bundle the editor pushed over the Live Link through the engine's own\n * `hotSwap`, returning the replacement. Never throws; a failure (unparseable\n * JSON, a bundle the runtime rejects, a different project) comes back as\n * `{ ok: false, error }` and the old engine is left as it was.\n *\n * Works whether the engine made its own registry or was given the game's: on\n * the game's registry the old engine hands its keys to the replacement, which a\n * plain save and load into a second engine cannot do (the two would clash).\n *\n * The engine remembers the options it was built with (seed, log, hostScopes,\n * registry), so `opts` is only for overriding one of them for the replacement.\n */\nexport function applyLiveBundle(engine: Engine, bundleJson: string, opts: EngineOptions = {}): LiveBundleResult {\n let bundle: Bundle;\n try {\n bundle = JSON.parse(bundleJson) as Bundle;\n } catch {\n return { ok: false, error: \"pushed bundle is not valid JSON\" };\n }\n try {\n // The engine's own hotSwap: on the game's registry the old engine has to hand\n // its keys over, which a plain save and load into a new engine cannot do.\n const { engine: next, report } = engine.hotSwap(bundle, opts);\n return { ok: true, engine: next, bundle, report };\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n","// ---------------------------------------------------------------------------\n// The host's container for a host scope, @world or any other the bundle\n// declares (design/flows.md; engine-runtimes.md 3.1).\n//\n// A host scope is the game's own state: the engine resolves it through a\n// resolver and NEVER saves it - \"host saves its container once, each engine saves its\n// own envelope\". A real game binds its own state here; a host that has no\n// state of its own (the demos, the playable page, the Board) uses this\n// ready-made container so @world still persists across its save/load.\n//\n// This is also what keeps a mixed Patter + Storylet Engine game honest: ONE\n// container, both engines mounting it foreign, neither writing it into its\n// envelope.\n// ---------------------------------------------------------------------------\n\nimport { PropertyBag as StateBag } from \"@wildwinter/scoperegistry\";\nimport type { ScalarValue } from \"@wildwinter/expr\";\nimport type { ScopeResolver } from \"@wildwinter/expr\";\nimport { hostScopeDecls } from \"@storylet-studio/model\";\nimport type { Bundle, PropertyBag } from \"@storylet-studio/model\";\n\nexport interface WorldContainer {\n /** Pass as `new Engine(bundle, { hostScopes: { world: container.resolver } })`. */\n resolver: ScopeResolver;\n /** The kernel bag itself (subscribe, audit, rows live there) - mount it\n * into a state logger or examiner beside the engine's own bags. Writing it\n * DIRECTLY is writing the kernel, so a `writable: false` declaration asks\n * the kernel's question: pass `{ host: true }` to say the game is speaking\n * (`bag.set(name, value, { host: true })`). Through `resolver` or an\n * engine's setProperty that is already answered. */\n bag: StateBag;\n /** The current values, for saving beside the engine's envelope. */\n values(): PropertyBag;\n /** Restore saved values over fresh defaults: orphaned keys drop, new\n * declarations keep their defaults - the same drift rule as loadGame. */\n load(values: PropertyBag): void;\n}\n\n/** A container seeded from one host scope's declarations in the bundle:\n * `@world`'s unless `token` names another (`\"player\"`).\n *\n * The container is the GAME's state, so it WRITES - even a declaration\n * carrying `writable: false`. That flag is the STORY's promise not to write\n * the value (Reboot.md 10), and the engine keeps it where the story writes: an\n * outcome is refused against the engine's read-only table before it ever\n * reaches this resolver. Enforcing it here as well refused the HOST too - the\n * clock the game must move, the harness driving the value it is testing\n * against - which is the opposite of what the flag says.\n *\n * So the declarations are seeded AS DECLARED, which is what an examiner over\n * this container should read, and the writes go through as HOST writes\n * (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the\n * engine's own doorway and passes the flag too: the engine has already sorted\n * story from host by then - a story write was refused earlier, a host write is\n * the only kind that arrives - and a resolver takes a name and a value with no\n * room to say which. A game wanting a rule of its own binds its own resolver\n * rather than this one; the ports' container (Unreal's UStoryletWorld) draws\n * the same line, with HostSet never refused and StorySet asking the game's own\n * read-only list. */\nexport function createWorldContainer(bundle: Bundle, token = \"world\"): WorldContainer {\n const bag = new StateBag(hostScopeDecls(bundle, token), { normalise: (n) => n });\n return {\n resolver: {\n get: (n) => bag.get(n),\n set: (n: string, v: ScalarValue) => { bag.set(n, v, { host: true }); },\n },\n bag,\n values: () => bag.values,\n load: (values) => bag.load(values),\n };\n}\n"],"mappings":"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","names":["browser_exports","__export","DURABLE_SCHEMA","Engine","Flow","STORYLET_ENGINE_VERSION","StoryletError","applyLiveBundle","boardFrame","compareStages","createBundleInspector","createStateLogger","createLiveLink","createPropertyInspector","createWorldContainer","describeBundle","deserializeState","diffState","effectiveFields","ensureInspectorStyle","extensionDecls","extensionsOf","fieldDecls","fieldValue","formatLogEntry","formatPropertySummary","formatScopeLabel","loadState","makePrng","nextStage","outcomeFieldDecls","saveState","serializeState","shuffleInPlace","snapshotState","stageIndex","versionInRange","own","record","key","deserialiseAst","node","args","EvalError","message","evaluate","node","ctx","dialect","missingPolicy","s","rec","n","scope","own","val","arg","ladder","ladderOf","current","stageIndex","def","l","r","left","right","lLadder","rLadder","sameLadder","valueEquals","assertNumbers","JOIN_TEXT","a","b","x","y","i","op","value","toUint32","seed","modded","makePrng","s","shuffleInPlace","arr","prng","i","j","ladderOf","ladder","typeWord","v","stageIndex","value","positionOf","stages","EvalError","i","compareStages","a","b","d","nextStage","valueFits","decl","value","opts","f","CHECK_FLAGS_COUNTING_CALL","node","DEFAULT_COUNTING_CALLS","matchedSpecificity","evalTruthy","opts","countingCalls","walk","want","l","r","rule","c","operands","holds","ENGINE_SCOPES","NEVER_PLAYED","host","h","stringArg","fn","args","i","v","EvalError","flagsArg","OWN_SCOPES","EXTERNAL_SCOPES","ENGINE_SCOPES","s","t","storyletsDialect","token","nextRandom","a","b","lo","hi","flags","arg","result","idx","card","dimension","value","gameIdify","text","bundleHostScopes","of","legacy","hostScopeSpec","token","s","hostScopeDecls","effectiveGameId","entity","pinned","gameIdify","valueAddresses","bundle","tags","box","boxGameId","group","tag","gameId","forms","list","print","accept","repeated","zoneQualified","candidates","ambiguous","zoneQualifiedValueAddressMessage","segment","zone","name","ambiguousValueAddressMessage","q","PLACE_GROUP","HOLE_REF","isHoleRef","value","parseHoleRef","m","BUNDLE_SCHEMA","BUNDLE_SCHEMA_V1","BUNDLE_SCHEMA_V0","BUNDLE_SCHEMAS","groupsOfBox","bundle","box","allTagGroups","bundle","groups","box","SAVE_SCHEMA","SAVE_SCHEMA_V2","SAVE_SCHEMA_V1","SAVEFILE_SCHEMA","SHARD_EXTENSIONS","MANIFEST_EXTENSION","SOURCE_EXTENSIONS","SHARD_EXTENSIONS","MANIFEST_EXTENSION","diffState","prev","next","changes","paths","path","from","to","show","v","prefixOf","m","createStateLogger","adapter","opts","sink","line","label","emit","c","full","out","prefix","name","value","baseline","pushed","mounted","hook","bag","change","mount","mounts","i","diffed","PropertyBag","_PropertyBag","declarations","opts","n","d","name","defaultFor","value","change","audit","fn","rowFor","c","k","values","saved","v","misfits","valueFits","writable","pathPrefix","rowName","lowerCase","SAVE_FRAGMENT_VERSION","ScopeRegistry","token","o","bag","waiting","e","prefix","key","only","taken","m","scope","found","resolver","norm","decls","out","owner","row","host","view","scopes","qualities","normOf","aliases","declsOf","properties","vals","blob","refit","fragment","by","wants","tagKey","boxId","groupId","tagId","StoryletError","message","ENGINE_OPTIONS","DURABLE_SCHEMA","cardIsShared","card","deckShared","cardIsDurable","deck","sharedCap","acrossLimit","orderRun","run","held","prng","first","s","rest","weight","drawn","left","total","r","k","shuffleInPlace","inOneHandOf","OWNER","identity","propertyPath","path","esc","id","unesc","sharedKey","kind","flowPrefix","flowId","flowKey","emptyPartitionValues","partitionsFromSections","sections","flowIds","shared","flows","flowOf","escaped","p","key","values","m","sectionsOf","keyOf","out","OWNED_SCOPES","bagFromDecls","decls","pathPrefix","PropertyBag","ownValue","bag","name","EMPTY_SCOPE","conditionPasses","v","SCOPE_DEFAULT_SHARED","isShared","scope","d","sharedHalf","flowHalf","isOwnedScope","emptyOwnerIndex","emptyOwnerIndexes","indexOwner","index","entity","gameId","effectiveGameId","indexValueOwners","bundle","addresses","valueAddresses","segment","candidates","zone","durableProps","internals","seen","push","decl","owner","list","addressOf","durableBag","prop","durableValues","props","entries","value","byKey","address","planDurable","cards","save","flow","draft","at","byAddress","carried","valueFits","eligible","e","spend","entry","assertDurable","schema","project","putDurable","hostReadOnly","spec","toForeignDecl","selfBackedDecls","writable","handDeclsOf","hand","resolveOwner","byGameId","ownerOrThrow","ambiguousValueAddressMessage","zoneQualifiedValueAddressMessage","legacyAddressMessage","buildPartition","bags","partitionValues","loadPartition","emptyDraft","SORT_SEP","items","item","a","b","propKey","walkScope","saved","byName","clean","walkPartition","n","savedKind","ids","finishReport","drift","evicted","droppedCooldowns","c","droppedSpent","droppedProperties","defaultedProperties","retypedProperties","isRecord","describeInput","bundleShapeProblems","problems","scopes","BUNDLE_SCHEMA","part","registry","refuseUnreadableSave","envelope","SAVE_SCHEMA","SAVE_SCHEMA_V2","SAVE_SCHEMA_V1","takes","quoted","xs","x","savedCard","savedHand","savedBox","cardKey","checkHostWrite","parts","host","hostRows","defaultFor","refuseUnreadableBundle","BUNDLE_SCHEMAS","missing","map","mapName","PLACE_GROUP","boxTags","box","group","tag","clash","boxName","g","names","where","template","Engine","_Engine","opts","setup","ScopeRegistry","misfits","view","event","turn","h","viewRevision","viewCache","reg","ctx","declSet","half","allTagGroups","node","bound","declared","bundleHostScopes","registered","mount","register","story","token","resolver","near","isBound","state","sharedDecls","grab","any","claim","durable","flowCards","otherClaims","existing","dealt","Flow","content","keepFlows","cardId","except","counts","found","perFlow","add","_prefix","row","mounts","handler","options","snapshot","next","mine","registeredKeys","f","waiting","report","stillWaiting","all","take","memory","plan","seed","hostScopes","scratch","dropped","moved","spent","withProps","cooldowns","cardName","board","restored","handKey","keys","known","kept","turns","engine","makePrng","put","pair","own","keep","boxRef","expr","deserialiseAst","ref","groups","groupsOfBox","record","tagIds","cached","made","memo","t","since","NEVER_PLAYED","last","handEnv","others","source","evaluate","storyletsDialect","boundTags","askNames","bindAll","bindings","isHoleRef","condition","criteria","groupRef","tagRef","groupName","parsed","parseHoleRef","wanted","ask","sources","contents","world","home","tags","claimed","trace","verdict","gateOk","scored","deckCtx","refused","priority","matchedSpecificity","i","j","taken","claimCounts","worldClaims","ordered","listed","handRef","conditionOk","evict","reason","survivors","handId","handRefs","want","room","outranked","from","o","outcomeGameId","bare","outcome","perPlay","newTurn","writes","target","landed","w","l","prev","outcomeId","match","boxKey","STORYLET_ENGINE_VERSION","SEMVER","parse","v","m","compareDigits","x","y","compare","a","b","i","c","xn","yn","versionInRange","version","min","maxExclusive","lo","hi","summarise","decls","d","defaultFor","durableCardCount","box","n","deck","card","handDecls","hand","movableHoles","bundle","filled","out","groupId","value","isHoleRef","group","groupsOfBox","g","effectiveGameId","handSlots","declared","t","describeBundle","boxes","hands","hosts","bundleHostScopes","properties","h","totals","boxGameId","cards","tag","template","movable","push","scope","owner","map","sites","boxOf","bundle","box","b","own","record","name","fieldDecls","outcomeFieldDecls","fieldValue","decls","values","fallback","f","effectiveFields","out","v","k","extensionDecls","extension","kind","ext","extensionsOf","address","node","first","second","named","items","x","cards","d","id","g","snapshotState","engine","flow","out","bag","row","extraState","flowBlob","id","saved","boxId","turn","cardId","at","handId","cards","createStateLogger","opts","live","serializeState","engine","world","saveState","SAVEFILE_SCHEMA","loadState","file","opts","SAVE_SCHEMA","SAVE_SCHEMA_V2","SAVE_SCHEMA_V1","container","before","report","e","deserializeState","json","parsed","renderTip","host","text","re","last","m","strong","inited","checked","suppressed","SHOW_DELAY","EDGE","GAP","tip","timer","active","activeKey","ensureEl","el","rectOf","anchor","place","t","r","tr","top","left","show","paint","hide","schedule","ensureTooltipHost","initTooltips","checkTooltipHost","checked","inited","tipAncestor","opts","suppressed","el","to","hide","active","tag","classNameOrProps","children","node","props","child","fallbackText","decl","d","defaultLabel","rungStrip","opts","make","el","tag","props","node","strip","decl","unset","fallback","readOnly","defaultLabel","stage","current","falls","rung","STYLE_ID","CSS","ensureInspectorStyle","style","eq","a","b","LOG_KINDS","LOG_KIND_LABELS","logKindOf","e","showVal","v","formatLogEntry","formatLogBody","flow","stamp","dealt","c","crit","g","t","listed","groupOf","path","parts","createPropertyInspector","engine","opts","liveFlow","live","el","head","h","saveBtn","blob","serializeState","url","filePicker","file","reader","deserializeState","refresh","loadBtn","filter","editors","groups","lastGroup","row","group","rowEl","buildRow","q","any","show","turnsHead","turnsBody","boardHead","boardBody","check","text","onChange","cls","label","box","buildLogPanel","which","caption","entriesOf","clear","empty","bar","body","kindOn","visibleLines","autoscroll","stampSeen","render","force","entries","lines","kind","on","copyBtn","clearBtn","flowLog","runLog","renderLog","line","parent","div","readLive","hand","cards","names","timer","pollMs","name","current","sync","commit","value","reset","widget","read","focused","w","input","holder","draw","rungStrip","stage","select","o","s","readAll","showVal","v","formatPropertySummary","p","options","durable","formatScopeLabel","scope","group","line","parent","text","cls","div","fold","label","open","details","summary","body","createBundleInspector","bundle","opts","ensureInspectorStyle","description","describeBundle","el","head","h","identity","totals","ident","handsBody","hand","template","moves","m","tagsBody","box","propsBody","map","mapBody","sites","n","countsBody","DEFAULT_URL","BOARD_EVENTS","boardFrame","flow","id","hands","hand","cards","c","turns","box","createLiveLink","opts","url","DEFAULT_URL","Ctor","queue","sock","closed","build","engine","unsubscribe","announced","dropped","wasOpen","lost","wait","retry","flush","m","post","frame","liveFlows","sendHello","flows","hello","f","first","b","postBoard","syncFlows","now","ids","onTrace","flowId","event","connect","s","ev","msg","detach","next","applyLiveBundle","engine","bundleJson","opts","bundle","next","report","e","createWorldContainer","bundle","token","bag","PropertyBag","hostScopeDecls","n","v","values"]}
|