@patterkit/play-helpers 0.12.4 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,6 +126,12 @@ link.flowOpened("main");
126
126
  engine restored from the old one (`kind: "structure"`). Wire it behind a dev flag - it is
127
127
  a development tool, not a shipping feature.
128
128
 
129
+ **Play in game**: Patterpad can ask the running game to start a block now. Pass `onPlay`; it
130
+ gets `{ id, sceneId, blockId, takeOver, build? }` and answers with
131
+ `link.replyPlay(id, { started, reason?, detail? })`, then or a frame or more later. With no
132
+ `onPlay`, the link answers `{ started: false, reason: "unhandled" }` itself. Details and the
133
+ reason codes Patterpad words: [Play in game](https://patterkit.dev/play/live-debug/#play-in-game).
134
+
129
135
  ## Property inspector
130
136
 
131
137
  `createPropertyInspector(engine)` builds a small DOM panel of the engine's `@patter`
package/dist/index.cjs CHANGED
@@ -125,6 +125,7 @@ function createStateLogger(engine, opts = {}) {
125
125
  }
126
126
 
127
127
  // src/debug.ts
128
+ var PLAY_UNHANDLED = "unhandled";
128
129
  var OPEN = 1;
129
130
  function createDebugLink(opts) {
130
131
  const url = opts.url ?? "ws://127.0.0.1:4471";
@@ -154,10 +155,20 @@ function createDebugLink(opts) {
154
155
  }, observe() {
155
156
  }, flowClosed() {
156
157
  }, setBuild() {
158
+ }, replyPlay() {
157
159
  }, close() {
158
160
  closed = true;
159
161
  }, state: "closed", build, url };
160
162
  }
163
+ const replyPlay = (id, result) => {
164
+ post({
165
+ t: "played",
166
+ id,
167
+ started: result.started === true,
168
+ ...typeof result.reason === "string" ? { reason: result.reason } : {},
169
+ ...typeof result.detail === "string" ? { detail: result.detail } : {}
170
+ });
171
+ };
161
172
  const sendHello = () => {
162
173
  const hello = JSON.stringify({ t: "hello", v: 1, build, project: opts.project, flows: [...flows] });
163
174
  try {
@@ -172,13 +183,29 @@ function createDebugLink(opts) {
172
183
  flush();
173
184
  });
174
185
  sock.addEventListener("message", (ev) => {
175
- if (!opts.onBundle || typeof ev.data !== "string") return;
186
+ if (typeof ev.data !== "string") return;
187
+ let msg;
176
188
  try {
177
- const msg = JSON.parse(ev.data);
178
- if (msg.t === "bundle" && typeof msg.build === "string" && typeof msg.data === "string") {
189
+ msg = JSON.parse(ev.data);
190
+ } catch {
191
+ return;
192
+ }
193
+ if (msg === null || typeof msg !== "object") return;
194
+ if (msg.t === "bundle") {
195
+ if (opts.onBundle && typeof msg.build === "string" && typeof msg.data === "string") {
179
196
  opts.onBundle({ build: msg.build, data: msg.data });
180
197
  }
181
- } catch {
198
+ } else if (msg.t === "play") {
199
+ const req = parsePlayRequest(msg);
200
+ if (!req) return;
201
+ if (!opts.onPlay) {
202
+ replyPlay(req.id, { started: false, reason: PLAY_UNHANDLED });
203
+ return;
204
+ }
205
+ try {
206
+ opts.onPlay(req);
207
+ } catch {
208
+ }
182
209
  }
183
210
  });
184
211
  sock.addEventListener("error", () => {
@@ -206,6 +233,7 @@ function createDebugLink(opts) {
206
233
  }
207
234
  post({ t: "frame", flow: flowId, sceneId, beatId, type, choiceId });
208
235
  },
236
+ replyPlay,
209
237
  setBuild(next) {
210
238
  if (closed || next === build) return;
211
239
  build = next;
@@ -229,6 +257,17 @@ function createDebugLink(opts) {
229
257
  url
230
258
  };
231
259
  }
260
+ function parsePlayRequest(msg) {
261
+ const { id, sceneId, blockId } = msg;
262
+ if (typeof id !== "string" || id === "" || typeof sceneId !== "string" || sceneId === "" || typeof blockId !== "string" || blockId === "") return null;
263
+ return {
264
+ id,
265
+ sceneId,
266
+ blockId,
267
+ takeOver: msg.takeOver === true,
268
+ ...typeof msg.build === "string" && msg.build !== "" ? { build: msg.build } : {}
269
+ };
270
+ }
232
271
 
233
272
  // src/refresh.ts
234
273
  var import_runtime = require("@patterkit/runtime");
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/save.ts","../src/properties.ts","../src/logger.ts","../src/debug.ts","../src/refresh.ts","../src/inspector.ts","../src/bundle-inspector.ts","../src/runner.ts","../src/audio.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @patterkit/play-helpers - the Patterplay JS companion.\n//\n// Thin game-integration helpers around @patterkit/runtime's Engine: save/load\n// serialisation, runtime property setters, a state logger, and the live-debug\n// link. None are required to play a bundle - they smooth the common host wiring\n// (localStorage saves, pushing game state into dialogue, debugging what changed,\n// streaming the cursor to Patterpad's live debugger).\n// ---------------------------------------------------------------------------\n\nexport { SAVE_SCHEMA, saveState, loadState, serializeState, deserializeState } from \"./save.js\";\nexport type { SaveEnvelope } from \"./save.js\";\n\nexport { getProperty, setProperty, setProperties } from \"./properties.js\";\nexport type { PropertyValue } from \"./properties.js\";\n\nexport { snapshotState, diffState, createStateLogger } from \"./logger.js\";\nexport type { StateSnapshot, StateChange, StateValue, StateLogger, StateLoggerOptions } from \"./logger.js\";\n\nexport { createDebugLink } from \"./debug.js\";\nexport type { DebugLink, DebugLinkOptions, DebugSocketLike } from \"./debug.js\";\n\nexport { applyLiveBundle } from \"./refresh.js\";\nexport type { LiveBundleResult } from \"./refresh.js\";\n\nexport { createPropertyInspector } from \"./inspector.js\";\nexport type { PropertyInspector, PropertyInspectorOptions } from \"./inspector.js\";\n\nexport { createBundleInspector } from \"./bundle-inspector.js\";\nexport type { BundleInspector, BundleInspectorOptions, BundleSection } from \"./bundle-inspector.js\";\n\nexport { Runner } from \"./runner.js\";\nexport type { RunnerEvent, RunnerEvents } from \"./runner.js\";\n\nexport { createAudioResolver } from \"./audio.js\";\nexport type { AudioResolver } from \"./audio.js\";\n","// ---------------------------------------------------------------------------\n// Save / load: wrap the Engine's whole-game snapshot in a tagged, versioned\n// envelope so a host can drop it into localStorage / a file and restore it\n// safely (a foreign blob throws instead of corrupting a run).\n// ---------------------------------------------------------------------------\n\nimport type { Engine } from \"@patterkit/runtime\";\nimport { SAVE_SCHEMA } from \"@patterkit/model\";\nimport type { SaveEnvelope } from \"@patterkit/model\";\n\n// The envelope (`schema` + `save`) and the save shape inside it are the FAMILY's contract, defined in\n// @patterkit/model and written identically by every Patterplay runtime. Re-exported so existing\n// imports keep working.\nexport { SAVE_SCHEMA };\nexport type { SaveEnvelope };\n\n/** Capture the whole game as a tagged envelope (wraps `engine.saveGame()`). */\nexport function saveState(engine: Engine): SaveEnvelope {\n return { schema: SAVE_SCHEMA, save: engine.saveGame() };\n}\n\n/** Restore a {@link saveState} envelope into an engine (fresh or live). Throws on a foreign/blank blob. */\nexport function loadState(engine: Engine, env: SaveEnvelope): void {\n if (!env || env.schema !== SAVE_SCHEMA || !env.save) {\n throw new Error(`loadState: not a ${SAVE_SCHEMA} envelope`);\n }\n engine.loadGame(env.save);\n}\n\n/** Serialise the whole game to a JSON string (envelope + save-game) - drop into localStorage or a file. */\nexport function serializeState(engine: Engine): string {\n return JSON.stringify(saveState(engine));\n}\n\n/** Parse + restore a {@link serializeState} string. Throws on malformed JSON or a foreign envelope. */\nexport function deserializeState(engine: Engine, json: string): void {\n loadState(engine, JSON.parse(json) as SaveEnvelope);\n}\n","// ---------------------------------------------------------------------------\n// Runtime property setters: read / write `@patter` globals, `@scene` props, or\n// a wired foreign scope at runtime - e.g. the game pushing inventory into the\n// dialogue, or reading a flag the dialogue set. Thin pass-throughs, plus a batch\n// setter for convenience.\n//\n// They take an Engine or a Flow. Game-wide properties (`@hp`, `@world.x`) go\n// through either; `@scene` props belong to a flow, since each flow has its own,\n// so they need the Flow: the Engine refuses an `@scene` ref.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@patterkit/runtime\";\n\n/** A property value the engine accepts (a Patter scalar: number / boolean / string / string[] flags). */\nexport type PropertyValue = Parameters<Engine[\"setProperty\"]>[1];\n\n/** Where properties are read and written: the Engine (game-wide refs), or a Flow (those plus `@scene`). */\nexport type PropertyHolder = Engine | Flow;\n\n/** Read a runtime property (`@hp`, a foreign `@world.x`, or with a Flow `@scene.locked`). Undefined when unset. */\nexport function getProperty(holder: PropertyHolder, ref: string): PropertyValue | undefined {\n return holder.getProperty(ref);\n}\n\n/** Set one runtime property. Mirrors `setProperty` on the Engine or Flow, exported for discoverability + symmetry. */\nexport function setProperty(holder: PropertyHolder, ref: string, value: PropertyValue): void {\n holder.setProperty(ref, value);\n}\n\n/** Set many at once, e.g. `setProperties(engine, { \"@hp\": 10 })` or `setProperties(flow, { \"@scene.locked\": false })`. */\nexport function setProperties(holder: PropertyHolder, values: Record<string, PropertyValue>): void {\n for (const [ref, value] of Object.entries(values)) holder.setProperty(ref, value);\n}\n","// ---------------------------------------------------------------------------\n// The Patterplay state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry, plus `logStep`, which is this product's own.\n//\n// It used to diff whole `engine.saveGame()` snapshots. That could only ever say\n// what changed BETWEEN captures, which means it could not see a value that\n// changed and changed back, could not name the reason a host attached to a\n// write, and reported a write only once something asked. The kernel logs a\n// property write as it LANDS, on the PropertyBag audit hook, and keeps the diff\n// for state that has no hook - here, the visit counts.\n//\n// The Storylet Engine's logger was already this shape; its core is what moved\n// into the kernel. Same core, two adapters.\n//\n// Paths, unchanged:\n// @patter.x the shared globals\n// @scene:<sceneId>.x the shared scene props\n// visit:<nodeId> shared visit counts\n// <flowId>/... the same three, per flow (its not-shared halves)\n// ---------------------------------------------------------------------------\n\nimport type { Engine, StepResult } from \"@patterkit/runtime\";\nimport { createStateLogger as createKernelStateLogger, diffState } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, ScalarValue, StateChange, StateSnapshot } from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @patterkit/play-helpers should not have to care that they moved.\nexport { diffState };\nexport type { StateChange, StateSnapshot };\n\n/** A flattened runtime-state value: a Patter scalar. The kernel calls this `ScalarValue`;\n * the alias stays because it is exported API and reads in Patter's own vocabulary. */\nexport type StateValue = ScalarValue;\n\n/** Flatten the engine's whole-game state into a path -> value map (shared scopes + every live\n * flow). The logger does not diff this - it mounts the bags directly - but it stays as the public\n * \"what is the state right now\" call, and it reads the same mounts, so the two agree on the path\n * space. A bag loaded into the registry but not yet claimed (a scene no flow has re-entered since\n * a load) appears once it is. */\nexport function snapshotState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const m of allMounts(engine)) {\n const prefix = m.pathPrefix ?? m.bag.pathPrefix;\n for (const [name, v] of Object.entries(m.bag.save())) out[`${prefix}${name}`] = v;\n }\n Object.assign(out, visitState(engine));\n return out;\n}\n\n/** Every bag the engine and its flows hold, with the path each answers to in a log. */\nfunction allMounts(engine: Engine): LogMount[] {\n return [...engine.listBags(), ...engine.flows().flatMap((f) => f.listBags())];\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to `console.log`. */\n sink?: (line: string) => void;\n /** Prefix tag for every line, e.g. the flow / save-slot name. */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state (no logging). */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the property writes already logged as they landed,\n * plus the visit counts, diffed and re-baselined. */\n capture(): StateChange[];\n /** Trace one played step (line / text / game-event / choice / end), including any `gameData`. */\n logStep(step: StepResult): void;\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\nfunction describeStep(step: StepResult): string {\n switch (step.type) {\n case \"line\": return `line ${step.character ?? \"?\"}: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"text\": return `text: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"gameEvent\": return `game event ${step.id}${gd(step.gameData)}`;\n case \"choice\": return `choice (${step.options.length} option${step.options.length === 1 ? \"\" : \"s\"})`;\n case \"end\": return \"end\";\n }\n}\nconst gd = (data: unknown): string => (data ? ` gameData=${JSON.stringify(data)}` : \"\");\n\n/** The visit counts, which live in no bag and so have no audit hook: the kernel diffs\n * these on capture, exactly as the whole logger used to work. */\nfunction visitState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const [id, n] of Object.entries(engine.getVisitCounts())) out[`visit:${id}`] = n;\n for (const flow of engine.flows()) {\n for (const [id, n] of Object.entries(flow.getVisitCounts())) out[`${flow.id}/visit:${id}`] = n;\n }\n return out;\n}\n\n/** Create a state logger over an engine. Property writes log as they land; call `capture()`\n * after each `advance`/`choose` to pick up the visit counts and re-baseline. */\nexport function createStateLogger(engine: Engine, opts: StateLoggerOptions = {}): StateLogger {\n const tag = opts.label ? `[${opts.label}] ` : \"\";\n const kernel = createKernelStateLogger({\n // Re-read on every capture: openFlow and loadGame both replace bags, and the kernel\n // re-mounts whatever it is handed.\n mounts: (): LogMount[] => allMounts(engine),\n extra: () => visitState(engine),\n }, { sink: opts.sink, label: tag });\n\n return {\n snapshot: () => snapshotState(engine),\n capture: () => kernel.capture(),\n dispose: () => kernel.dispose(),\n logStep(step) { (opts.sink ?? ((l: string) => console.log(l)))(`${tag}${describeStep(step)}`); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live debug link (#181) - the game-side client.\n//\n// Streams a running game's story position to Patterpad over a localhost\n// WebSocket, so the editor can follow the live cursor like a debugger. It is\n// OBSERVE-ONLY: the game stays in control; the editor is a passive mirror.\n//\n// Wire protocol `patterplay/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:1, build, project?, flows? } - on connect\n// frame : { t:\"frame\", flow, sceneId, beatId, type, choiceId? } - per transition\n// flow : { t:\"flowOpen\" | \"flowClose\", flow } - lifecycle\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: live bundle refresh.\n// `data` is the full .patterc JSON. Pass an `onBundle` handler (or just feed it to\n// `applyLiveBundle`, which picks strings-only vs full hot swap itself), then call\n// `link.setBuild(newHash)` so the editor's match/stale pill updates.\n//\n// The link is loopback-only on the editor side (127.0.0.1), so there's no pairing\n// token - only processes on the same machine can reach it. The client never throws\n// into the game loop - a missing/closed editor just means frames go nowhere. Wire\n// it up after each advance()/choose():\n//\n// const link = createDebugLink({ build: bundle.content.hash });\n// link.flowOpened(\"main\");\n// // ...after each step:\n// link.observe(\"main\", flow.currentScene, step.id ?? null, step.type);\n// ---------------------------------------------------------------------------\n\n/** A minimal structural type for a WebSocket implementation (browsers + Node 21+ have a global one). */\nexport interface DebugSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"close\" | \"error\", listener: () => void): void;\n /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype DebugSocketCtor = new (url: string) => DebugSocketLike;\n\nexport interface DebugLinkOptions {\n /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */\n build: string;\n /** Optional project name, shown in the editor's debug panel. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4471`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (Node < 21, or tests with `ws`). */\n WebSocket?: DebugSocketCtor;\n /** Live bundle refresh: the editor pushed a freshly compiled bundle. `data` is the .patterc JSON;\n * hand it (with your current engine + bundle) to `applyLiveBundle`, re-bind your flow handles if it\n * hot-swapped, then call `link.setBuild(build)`. Never called with malformed payloads. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\nexport interface DebugLink {\n /** Tell the editor a flow opened (so it can list it in the follow selector). */\n flowOpened(flowId: string): void;\n /** Report the current position of a flow - call after each advance()/choose(). */\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void;\n /** Tell the editor a flow closed. */\n flowClosed(flowId: string): void;\n /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's\n * match/stale pill updates and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link. */\n close(): void;\n /** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game \"the editor\n * is not listening\" and \"I never attached\" look the same, and this tells them apart. */\n readonly state: \"connecting\" | \"connected\" | \"closed\";\n /** The build the link handshakes with (the bundle's content hash), as `setBuild` last left it. */\n readonly build: string;\n /** The address the link dials. */\n readonly url: string;\n}\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/**\n * Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects\n * or if it was never listening - safe to leave wired into a shipping build behind a flag.\n */\nexport function createDebugLink(opts: DebugLinkOptions): DebugLink {\n const url = opts.url ?? \"ws://127.0.0.1:4471\";\n const Ctor: DebugSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: DebugSocketCtor }).WebSocket;\n const flows = new Set<string>();\n let queue: string[] = [];\n let sock: DebugSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live bundle refresh lands\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n // Queue only while there is a socket that is connecting or open. Once the editor is gone (or never\n // answered), the socket is dropped and a message has nowhere to go, so it is dropped too: queueing it\n // anyway grew the queue by one string per step for the rest of a game that shipped with the link wired\n // in, which is exactly the build the docs say this is safe in.\n const post = (msg: object): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n queue.push(JSON.stringify(msg));\n flush();\n };\n\n if (!Ctor) {\n // No WebSocket available (old Node, no impl passed) - degrade to a silent no-op link.\n return { flowOpened() {}, observe() {}, flowClosed() {}, setBuild() {}, close() { closed = true; }, state: \"closed\", build, url };\n }\n\n const sendHello = (): void => {\n const hello = JSON.stringify({ t: \"hello\", v: 1, build, project: opts.project, flows: [...flows] });\n try { sock?.send(hello); } catch { /* race: closed immediately */ }\n };\n\n try {\n sock = new Ctor(url);\n sock.addEventListener(\"open\", () => {\n // Handshake first, so the editor can verify the build + seed the flow list before frames arrive.\n sendHello();\n flush();\n });\n // Live bundle refresh: the editor pushed a new bundle. Validate the shape here so the host's\n // handler never sees a malformed payload; everything else the editor might send is ignored.\n sock.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n sock.addEventListener(\"error\", () => { /* editor not listening - stay a no-op */ });\n sock.addEventListener(\"close\", () => { sock = null; queue = []; });\n } catch { sock = null; } // malformed URL etc. - never throw into the game\n\n return {\n flowOpened(flowId: string): void { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); },\n flowClosed(flowId: string): void { flows.delete(flowId); post({ t: \"flowClose\", flow: flowId }); },\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void {\n // A flow reports itself. `flowOpened` is the host's job and nothing could check it, so a game\n // that opened a flow and forgot to announce it left the editor's list short - and the omission\n // survived a reconnect, because the hello sends this set. Observing a flow we have not seen\n // announces it first, which makes the host's call a courtesy (it lists the flow before its\n // first step) rather than something to remember.\n if (!flows.has(flowId)) { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); }\n post({ t: \"frame\", flow: flowId, sceneId, beatId, type, choiceId });\n },\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n if (sock && sock.readyState === OPEN) sendHello(); // re-handshake: the editor re-reads the build\n },\n close(): void { closed = true; queue = []; try { sock?.close(); } catch { /* already gone */ } sock = null; },\n get state() { return closed || !sock ? \"closed\" : sock.readyState === OPEN ? \"connected\" : \"connecting\"; },\n get build() { return build; },\n url,\n };\n}\n","// ---------------------------------------------------------------------------\n// Live bundle refresh (design/proposals/live-bundle-refresh.md) - the game-side\n// applier. The editor pushes a freshly compiled bundle over the debug link\n// (createDebugLink's `onBundle`); this helper picks the right tier and applies\n// it:\n// - same structureHash -> tier 1: engine.replaceStrings() - nothing restarts.\n// - changed structure -> tier 2: engine.hotSwap() - the run carried over,\n// content drift resolved by the save system (§9.8).\n// Returns the engine to keep using (the SAME one for \"text\", a REPLACEMENT for\n// \"structure\" - re-bind your Flow handles via engine.getFlow(id)) plus the\n// parsed bundle for the next comparison. Wire-up:\n//\n// let { engine, bundle } = boot();\n// const link = createDebugLink({\n// build: bundle.content.hash!,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, bundle, data);\n// engine = r.engine; bundle = r.bundle;\n// if (r.kind === \"structure\") flow = engine.getFlow(\"main\")!;\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport { Engine } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/runtime\";\n\nexport interface LiveBundleResult {\n /** The engine to keep using: the same instance for a \"text\" swap, a replacement for \"structure\". */\n engine: Engine;\n /** The parsed pushed bundle - hold on to it for the next apply's comparison. */\n bundle: Bundle;\n /** Which tier applied: \"text\" (strings-only, nothing restarted) or \"structure\" (full hot swap). */\n kind: \"text\" | \"structure\";\n}\n\n/**\n * Apply a bundle the editor pushed over the debug link. `current` is the bundle `engine` is running\n * (needed for the structure-hash comparison); `data` is the pushed .patterc JSON. Throws only on\n * unparseable JSON - a structural swap's edge cases are absorbed by the §9.8 drift policy inside\n * `hotSwap`, which never throws for ordinary edits.\n */\nexport function applyLiveBundle(engine: Engine, current: Bundle, data: string): LiveBundleResult {\n const next = JSON.parse(data) as Bundle;\n // Same structure = a text-only edit: swap the string tables in place and keep everything running.\n // Missing structureHash on either side (an older compiler) falls through to the full swap - safe,\n // just less gentle than it could be.\n const sameStructure = current.content.structureHash !== undefined\n && current.content.structureHash === next.content.structureHash;\n if (sameStructure) {\n engine.replaceStrings(next);\n return { engine, bundle: next, kind: \"text\" };\n }\n return { engine: engine.hotSwap(next), bundle: next, kind: \"structure\" };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// A live @patter property inspector for the browser: a small DOM panel that\n// watches AND edits a running Engine's shared @patter properties. Type-aware\n// editors (bool / number / text / enum / flags), a reset-to-default per row, and\n// a poll that refreshes values WITHOUT clobbering the field you're editing.\n//\n// The JS parity of the Unity PatterStateWindow / Godot PatterStatePanel. Those\n// use a PatterDebug registry because the editor window is separate from the game;\n// here the game runs in-process, so you just pass the engine directly:\n//\n// const inspector = createPropertyInspector(engine, { container: document.body });\n// // ...later: inspector.destroy();\n// ---------------------------------------------------------------------------\n\nimport { rungStrip } from \"@wildwinter/app-shell/value-control\";\nimport type { Engine, PropertyRow } from \"@patterkit/runtime\";\nimport { serializeState, deserializeState } from \"./save.js\";\nimport type { PropertyValue } from \"./properties.js\";\n\nexport interface PropertyInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Runtime state\". */\n title?: string;\n /** Live-refresh interval in ms; 0 disables polling (call `refresh()` yourself). Default 250. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** Re-read every property and update the editors, skipping the field you're editing. */\n refresh(): void;\n /** Stop polling and remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-inspector-style\";\nconst CSS = `\n.pp-insp{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:22rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-insp h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-insp-empty{color:#8a9691;font-style:italic}\n.pp-insp-io { display: flex; gap: 6px; margin: 0 0 8px; }\n .pp-insp-io button { font: inherit; padding: 2px 8px; cursor: pointer; }\n .pp-insp-row{display:flex;align-items:center;gap:.4rem;margin:.18rem 0}\n.pp-insp-ref{flex:0 0 8rem;font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.pp-insp-ctl{flex:1;min-width:0;display:flex}\n.pp-insp-ctl input[type=text],.pp-insp-ctl input[type=number],.pp-insp-ctl select{width:100%;box-sizing:border-box;font:inherit;padding:.15rem .3rem;border:1px solid #cfc7b8;border-radius:6px;background:#fff;color:inherit}\n.pp-insp-reset{flex:0 0 auto;border:1px solid #cfc7b8;background:#fff;border-radius:6px;cursor:pointer;color:#5c6b62;width:1.6rem;height:1.6rem;line-height:1;padding:0}\n.pp-insp-reset:disabled{opacity:.35;cursor:default}\n.pp-insp-ctl .seg{display:inline-flex;flex-wrap:wrap;border:1px solid #cfc7b8;border-radius:6px;overflow:hidden;background:#fff}\n.pp-insp-ctl .seg-opt{background:none;border:0;cursor:pointer;color:#5c6b62;font:400 .78rem/1.3 ui-monospace,monospace;padding:.12rem .4rem}\n.pp-insp-ctl .seg-opt+.seg-opt{border-left:1px solid #cfc7b8}\n.pp-insp-ctl .seg-opt.on{color:#214f4b;background:#dbe8e4;font-weight:600;cursor:default}\n.pp-insp-ctl .shell-rung.stray{color:#9a3b2e;text-decoration:line-through}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\n/** Has the DISPLAYED value changed? Order-sensitive on purpose, and deliberately\n * NOT the language's value equality, which compares flags as a set: this asks\n * whether the row needs redrawing, and the row renders the list in its stored\n * order, so a reordering is a change here even though it is not a change to\n * what the value means. */\nfunction sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => x === b[i]);\n return a === b;\n}\n\nexport function createPropertyInspector(engine: Engine, opts: PropertyInspectorOptions = {}): PropertyInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n\n const el = doc.createElement(\"div\");\n el.className = \"pp-insp\";\n const heading = doc.createElement(\"h4\");\n heading.textContent = opts.title ?? \"Runtime state\";\n const list = doc.createElement(\"div\");\n\n // Save/Load the whole run as a patter/save@0 envelope file - the parity of Unity's\n // PatterStateWindow buttons, Godot's state-panel dialogs, and Unreal's Slate panel.\n const io = doc.createElement(\"div\");\n io.className = \"pp-insp-io\";\n const saveBtn = doc.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.textContent = \"Save state\\u2026\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const a = doc.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"save.patterstate\";\n a.click();\n URL.revokeObjectURL(a.href);\n });\n const loadBtn = doc.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.textContent = \"Load state\\u2026\";\n loadBtn.addEventListener(\"click\", () => {\n const input = doc.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".patterstate,application/json\";\n input.addEventListener(\"change\", () => {\n const file = input.files?.[0];\n if (!file) return;\n void file.text().then((text) => {\n try { deserializeState(engine, text); refresh(); }\n catch (err) { console.error(\"Load state failed:\", err instanceof Error ? err.message : err); }\n });\n });\n input.click();\n });\n io.append(saveBtn, loadBtn);\n el.append(heading, io, list);\n\n const rowRefreshers: Array<() => void> = [];\n\n const buildRow = (row: PropertyRow): void => {\n const r = doc.createElement(\"div\");\n r.className = \"pp-insp-row\";\n const label = doc.createElement(\"span\");\n label.className = \"pp-insp-ref\";\n label.textContent = row.path;\n label.title = row.path;\n const ctl = doc.createElement(\"div\");\n ctl.className = \"pp-insp-ctl\";\n const reset = doc.createElement(\"button\");\n reset.className = \"pp-insp-reset\";\n reset.type = \"button\";\n reset.textContent = \"↺\"; // ↺\n reset.title = \"Reset to default\";\n r.append(label, ctl, reset);\n list.appendChild(r);\n\n // `read` pushes the live engine value into the editor, but never while that editor has focus\n // (so it can't wipe what you're typing). `syncReset` disables the ↺ when the value is the default.\n let read: () => void;\n const commit = (v: PropertyValue): void => { engine.setProperty(row.path, v); syncReset(); };\n const focused = (node: Element): boolean => doc.activeElement === node;\n function syncReset(): void { reset.disabled = sameValue(engine.getProperty(row.path), row.default); }\n reset.addEventListener(\"click\", () => { engine.setProperty(row.path, row.default); read(); syncReset(); });\n\n if (row.type === \"boolean\") {\n const cb = doc.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.addEventListener(\"change\", () => commit(cb.checked));\n ctl.appendChild(cb);\n read = () => { if (!focused(cb)) cb.checked = engine.getProperty(row.path) === true; };\n } else if (row.type === \"number\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"number\";\n inp.addEventListener(\"change\", () => commit(Number(inp.value)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"quality\") {\n // A quality shows as its LADDER, the current stage marked, as in both editors: the family's one strip\n // (@wildwinter/app-shell/value-control), built in this inspector's own document.\n const draw = (): void => {\n const v = engine.getProperty(row.path);\n ctl.replaceChildren(rungStrip({ stages: row.stages ?? [], value: typeof v === \"string\" ? v : undefined, decl: row,\n onPick: (stage) => { commit(stage); draw(); }, doc }));\n };\n read = draw;\n } else if (row.type === \"enum\") {\n // An enum edits as a dropdown of its values.\n const sel = doc.createElement(\"select\");\n for (const v of row.values ?? []) {\n const o = doc.createElement(\"option\");\n o.value = v; o.textContent = v;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", () => commit(sel.value));\n ctl.appendChild(sel);\n read = () => { if (!focused(sel)) sel.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"flags\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.placeholder = \"comma, separated, flags\";\n inp.addEventListener(\"change\", () => commit(inp.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) { const v = engine.getProperty(row.path); inp.value = Array.isArray(v) ? v.join(\", \") : \"\"; } };\n } else {\n // string (and any unrecognised type) -> a text field\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.addEventListener(\"change\", () => commit(inp.value));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n }\n\n read();\n syncReset();\n rowRefreshers.push(() => { read(); syncReset(); });\n };\n\n // Properties are fixed for a bundle, so build the rows once; polling just refreshes their values.\n const props = engine.listProperties();\n if (props.length === 0) {\n const empty = doc.createElement(\"div\");\n empty.className = \"pp-insp-empty\";\n empty.textContent = \"No @patter properties.\";\n list.appendChild(empty);\n } else {\n for (const row of props) buildRow(row);\n }\n\n const refresh = (): void => { for (const fn of rowRefreshers) fn(); };\n\n opts.container?.appendChild(el);\n const pollMs = opts.pollMs ?? 250;\n let timer: ReturnType<typeof setInterval> | undefined;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n return {\n el,\n refresh,\n destroy() { if (timer !== undefined) clearInterval(timer); el.remove(); },\n };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// The bundle inspector's VIEW, for the web: a read-only panel over\n// `describeBundle`, in the same visual grammar as the live property inspector.\n//\n// The other three runtimes hang their view off an imported asset - a Unity\n// CustomEditor, an Unreal details customisation, a Godot EditorInspectorPlugin.\n// JS has no asset pipeline, so there is no inspector to extend and the summary\n// function IS the surface. This exists because \"the function is the surface\"\n// leaves the JS side without the thing the other three give you for free: a\n// place to LOOK at a bundle. A drop-in page can now do\n//\n// const panel = createBundleInspector(bundle, { container: document.body });\n// // ...later: panel.destroy();\n//\n// Read-only on purpose. The live inspector edits because a running game has\n// state worth changing; this describes an artefact, and there is nothing here a\n// reader could sensibly write to.\n// ---------------------------------------------------------------------------\n\nimport { describeBundle } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/model\";\nimport type { BundleDescription, PropertySummary } from \"@patterkit/runtime\";\n\nexport interface BundleInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Bundle\". */\n title?: string;\n /** Sections open on mount. Default: identity and addresses, the two an\n * integrator reaches for first; the rest are a click away. */\n open?: BundleSection[];\n}\n\nexport type BundleSection = \"identity\" | \"addresses\" | \"hostScopes\" | \"properties\" | \"gameData\" | \"counts\";\n\nexport interface BundleInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** The description the panel is showing, for a caller that wants the data too. */\n readonly description: BundleDescription;\n /** Remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-bundle-style\";\n// Deliberately the property inspector's palette and metrics: the two panels are\n// often on the same page, and a second look-and-feel would read as a second tool.\nconst CSS = `\n.pp-bundle{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:26rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-bundle h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-bundle details{border-top:1px solid #e3dccf;padding:.3rem 0}\n.pp-bundle details:first-of-type{border-top:0}\n.pp-bundle summary{cursor:pointer;font-weight:600;color:#214f4b;list-style:none}\n.pp-bundle summary::-webkit-details-marker{display:none}\n.pp-bundle summary::before{content:\"\\\\25B8\";display:inline-block;width:1rem;color:#8a9691}\n.pp-bundle details[open]>summary::before{content:\"\\\\25BE\"}\n.pp-bundle-count{color:#8a9691;font-weight:400}\n.pp-bundle-row{display:flex;gap:.4rem;margin:.16rem 0 .16rem 1rem}\n.pp-bundle-key{flex:0 0 8rem;color:#5c6b62}\n.pp-bundle-val,.pp-bundle-addr{font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;word-break:break-all}\n.pp-bundle-sub{margin-left:2rem}\n.pp-bundle-empty{color:#8a9691;font-style:italic;margin-left:1rem}\n.pp-bundle-warn{color:#8a3a2f;font-weight:600}\n.pp-bundle-tag{font-size:.7rem;color:#5c6b62;border:1px solid #cfc7b8;border-radius:5px;padding:0 .25rem;margin-left:.3rem}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\nconst el = (doc: Document, tag: string, cls?: string, text?: string): HTMLElement => {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text !== undefined) e.textContent = text;\n return e;\n};\n\n/** One \"key: value\" line. */\nfunction row(doc: Document, key: string, value: string, valueClass = \"pp-bundle-val\"): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key\", key), el(doc, \"span\", valueClass, value));\n return r;\n}\n\n/** A property line: the name, and its type. A bundle compiled since 2026-10-19 resolves every default, host\n * scopes' included, so \"no default\" shows only on an older bundle, where it meant the game supplied it. */\nfunction propertyRow(doc: Document, p: PropertySummary): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", p.name));\n const meta = el(doc, \"span\", \"pp-bundle-val\", p.type);\n if (!p.hasDefault) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"no default\"));\n r.append(meta);\n return r;\n}\n\nfunction section(doc: Document, name: BundleSection, label: string, count: number | null, open: boolean): HTMLDetailsElement {\n const d = doc.createElement(\"details\");\n d.dataset[\"section\"] = name;\n d.open = open;\n const s = doc.createElement(\"summary\");\n s.textContent = label;\n if (count !== null) s.append(el(doc, \"span\", \"pp-bundle-count\", ` ${count}`));\n d.append(s);\n return d;\n}\n\n/**\n * Build a read-only panel describing a compiled bundle: what it is, what game\n * code may call on it, and what the host must supply.\n */\nexport function createBundleInspector(bundle: Bundle, opts: BundleInspectorOptions = {}): BundleInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n const d = describeBundle(bundle);\n const open = new Set<BundleSection>(opts.open ?? [\"identity\", \"addresses\"]);\n\n const root = el(doc, \"div\", \"pp-bundle\");\n root.append(el(doc, \"h4\", undefined, opts.title ?? \"Bundle\"));\n\n // --- identity ------------------------------------------------------------\n const id = section(doc, \"identity\", d.identity.project || \"(unnamed project)\", null, open.has(\"identity\"));\n if (d.identity.version) id.append(row(doc, \"version\", d.identity.version));\n id.append(row(doc, \"schema\", d.identity.schema));\n id.append(row(doc, \"locales\", `${d.identity.defaultLocale}${d.identity.locales.length > 1 ? ` (+${d.identity.locales.length - 1})` : \"\"}`));\n id.append(row(doc, \"strings\", d.identity.localisation));\n if (d.identity.voiced) id.append(row(doc, \"voiced\", \"yes\"));\n if (d.identity.hash) id.append(row(doc, \"hash\", d.identity.hash));\n // Equal structureHash + a different hash = a text-only edit, which is what makes\n // a live hot-swap safe. Both are shown so an integrator can tell those apart.\n if (d.identity.structureHash) id.append(row(doc, \"structure\", d.identity.structureHash));\n if (d.identity.sourceDebug) {\n id.append(row(doc, \"build\", \"SOURCE DEBUG - not shippable\", \"pp-bundle-val pp-bundle-warn\"));\n }\n root.append(id);\n\n // --- addresses -----------------------------------------------------------\n const addr = section(doc, \"addresses\", \"Addresses\", d.addresses.length, open.has(\"addresses\"));\n if (!d.addresses.length) addr.append(el(doc, \"div\", \"pp-bundle-empty\", \"no scenes\"));\n for (const a of d.addresses) {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", a.gameId), el(doc, \"span\", \"pp-bundle-val\", a.name));\n addr.append(r);\n // Nested, because a block address is scene-scoped: the pair is the address.\n for (const b of a.blocks) {\n const br = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n br.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", b.gameId), el(doc, \"span\", \"pp-bundle-val\", b.name));\n addr.append(br);\n }\n }\n root.append(addr);\n\n // --- host scopes ---------------------------------------------------------\n // Counted in PROPERTIES rather than scopes: the heading says \"properties\", and a\n // reader comparing it with the rows below should not have to work out which it meant.\n // An opaque scope contributes none, and says so on its own row.\n const hostCount = d.hostScopes.reduce((n, s) => n + s.properties.length, 0);\n const host = section(doc, \"hostScopes\", \"Host properties\", hostCount, open.has(\"hostScopes\"));\n if (!d.hostScopes.length) host.append(el(doc, \"div\", \"pp-bundle-empty\", \"the game supplies nothing\"));\n for (const s of d.hostScopes) {\n const head = el(doc, \"div\", \"pp-bundle-row\");\n head.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", `@${s.token}`));\n const meta = el(doc, \"span\", \"pp-bundle-val\", s.opaque ? \"any name, unchecked\" : `${s.properties.length} declared`);\n if (!s.writable) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"read-only\"));\n head.append(meta);\n host.append(head);\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n host.append(pr);\n }\n }\n root.append(host);\n\n // --- owned properties ----------------------------------------------------\n const ownedCount = d.properties.patter.length + d.properties.scene.reduce((n, s) => n + s.properties.length, 0);\n const owned = section(doc, \"properties\", \"Story properties\", ownedCount, open.has(\"properties\"));\n if (!ownedCount) owned.append(el(doc, \"div\", \"pp-bundle-empty\", \"none declared\"));\n for (const p of d.properties.patter) owned.append(propertyRow(doc, p));\n for (const s of d.properties.scene) {\n owned.append(row(doc, `@scene`, s.gameId, \"pp-bundle-val pp-bundle-addr\"));\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n owned.append(pr);\n }\n }\n root.append(owned);\n\n // --- gameData ------------------------------------------------------------\n if (d.gameData.length) {\n const gd = section(doc, \"gameData\", \"Game data\", d.gameData.reduce((n, g) => n + g.fields.length, 0), open.has(\"gameData\"));\n for (const g of d.gameData) {\n gd.append(row(doc, \"on\", g.kind));\n for (const f of g.fields) {\n const fr = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n fr.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", f.name), el(doc, \"span\", \"pp-bundle-val\", f.type));\n gd.append(fr);\n }\n }\n root.append(gd);\n }\n\n // --- counts --------------------------------------------------------------\n const counts = section(doc, \"counts\", \"Counts\", null, open.has(\"counts\"));\n for (const [key, value] of Object.entries(d.counts)) counts.append(row(doc, key, String(value)));\n root.append(counts);\n\n opts.container?.append(root);\n return {\n el: root,\n description: d,\n destroy() { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// The thin runner: the flow API turned from pull to push, for straightforward\n// games. Start a conversation, and the runner hands each beat to the game as an\n// event; the game shows it however it likes and calls next() when it's ready.\n// A choice waits for choose(id). Nothing else: no timing, no auto-advance, no\n// UI, no audio. A line carries its beat id, so a game plays its recording with\n// the audio resolver, or its own. The same runner in every Patterplay runtime.\n//\n// const runner = new Runner(engine);\n// runner.on(\"line\", (step) => { show(step.speakerLabel, step.text); });\n// runner.on(\"choice\", (step) => offer(step.options)); // then runner.choose(id)\n// runner.on(\"ended\", (reason) => close()); // \"ended\", or \"ran-dry\": a choice had nothing left\n// runner.begin(); // the project's start scene; next() on each click\n//\n// A fuller dialogue manager for more complex games, Patterstage, is coming.\n// ---------------------------------------------------------------------------\n\nimport type { ChoiceOption, FlowEndReason, Engine, Flow, StepResult } from \"@patterkit/runtime\";\n\ntype Step<T extends StepResult[\"type\"]> = Extract<StepResult, { type: T }>;\n\n/** What each runner event hands its handler. */\nexport interface RunnerEvents {\n line: Step<\"line\">;\n text: Step<\"text\">;\n gameEvent: Step<\"gameEvent\">;\n choice: { groupId: string; options: ChoiceOption[] };\n /** Why the flow ended, as its `end` step says: `\"ran-dry\"` when a choice had nothing left to offer and\n * nothing came after it, otherwise `\"ended\"`. */\n ended: FlowEndReason;\n}\n\nexport type RunnerEvent = keyof RunnerEvents;\n\n/**\n * Plays one flow, a beat at a time, as events. Every delivered beat (a line, a text or a game event)\n * waits for {@link next}; a choice waits for {@link choose}. Handlers may call `next()` or `choose()`\n * themselves: the runner finishes the event it is delivering first, so a long run never deepens the\n * stack.\n *\n * The runner keeps no state a save needs: `engine.saveGame()` and `loadGame()` stay the one way to\n * save. After a load, call `next()` to carry on (re-show the last line with `flow.stepFor(id)` if you\n * like). After `engine.hotSwap()`, hand the new engine to {@link rebind}.\n */\nexport class Runner {\n private readonly handlers: { [E in RunnerEvent]: Set<(payload: RunnerEvents[E]) => void> } = {\n line: new Set(), text: new Set(), gameEvent: new Set(), choice: new Set(), ended: new Set(),\n };\n /** next() calls asked for while an event was being delivered, played once it returns. */\n private queued = 0;\n private delivering = false;\n\n constructor(private engine: Engine, readonly flowId: string = \"main\") {}\n\n /** The flow the runner plays, looked up by id each time: a load or a hot swap replaces it. */\n get flow(): Flow | undefined {\n return this.engine.getFlow(this.flowId);\n }\n\n /** Listen for an event; returns a function that stops listening. */\n on<E extends RunnerEvent>(event: E, handler: (payload: RunnerEvents[E]) => void): () => void {\n const set = this.handlers[event] as Set<(payload: RunnerEvents[E]) => void>;\n set.add(handler);\n return () => { set.delete(handler); };\n }\n\n /**\n * Start a conversation at a scene (and block), and deliver its first beat. The first begin opens the\n * flow. A later one moves the same flow there, as an authored jump does, so visit counts, variations\n * and the flow's own properties carry on through the playthrough.\n *\n * With no scene, the story starts from the beginning, at the project's start scene: the first time\n * opens the flow there, and a later time resets it (`flow.reset()`), forgetting the flow's own state,\n * as a new game does. Throws on an address that doesn't resolve, before anything changes.\n */\n begin(scene?: string, block?: string): void {\n const flow = this.flow;\n if (!flow || flow.isClosed) {\n this.engine.openFlow(this.flowId, { scene, block });\n } else if (scene === undefined) {\n if (block !== undefined) throw new Error(\"Runner.begin: a block needs its scene\");\n flow.reset();\n } else if (!flow.goto(scene, block)) {\n throw new Error(`Runner.begin: address not found: ${scene}${block === undefined ? \"\" : ` / ${block}`}`);\n }\n this.queued = 0;\n this.next();\n }\n\n /** Deliver the next beat, the pending choice, or the end. */\n next(): void {\n if (!this.flow) throw new Error(`Runner.next: flow '${this.flowId}' isn't open; call begin() first`);\n this.queued++;\n if (this.delivering) return;\n this.delivering = true;\n try {\n while (this.queued > 0) {\n this.queued--;\n this.deliver(this.flow.advance());\n }\n } finally {\n this.delivering = false;\n this.queued = 0;\n }\n }\n\n /** Pick an option of the pending choice, then deliver what it leads to. Throws as `flow.choose` does\n * (no choice pending, an unknown option, or one that isn't eligible). */\n choose(id: string): void {\n const flow = this.flow;\n if (!flow) throw new Error(`Runner.choose: flow '${this.flowId}' isn't open; call begin() first`);\n flow.choose(id);\n this.next();\n }\n\n /**\n * Stop delivering: any next() a handler asked for is dropped, and nothing more is delivered until the\n * game asks. The flow stays where it is, open and saveable, and `ended` is not raised; `next()` carries\n * on from there and `begin()` begins elsewhere.\n */\n stop(): void {\n this.queued = 0;\n }\n\n /** Play on a replacement engine (what `engine.hotSwap()` or `applyLiveBundle` returns). */\n rebind(engine: Engine): void {\n this.engine = engine;\n }\n\n private deliver(step: StepResult): void {\n switch (step.type) {\n case \"line\": return this.emit(\"line\", step);\n case \"text\": return this.emit(\"text\", step);\n case \"gameEvent\": return this.emit(\"gameEvent\", step);\n case \"choice\": return this.emit(\"choice\", { groupId: step.groupId, options: step.options });\n case \"end\": return this.emit(\"ended\", step.reason);\n }\n }\n\n private emit<E extends RunnerEvent>(event: E, payload: RunnerEvents[E]): void {\n for (const handler of [...this.handlers[event]]) (handler as (p: RunnerEvents[E]) => void)(payload);\n }\n}\n","// ---------------------------------------------------------------------------\n// Audio resolver (#206): map a beat id to the path/URL of its winning audio\n// take, using the `patteraudio.json` manifest Patterpad (or the CLI) emits next\n// to the Audio Folders. It RESOLVES ONLY - playback stays yours (Web Audio, an\n// <audio> element, etc.), which can't be portable across platforms anyway.\n//\n// The manifest already encodes the highest-rung \"winner\" per beat, so there's no\n// folder search at runtime. Deploy your audio folder somewhere your game can read\n// (e.g. under /public), fetch the manifest once, and:\n//\n// const audio = createAudioResolver(manifestJson, \"audio\"); // base = where the folder is served\n// const src = audio.resolve(step.id); // \"audio/final/L1.wav\", or null when the beat has no recording\n// if (src) new Audio(src).play();\n// ---------------------------------------------------------------------------\n\nexport interface AudioResolver {\n /** The full path/URL of a beat's winning audio take, or null when it has none. */\n resolve(beatId: string): string | null;\n}\n\ninterface AudioManifest {\n schema?: string;\n clips?: Record<string, { file: string; status?: string }>;\n}\n\n/**\n * Build an audio resolver from a `patteraudio.json` manifest string plus the base path/URL its files live\n * under (wherever you deployed the audio folder). `resolve(beatId)` joins the base with the winning clip's\n * relative path, or returns null when the beat has no recording. Never throws on a missing beat.\n */\nexport function createAudioResolver(manifestJson: string, basePath: string): AudioResolver {\n const manifest = JSON.parse(manifestJson) as AudioManifest;\n const clips = manifest.clips ?? {};\n // A base that already ends in a separator is joined as it stands: trimming it turned a root such as \"/\"\n // or \"res://\" into \"\" or \"res:\", and the path lost its root.\n const sep = basePath === \"\" || /[/\\\\]$/.test(basePath) ? \"\" : \"/\";\n return {\n resolve(beatId: string): string | null {\n const clip = clips[beatId];\n if (!clip || !clip.file) return null;\n return `${basePath}${sep}${clip.file}`;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOA,mBAA4B;AAUrB,SAAS,UAAU,QAA8B;AACtD,SAAO,EAAE,QAAQ,0BAAa,MAAM,OAAO,SAAS,EAAE;AACxD;AAGO,SAAS,UAAU,QAAgB,KAAyB;AACjE,MAAI,CAAC,OAAO,IAAI,WAAW,4BAAe,CAAC,IAAI,MAAM;AACnD,UAAM,IAAI,MAAM,oBAAoB,wBAAW,WAAW;AAAA,EAC5D;AACA,SAAO,SAAS,IAAI,IAAI;AAC1B;AAGO,SAAS,eAAe,QAAwB;AACrD,SAAO,KAAK,UAAU,UAAU,MAAM,CAAC;AACzC;AAGO,SAAS,iBAAiB,QAAgB,MAAoB;AACnE,YAAU,QAAQ,KAAK,MAAM,IAAI,CAAiB;AACpD;;;ACjBO,SAAS,YAAY,QAAwB,KAAwC;AAC1F,SAAO,OAAO,YAAY,GAAG;AAC/B;AAGO,SAAS,YAAY,QAAwB,KAAa,OAA4B;AAC3F,SAAO,YAAY,KAAK,KAAK;AAC/B;AAGO,SAAS,cAAc,QAAwB,QAA6C;AACjG,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,EAAG,QAAO,YAAY,KAAK,KAAK;AAClF;;;ACVA,2BAAwE;AAiBjE,SAAS,cAAc,QAA+B;AAC3D,QAAM,MAAqB,CAAC;AAC5B,aAAW,KAAK,UAAU,MAAM,GAAG;AACjC,UAAM,SAAS,EAAE,cAAc,EAAE,IAAI;AACrC,eAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAG,KAAI,GAAG,MAAM,GAAG,IAAI,EAAE,IAAI;AAAA,EAClF;AACA,SAAO,OAAO,KAAK,WAAW,MAAM,CAAC;AACrC,SAAO;AACT;AAGA,SAAS,UAAU,QAA4B;AAC7C,SAAO,CAAC,GAAG,OAAO,SAAS,GAAG,GAAG,OAAO,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC9E;AAqBA,SAAS,aAAa,MAA0B;AAC9C,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK;AAAQ,aAAO,QAAQ,KAAK,aAAa,GAAG,KAAK,KAAK,UAAU,KAAK,IAAI,CAAC,GAAG,GAAG,KAAK,QAAQ,CAAC;AAAA,IACnG,KAAK;AAAQ,aAAO,SAAS,KAAK,UAAU,KAAK,IAAI,CAAC,GAAG,GAAG,KAAK,QAAQ,CAAC;AAAA,IAC1E,KAAK;AAAa,aAAO,cAAc,KAAK,EAAE,GAAG,GAAG,KAAK,QAAQ,CAAC;AAAA,IAClE,KAAK;AAAU,aAAO,WAAW,KAAK,QAAQ,MAAM,UAAU,KAAK,QAAQ,WAAW,IAAI,KAAK,GAAG;AAAA,IAClG,KAAK;AAAO,aAAO;AAAA,EACrB;AACF;AACA,IAAM,KAAK,CAAC,SAA2B,OAAO,aAAa,KAAK,UAAU,IAAI,CAAC,KAAK;AAIpF,SAAS,WAAW,QAA+B;AACjD,QAAM,MAAqB,CAAC;AAC5B,aAAW,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,OAAO,eAAe,CAAC,EAAG,KAAI,SAAS,EAAE,EAAE,IAAI;AACpF,aAAW,QAAQ,OAAO,MAAM,GAAG;AACjC,eAAW,CAAC,IAAI,CAAC,KAAK,OAAO,QAAQ,KAAK,eAAe,CAAC,EAAG,KAAI,GAAG,KAAK,EAAE,UAAU,EAAE,EAAE,IAAI;AAAA,EAC/F;AACA,SAAO;AACT;AAIO,SAAS,kBAAkB,QAAgB,OAA2B,CAAC,GAAgB;AAC5F,QAAM,MAAM,KAAK,QAAQ,IAAI,KAAK,KAAK,OAAO;AAC9C,QAAM,aAAS,qBAAAA,mBAAwB;AAAA;AAAA;AAAA,IAGrC,QAAQ,MAAkB,UAAU,MAAM;AAAA,IAC1C,OAAO,MAAM,WAAW,MAAM;AAAA,EAChC,GAAG,EAAE,MAAM,KAAK,MAAM,OAAO,IAAI,CAAC;AAElC,SAAO;AAAA,IACL,UAAU,MAAM,cAAc,MAAM;AAAA,IACpC,SAAS,MAAM,OAAO,QAAQ;AAAA,IAC9B,SAAS,MAAM,OAAO,QAAQ;AAAA,IAC9B,QAAQ,MAAM;AAAE,OAAC,KAAK,SAAS,CAAC,MAAc,QAAQ,IAAI,CAAC,IAAI,GAAG,GAAG,GAAG,aAAa,IAAI,CAAC,EAAE;AAAA,IAAG;AAAA,EACjG;AACF;;;ACtCA,IAAM,OAAO;AAMN,SAAS,gBAAgB,MAAmC;AACjE,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,OAAoC,KAAK,aAAc,WAA+C;AAC5G,QAAM,QAAQ,oBAAI,IAAY;AAC9B,MAAI,QAAkB,CAAC;AACvB,MAAI,OAA+B;AACnC,MAAI,SAAS;AACb,MAAI,QAAQ,KAAK;AAEjB,QAAM,QAAQ,MAAY;AACxB,QAAI,CAAC,QAAQ,KAAK,eAAe,KAAM;AACvC,eAAW,KAAK,OAAO;AAAE,UAAI;AAAE,aAAK,KAAK,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAyB;AAAA,IAAE;AAChF,YAAQ,CAAC;AAAA,EACX;AAKA,QAAM,OAAO,CAAC,QAAsB;AAClC,QAAI,UAAU,CAAC,QAAQ,KAAK,aAAa,KAAM;AAC/C,UAAM,KAAK,KAAK,UAAU,GAAG,CAAC;AAC9B,UAAM;AAAA,EACR;AAEA,MAAI,CAAC,MAAM;AAET,WAAO,EAAE,aAAa;AAAA,IAAC,GAAG,UAAU;AAAA,IAAC,GAAG,aAAa;AAAA,IAAC,GAAG,WAAW;AAAA,IAAC,GAAG,QAAQ;AAAE,eAAS;AAAA,IAAM,GAAG,OAAO,UAAU,OAAO,IAAI;AAAA,EAClI;AAEA,QAAM,YAAY,MAAY;AAC5B,UAAM,QAAQ,KAAK,UAAU,EAAE,GAAG,SAAS,GAAG,GAAG,OAAO,SAAS,KAAK,SAAS,OAAO,CAAC,GAAG,KAAK,EAAE,CAAC;AAClG,QAAI;AAAE,YAAM,KAAK,KAAK;AAAA,IAAG,QAAQ;AAAA,IAAiC;AAAA,EACpE;AAEA,MAAI;AACF,WAAO,IAAI,KAAK,GAAG;AACnB,SAAK,iBAAiB,QAAQ,MAAM;AAElC,gBAAU;AACV,YAAM;AAAA,IACR,CAAC;AAGD,SAAK,iBAAiB,WAAW,CAAC,OAA0B;AAC1D,UAAI,CAAC,KAAK,YAAY,OAAO,GAAG,SAAS,SAAU;AACnD,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,GAAG,IAAI;AAC9B,YAAI,IAAI,MAAM,YAAY,OAAO,IAAI,UAAU,YAAY,OAAO,IAAI,SAAS,UAAU;AACvF,eAAK,SAAS,EAAE,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK,CAAC;AAAA,QACpD;AAAA,MACF,QAAQ;AAAA,MAAmB;AAAA,IAC7B,CAAC;AACD,SAAK,iBAAiB,SAAS,MAAM;AAAA,IAA4C,CAAC;AAClF,SAAK,iBAAiB,SAAS,MAAM;AAAE,aAAO;AAAM,cAAQ,CAAC;AAAA,IAAG,CAAC;AAAA,EACnE,QAAQ;AAAE,WAAO;AAAA,EAAM;AAEvB,SAAO;AAAA,IACL,WAAW,QAAsB;AAAE,YAAM,IAAI,MAAM;AAAG,WAAK,EAAE,GAAG,YAAY,MAAM,OAAO,CAAC;AAAA,IAAG;AAAA,IAC7F,WAAW,QAAsB;AAAE,YAAM,OAAO,MAAM;AAAG,WAAK,EAAE,GAAG,aAAa,MAAM,OAAO,CAAC;AAAA,IAAG;AAAA,IACjG,QAAQ,QAAgB,SAAwB,QAAuB,MAAc,UAAyB;AAM5G,UAAI,CAAC,MAAM,IAAI,MAAM,GAAG;AAAE,cAAM,IAAI,MAAM;AAAG,aAAK,EAAE,GAAG,YAAY,MAAM,OAAO,CAAC;AAAA,MAAG;AACpF,WAAK,EAAE,GAAG,SAAS,MAAM,QAAQ,SAAS,QAAQ,MAAM,SAAS,CAAC;AAAA,IACpE;AAAA,IACA,SAAS,MAAoB;AAC3B,UAAI,UAAU,SAAS,MAAO;AAC9B,cAAQ;AACR,UAAI,QAAQ,KAAK,eAAe,KAAM,WAAU;AAAA,IAClD;AAAA,IACA,QAAc;AAAE,eAAS;AAAM,cAAQ,CAAC;AAAG,UAAI;AAAE,cAAM,MAAM;AAAA,MAAG,QAAQ;AAAA,MAAqB;AAAE,aAAO;AAAA,IAAM;AAAA,IAC5G,IAAI,QAAQ;AAAE,aAAO,UAAU,CAAC,OAAO,WAAW,KAAK,eAAe,OAAO,cAAc;AAAA,IAAc;AAAA,IACzG,IAAI,QAAQ;AAAE,aAAO;AAAA,IAAO;AAAA,IAC5B;AAAA,EACF;AACF;;;ACtIA,qBAAuB;AAkBhB,SAAS,gBAAgB,QAAgB,SAAiB,MAAgC;AAC/F,QAAM,OAAO,KAAK,MAAM,IAAI;AAI5B,QAAM,gBAAgB,QAAQ,QAAQ,kBAAkB,UACnD,QAAQ,QAAQ,kBAAkB,KAAK,QAAQ;AACpD,MAAI,eAAe;AACjB,WAAO,eAAe,IAAI;AAC1B,WAAO,EAAE,QAAQ,QAAQ,MAAM,MAAM,OAAO;AAAA,EAC9C;AACA,SAAO,EAAE,QAAQ,OAAO,QAAQ,IAAI,GAAG,QAAQ,MAAM,MAAM,YAAY;AACzE;;;ACvCA,2BAA0B;AAuB1B,IAAM,WAAW;AACjB,IAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBZ,SAAS,YAAY,KAAqB;AACxC,MAAI,IAAI,eAAe,QAAQ,EAAG;AAClC,QAAM,IAAI,IAAI,cAAc,OAAO;AACnC,IAAE,KAAK;AACP,IAAE,cAAc;AAChB,GAAC,IAAI,QAAQ,IAAI,iBAAiB,YAAY,CAAC;AACjD;AAOA,SAAS,UAAU,GAA8B,GAAuC;AACtF,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,EAAG,QAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,GAAG,MAAM,MAAM,EAAE,CAAC,CAAC;AACtG,SAAO,MAAM;AACf;AAEO,SAAS,wBAAwB,QAAgB,OAAiC,CAAC,GAAsB;AAC9G,QAAM,MAAM,KAAK,WAAW,iBAAiB;AAC7C,cAAY,GAAG;AAEf,QAAMC,MAAK,IAAI,cAAc,KAAK;AAClC,EAAAA,IAAG,YAAY;AACf,QAAM,UAAU,IAAI,cAAc,IAAI;AACtC,UAAQ,cAAc,KAAK,SAAS;AACpC,QAAM,OAAO,IAAI,cAAc,KAAK;AAIpC,QAAM,KAAK,IAAI,cAAc,KAAK;AAClC,KAAG,YAAY;AACf,QAAM,UAAU,IAAI,cAAc,QAAQ;AAC1C,UAAQ,OAAO;AACf,UAAQ,cAAc;AACtB,UAAQ,iBAAiB,SAAS,MAAM;AACtC,UAAM,OAAO,IAAI,KAAK,CAAC,eAAe,MAAM,CAAC,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC5E,UAAM,IAAI,IAAI,cAAc,GAAG;AAC/B,MAAE,OAAO,IAAI,gBAAgB,IAAI;AACjC,MAAE,WAAW;AACb,MAAE,MAAM;AACR,QAAI,gBAAgB,EAAE,IAAI;AAAA,EAC5B,CAAC;AACD,QAAM,UAAU,IAAI,cAAc,QAAQ;AAC1C,UAAQ,OAAO;AACf,UAAQ,cAAc;AACtB,UAAQ,iBAAiB,SAAS,MAAM;AACtC,UAAM,QAAQ,IAAI,cAAc,OAAO;AACvC,UAAM,OAAO;AACb,UAAM,SAAS;AACf,UAAM,iBAAiB,UAAU,MAAM;AACrC,YAAM,OAAO,MAAM,QAAQ,CAAC;AAC5B,UAAI,CAAC,KAAM;AACX,WAAK,KAAK,KAAK,EAAE,KAAK,CAAC,SAAS;AAC9B,YAAI;AAAE,2BAAiB,QAAQ,IAAI;AAAG,kBAAQ;AAAA,QAAG,SAC1C,KAAK;AAAE,kBAAQ,MAAM,sBAAsB,eAAe,QAAQ,IAAI,UAAU,GAAG;AAAA,QAAG;AAAA,MAC/F,CAAC;AAAA,IACH,CAAC;AACD,UAAM,MAAM;AAAA,EACd,CAAC;AACD,KAAG,OAAO,SAAS,OAAO;AAC1B,EAAAA,IAAG,OAAO,SAAS,IAAI,IAAI;AAE3B,QAAM,gBAAmC,CAAC;AAE1C,QAAM,WAAW,CAACC,SAA2B;AAC3C,UAAM,IAAI,IAAI,cAAc,KAAK;AACjC,MAAE,YAAY;AACd,UAAM,QAAQ,IAAI,cAAc,MAAM;AACtC,UAAM,YAAY;AAClB,UAAM,cAAcA,KAAI;AACxB,UAAM,QAAQA,KAAI;AAClB,UAAM,MAAM,IAAI,cAAc,KAAK;AACnC,QAAI,YAAY;AAChB,UAAM,QAAQ,IAAI,cAAc,QAAQ;AACxC,UAAM,YAAY;AAClB,UAAM,OAAO;AACb,UAAM,cAAc;AACpB,UAAM,QAAQ;AACd,MAAE,OAAO,OAAO,KAAK,KAAK;AAC1B,SAAK,YAAY,CAAC;AAIlB,QAAI;AACJ,UAAM,SAAS,CAAC,MAA2B;AAAE,aAAO,YAAYA,KAAI,MAAM,CAAC;AAAG,gBAAU;AAAA,IAAG;AAC3F,UAAM,UAAU,CAAC,SAA2B,IAAI,kBAAkB;AAClE,aAAS,YAAkB;AAAE,YAAM,WAAW,UAAU,OAAO,YAAYA,KAAI,IAAI,GAAGA,KAAI,OAAO;AAAA,IAAG;AACpG,UAAM,iBAAiB,SAAS,MAAM;AAAE,aAAO,YAAYA,KAAI,MAAMA,KAAI,OAAO;AAAG,WAAK;AAAG,gBAAU;AAAA,IAAG,CAAC;AAEzG,QAAIA,KAAI,SAAS,WAAW;AAC1B,YAAM,KAAK,IAAI,cAAc,OAAO;AACpC,SAAG,OAAO;AACV,SAAG,iBAAiB,UAAU,MAAM,OAAO,GAAG,OAAO,CAAC;AACtD,UAAI,YAAY,EAAE;AAClB,aAAO,MAAM;AAAE,YAAI,CAAC,QAAQ,EAAE,EAAG,IAAG,UAAU,OAAO,YAAYA,KAAI,IAAI,MAAM;AAAA,MAAM;AAAA,IACvF,WAAWA,KAAI,SAAS,UAAU;AAChC,YAAM,MAAM,IAAI,cAAc,OAAO;AACrC,UAAI,OAAO;AACX,UAAI,iBAAiB,UAAU,MAAM,OAAO,OAAO,IAAI,KAAK,CAAC,CAAC;AAC9D,UAAI,YAAY,GAAG;AACnB,aAAO,MAAM;AAAE,YAAI,CAAC,QAAQ,GAAG,EAAG,KAAI,QAAQ,OAAO,OAAO,YAAYA,KAAI,IAAI,KAAK,EAAE;AAAA,MAAG;AAAA,IAC5F,WAAWA,KAAI,SAAS,WAAW;AAGjC,YAAM,OAAO,MAAY;AACvB,cAAM,IAAI,OAAO,YAAYA,KAAI,IAAI;AACrC,YAAI,oBAAgB,gCAAU;AAAA,UAAE,QAAQA,KAAI,UAAU,CAAC;AAAA,UAAG,OAAO,OAAO,MAAM,WAAW,IAAI;AAAA,UAAW,MAAMA;AAAA,UAC5G,QAAQ,CAAC,UAAU;AAAE,mBAAO,KAAK;AAAG,iBAAK;AAAA,UAAG;AAAA,UAAG;AAAA,QAAI,CAAC,CAAC;AAAA,MACzD;AACA,aAAO;AAAA,IACT,WAAWA,KAAI,SAAS,QAAQ;AAE9B,YAAM,MAAM,IAAI,cAAc,QAAQ;AACtC,iBAAW,KAAKA,KAAI,UAAU,CAAC,GAAG;AAChC,cAAM,IAAI,IAAI,cAAc,QAAQ;AACpC,UAAE,QAAQ;AAAG,UAAE,cAAc;AAC7B,YAAI,YAAY,CAAC;AAAA,MACnB;AACA,UAAI,iBAAiB,UAAU,MAAM,OAAO,IAAI,KAAK,CAAC;AACtD,UAAI,YAAY,GAAG;AACnB,aAAO,MAAM;AAAE,YAAI,CAAC,QAAQ,GAAG,EAAG,KAAI,QAAQ,OAAO,OAAO,YAAYA,KAAI,IAAI,KAAK,EAAE;AAAA,MAAG;AAAA,IAC5F,WAAWA,KAAI,SAAS,SAAS;AAC/B,YAAM,MAAM,IAAI,cAAc,OAAO;AACrC,UAAI,OAAO;AACX,UAAI,cAAc;AAClB,UAAI,iBAAiB,UAAU,MAAM,OAAO,IAAI,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAClH,UAAI,YAAY,GAAG;AACnB,aAAO,MAAM;AAAE,YAAI,CAAC,QAAQ,GAAG,GAAG;AAAE,gBAAM,IAAI,OAAO,YAAYA,KAAI,IAAI;AAAG,cAAI,QAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,KAAK,IAAI,IAAI;AAAA,QAAI;AAAA,MAAE;AAAA,IAClI,OAAO;AAEL,YAAM,MAAM,IAAI,cAAc,OAAO;AACrC,UAAI,OAAO;AACX,UAAI,iBAAiB,UAAU,MAAM,OAAO,IAAI,KAAK,CAAC;AACtD,UAAI,YAAY,GAAG;AACnB,aAAO,MAAM;AAAE,YAAI,CAAC,QAAQ,GAAG,EAAG,KAAI,QAAQ,OAAO,OAAO,YAAYA,KAAI,IAAI,KAAK,EAAE;AAAA,MAAG;AAAA,IAC5F;AAEA,SAAK;AACL,cAAU;AACV,kBAAc,KAAK,MAAM;AAAE,WAAK;AAAG,gBAAU;AAAA,IAAG,CAAC;AAAA,EACnD;AAGA,QAAM,QAAQ,OAAO,eAAe;AACpC,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,QAAQ,IAAI,cAAc,KAAK;AACrC,UAAM,YAAY;AAClB,UAAM,cAAc;AACpB,SAAK,YAAY,KAAK;AAAA,EACxB,OAAO;AACL,eAAWA,QAAO,MAAO,UAASA,IAAG;AAAA,EACvC;AAEA,QAAM,UAAU,MAAY;AAAE,eAAW,MAAM,cAAe,IAAG;AAAA,EAAG;AAEpE,OAAK,WAAW,YAAYD,GAAE;AAC9B,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI;AACJ,MAAI,SAAS,EAAG,SAAQ,YAAY,SAAS,MAAM;AAEnD,SAAO;AAAA,IACL,IAAAA;AAAA,IACA;AAAA,IACA,UAAU;AAAE,UAAI,UAAU,OAAW,eAAc,KAAK;AAAG,MAAAA,IAAG,OAAO;AAAA,IAAG;AAAA,EAC1E;AACF;;;AC5MA,IAAAE,kBAA+B;AAyB/B,IAAMC,YAAW;AAGjB,IAAMC,OAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBZ,SAASC,aAAY,KAAqB;AACxC,MAAI,IAAI,eAAeF,SAAQ,EAAG;AAClC,QAAM,IAAI,IAAI,cAAc,OAAO;AACnC,IAAE,KAAKA;AACP,IAAE,cAAcC;AAChB,GAAC,IAAI,QAAQ,IAAI,iBAAiB,YAAY,CAAC;AACjD;AAEA,IAAM,KAAK,CAAC,KAAe,KAAa,KAAc,SAA+B;AACnF,QAAM,IAAI,IAAI,cAAc,GAAG;AAC/B,MAAI,IAAK,GAAE,YAAY;AACvB,MAAI,SAAS,OAAW,GAAE,cAAc;AACxC,SAAO;AACT;AAGA,SAAS,IAAI,KAAe,KAAa,OAAe,aAAa,iBAA8B;AACjG,QAAM,IAAI,GAAG,KAAK,OAAO,eAAe;AACxC,IAAE,OAAO,GAAG,KAAK,QAAQ,iBAAiB,GAAG,GAAG,GAAG,KAAK,QAAQ,YAAY,KAAK,CAAC;AAClF,SAAO;AACT;AAIA,SAAS,YAAY,KAAe,GAAiC;AACnE,QAAM,IAAI,GAAG,KAAK,OAAO,eAAe;AACxC,IAAE,OAAO,GAAG,KAAK,QAAQ,gCAAgC,EAAE,IAAI,CAAC;AAChE,QAAM,OAAO,GAAG,KAAK,QAAQ,iBAAiB,EAAE,IAAI;AACpD,MAAI,CAAC,EAAE,WAAY,MAAK,OAAO,GAAG,KAAK,QAAQ,iBAAiB,YAAY,CAAC;AAC7E,IAAE,OAAO,IAAI;AACb,SAAO;AACT;AAEA,SAAS,QAAQ,KAAe,MAAqB,OAAe,OAAsB,MAAmC;AAC3H,QAAM,IAAI,IAAI,cAAc,SAAS;AACrC,IAAE,QAAQ,SAAS,IAAI;AACvB,IAAE,OAAO;AACT,QAAM,IAAI,IAAI,cAAc,SAAS;AACrC,IAAE,cAAc;AAChB,MAAI,UAAU,KAAM,GAAE,OAAO,GAAG,KAAK,QAAQ,mBAAmB,IAAI,KAAK,EAAE,CAAC;AAC5E,IAAE,OAAO,CAAC;AACV,SAAO;AACT;AAMO,SAAS,sBAAsB,QAAgB,OAA+B,CAAC,GAAoB;AACxG,QAAM,MAAM,KAAK,WAAW,iBAAiB;AAC7C,EAAAC,aAAY,GAAG;AACf,QAAM,QAAI,gCAAe,MAAM;AAC/B,QAAM,OAAO,IAAI,IAAmB,KAAK,QAAQ,CAAC,YAAY,WAAW,CAAC;AAE1E,QAAM,OAAO,GAAG,KAAK,OAAO,WAAW;AACvC,OAAK,OAAO,GAAG,KAAK,MAAM,QAAW,KAAK,SAAS,QAAQ,CAAC;AAG5D,QAAM,KAAK,QAAQ,KAAK,YAAY,EAAE,SAAS,WAAW,qBAAqB,MAAM,KAAK,IAAI,UAAU,CAAC;AACzG,MAAI,EAAE,SAAS,QAAS,IAAG,OAAO,IAAI,KAAK,WAAW,EAAE,SAAS,OAAO,CAAC;AACzE,KAAG,OAAO,IAAI,KAAK,UAAU,EAAE,SAAS,MAAM,CAAC;AAC/C,KAAG,OAAO,IAAI,KAAK,WAAW,GAAG,EAAE,SAAS,aAAa,GAAG,EAAE,SAAS,QAAQ,SAAS,IAAI,MAAM,EAAE,SAAS,QAAQ,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;AAC1I,KAAG,OAAO,IAAI,KAAK,WAAW,EAAE,SAAS,YAAY,CAAC;AACtD,MAAI,EAAE,SAAS,OAAQ,IAAG,OAAO,IAAI,KAAK,UAAU,KAAK,CAAC;AAC1D,MAAI,EAAE,SAAS,KAAM,IAAG,OAAO,IAAI,KAAK,QAAQ,EAAE,SAAS,IAAI,CAAC;AAGhE,MAAI,EAAE,SAAS,cAAe,IAAG,OAAO,IAAI,KAAK,aAAa,EAAE,SAAS,aAAa,CAAC;AACvF,MAAI,EAAE,SAAS,aAAa;AAC1B,OAAG,OAAO,IAAI,KAAK,SAAS,gCAAgC,8BAA8B,CAAC;AAAA,EAC7F;AACA,OAAK,OAAO,EAAE;AAGd,QAAM,OAAO,QAAQ,KAAK,aAAa,aAAa,EAAE,UAAU,QAAQ,KAAK,IAAI,WAAW,CAAC;AAC7F,MAAI,CAAC,EAAE,UAAU,OAAQ,MAAK,OAAO,GAAG,KAAK,OAAO,mBAAmB,WAAW,CAAC;AACnF,aAAW,KAAK,EAAE,WAAW;AAC3B,UAAM,IAAI,GAAG,KAAK,OAAO,eAAe;AACxC,MAAE,OAAO,GAAG,KAAK,QAAQ,gCAAgC,EAAE,MAAM,GAAG,GAAG,KAAK,QAAQ,iBAAiB,EAAE,IAAI,CAAC;AAC5G,SAAK,OAAO,CAAC;AAEb,eAAW,KAAK,EAAE,QAAQ;AACxB,YAAM,KAAK,GAAG,KAAK,OAAO,6BAA6B;AACvD,SAAG,OAAO,GAAG,KAAK,QAAQ,gCAAgC,EAAE,MAAM,GAAG,GAAG,KAAK,QAAQ,iBAAiB,EAAE,IAAI,CAAC;AAC7G,WAAK,OAAO,EAAE;AAAA,IAChB;AAAA,EACF;AACA,OAAK,OAAO,IAAI;AAMhB,QAAM,YAAY,EAAE,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,WAAW,QAAQ,CAAC;AAC1E,QAAM,OAAO,QAAQ,KAAK,cAAc,mBAAmB,WAAW,KAAK,IAAI,YAAY,CAAC;AAC5F,MAAI,CAAC,EAAE,WAAW,OAAQ,MAAK,OAAO,GAAG,KAAK,OAAO,mBAAmB,2BAA2B,CAAC;AACpG,aAAW,KAAK,EAAE,YAAY;AAC5B,UAAM,OAAO,GAAG,KAAK,OAAO,eAAe;AAC3C,SAAK,OAAO,GAAG,KAAK,QAAQ,gCAAgC,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1E,UAAM,OAAO,GAAG,KAAK,QAAQ,iBAAiB,EAAE,SAAS,wBAAwB,GAAG,EAAE,WAAW,MAAM,WAAW;AAClH,QAAI,CAAC,EAAE,SAAU,MAAK,OAAO,GAAG,KAAK,QAAQ,iBAAiB,WAAW,CAAC;AAC1E,SAAK,OAAO,IAAI;AAChB,SAAK,OAAO,IAAI;AAChB,eAAW,KAAK,EAAE,YAAY;AAC5B,YAAM,KAAK,YAAY,KAAK,CAAC;AAC7B,SAAG,UAAU,IAAI,eAAe;AAChC,WAAK,OAAO,EAAE;AAAA,IAChB;AAAA,EACF;AACA,OAAK,OAAO,IAAI;AAGhB,QAAM,aAAa,EAAE,WAAW,OAAO,SAAS,EAAE,WAAW,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,WAAW,QAAQ,CAAC;AAC9G,QAAM,QAAQ,QAAQ,KAAK,cAAc,oBAAoB,YAAY,KAAK,IAAI,YAAY,CAAC;AAC/F,MAAI,CAAC,WAAY,OAAM,OAAO,GAAG,KAAK,OAAO,mBAAmB,eAAe,CAAC;AAChF,aAAW,KAAK,EAAE,WAAW,OAAQ,OAAM,OAAO,YAAY,KAAK,CAAC,CAAC;AACrE,aAAW,KAAK,EAAE,WAAW,OAAO;AAClC,UAAM,OAAO,IAAI,KAAK,UAAU,EAAE,QAAQ,8BAA8B,CAAC;AACzE,eAAW,KAAK,EAAE,YAAY;AAC5B,YAAM,KAAK,YAAY,KAAK,CAAC;AAC7B,SAAG,UAAU,IAAI,eAAe;AAChC,YAAM,OAAO,EAAE;AAAA,IACjB;AAAA,EACF;AACA,OAAK,OAAO,KAAK;AAGjB,MAAI,EAAE,SAAS,QAAQ;AACrB,UAAMC,MAAK,QAAQ,KAAK,YAAY,aAAa,EAAE,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,QAAQ,CAAC,GAAG,KAAK,IAAI,UAAU,CAAC;AAC1H,eAAW,KAAK,EAAE,UAAU;AAC1B,MAAAA,IAAG,OAAO,IAAI,KAAK,MAAM,EAAE,IAAI,CAAC;AAChC,iBAAW,KAAK,EAAE,QAAQ;AACxB,cAAM,KAAK,GAAG,KAAK,OAAO,6BAA6B;AACvD,WAAG,OAAO,GAAG,KAAK,QAAQ,gCAAgC,EAAE,IAAI,GAAG,GAAG,KAAK,QAAQ,iBAAiB,EAAE,IAAI,CAAC;AAC3G,QAAAA,IAAG,OAAO,EAAE;AAAA,MACd;AAAA,IACF;AACA,SAAK,OAAOA,GAAE;AAAA,EAChB;AAGA,QAAM,SAAS,QAAQ,KAAK,UAAU,UAAU,MAAM,KAAK,IAAI,QAAQ,CAAC;AACxE,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,EAAE,MAAM,EAAG,QAAO,OAAO,IAAI,KAAK,KAAK,OAAO,KAAK,CAAC,CAAC;AAC/F,OAAK,OAAO,MAAM;AAElB,OAAK,WAAW,OAAO,IAAI;AAC3B,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,UAAU;AAAE,WAAK,OAAO;AAAA,IAAG;AAAA,EAC7B;AACF;;;AC9KO,IAAM,SAAN,MAAa;AAAA,EAQlB,YAAoB,QAAyB,SAAiB,QAAQ;AAAlD;AAAyB;AAAA,EAA0B;AAAA,EAAnD;AAAA,EAAyB;AAAA,EAP5B,WAA4E;AAAA,IAC3F,MAAM,oBAAI,IAAI;AAAA,IAAG,MAAM,oBAAI,IAAI;AAAA,IAAG,WAAW,oBAAI,IAAI;AAAA,IAAG,QAAQ,oBAAI,IAAI;AAAA,IAAG,OAAO,oBAAI,IAAI;AAAA,EAC5F;AAAA;AAAA,EAEQ,SAAS;AAAA,EACT,aAAa;AAAA;AAAA,EAKrB,IAAI,OAAyB;AAC3B,WAAO,KAAK,OAAO,QAAQ,KAAK,MAAM;AAAA,EACxC;AAAA;AAAA,EAGA,GAA0B,OAAU,SAAyD;AAC3F,UAAM,MAAM,KAAK,SAAS,KAAK;AAC/B,QAAI,IAAI,OAAO;AACf,WAAO,MAAM;AAAE,UAAI,OAAO,OAAO;AAAA,IAAG;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAM,OAAgB,OAAsB;AAC1C,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,QAAQ,KAAK,UAAU;AAC1B,WAAK,OAAO,SAAS,KAAK,QAAQ,EAAE,OAAO,MAAM,CAAC;AAAA,IACpD,WAAW,UAAU,QAAW;AAC9B,UAAI,UAAU,OAAW,OAAM,IAAI,MAAM,uCAAuC;AAChF,WAAK,MAAM;AAAA,IACb,WAAW,CAAC,KAAK,KAAK,OAAO,KAAK,GAAG;AACnC,YAAM,IAAI,MAAM,oCAAoC,KAAK,GAAG,UAAU,SAAY,KAAK,MAAM,KAAK,EAAE,EAAE;AAAA,IACxG;AACA,SAAK,SAAS;AACd,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA,EAGA,OAAa;AACX,QAAI,CAAC,KAAK,KAAM,OAAM,IAAI,MAAM,sBAAsB,KAAK,MAAM,kCAAkC;AACnG,SAAK;AACL,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAClB,QAAI;AACF,aAAO,KAAK,SAAS,GAAG;AACtB,aAAK;AACL,aAAK,QAAQ,KAAK,KAAK,QAAQ,CAAC;AAAA,MAClC;AAAA,IACF,UAAE;AACA,WAAK,aAAa;AAClB,WAAK,SAAS;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,OAAO,IAAkB;AACvB,UAAM,OAAO,KAAK;AAClB,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,wBAAwB,KAAK,MAAM,kCAAkC;AAChG,SAAK,OAAO,EAAE;AACd,SAAK,KAAK;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAa;AACX,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,OAAO,QAAsB;AAC3B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEQ,QAAQ,MAAwB;AACtC,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AAAQ,eAAO,KAAK,KAAK,QAAQ,IAAI;AAAA,MAC1C,KAAK;AAAQ,eAAO,KAAK,KAAK,QAAQ,IAAI;AAAA,MAC1C,KAAK;AAAa,eAAO,KAAK,KAAK,aAAa,IAAI;AAAA,MACpD,KAAK;AAAU,eAAO,KAAK,KAAK,UAAU,EAAE,SAAS,KAAK,SAAS,SAAS,KAAK,QAAQ,CAAC;AAAA,MAC1F,KAAK;AAAO,eAAO,KAAK,KAAK,SAAS,KAAK,MAAM;AAAA,IACnD;AAAA,EACF;AAAA,EAEQ,KAA4B,OAAU,SAAgC;AAC5E,eAAW,WAAW,CAAC,GAAG,KAAK,SAAS,KAAK,CAAC,EAAG,CAAC,QAAyC,OAAO;AAAA,EACpG;AACF;;;AChHO,SAAS,oBAAoB,cAAsB,UAAiC;AACzF,QAAM,WAAW,KAAK,MAAM,YAAY;AACxC,QAAM,QAAQ,SAAS,SAAS,CAAC;AAGjC,QAAM,MAAM,aAAa,MAAM,SAAS,KAAK,QAAQ,IAAI,KAAK;AAC9D,SAAO;AAAA,IACL,QAAQ,QAA+B;AACrC,YAAM,OAAO,MAAM,MAAM;AACzB,UAAI,CAAC,QAAQ,CAAC,KAAK,KAAM,QAAO;AAChC,aAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,KAAK,IAAI;AAAA,IACtC;AAAA,EACF;AACF;","names":["createKernelStateLogger","el","row","import_runtime","STYLE_ID","CSS","injectStyle","gd"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/save.ts","../src/properties.ts","../src/logger.ts","../src/debug.ts","../src/refresh.ts","../src/inspector.ts","../src/bundle-inspector.ts","../src/runner.ts","../src/audio.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @patterkit/play-helpers - the Patterplay JS companion.\n//\n// Thin game-integration helpers around @patterkit/runtime's Engine: save/load\n// serialisation, runtime property setters, a state logger, and the live-debug\n// link. None are required to play a bundle - they smooth the common host wiring\n// (localStorage saves, pushing game state into dialogue, debugging what changed,\n// streaming the cursor to Patterpad's live debugger).\n// ---------------------------------------------------------------------------\n\nexport { SAVE_SCHEMA, saveState, loadState, serializeState, deserializeState } from \"./save.js\";\nexport type { SaveEnvelope } from \"./save.js\";\n\nexport { getProperty, setProperty, setProperties } from \"./properties.js\";\nexport type { PropertyValue } from \"./properties.js\";\n\nexport { snapshotState, diffState, createStateLogger } from \"./logger.js\";\nexport type { StateSnapshot, StateChange, StateValue, StateLogger, StateLoggerOptions } from \"./logger.js\";\n\nexport { createDebugLink } from \"./debug.js\";\nexport type { DebugLink, DebugLinkOptions, DebugSocketLike, PlayRequest, PlayResult } from \"./debug.js\";\n\nexport { applyLiveBundle } from \"./refresh.js\";\nexport type { LiveBundleResult } from \"./refresh.js\";\n\nexport { createPropertyInspector } from \"./inspector.js\";\nexport type { PropertyInspector, PropertyInspectorOptions } from \"./inspector.js\";\n\nexport { createBundleInspector } from \"./bundle-inspector.js\";\nexport type { BundleInspector, BundleInspectorOptions, BundleSection } from \"./bundle-inspector.js\";\n\nexport { Runner } from \"./runner.js\";\nexport type { RunnerEvent, RunnerEvents } from \"./runner.js\";\n\nexport { createAudioResolver } from \"./audio.js\";\nexport type { AudioResolver } from \"./audio.js\";\n","// ---------------------------------------------------------------------------\n// Save / load: wrap the Engine's whole-game snapshot in a tagged, versioned\n// envelope so a host can drop it into localStorage / a file and restore it\n// safely (a foreign blob throws instead of corrupting a run).\n// ---------------------------------------------------------------------------\n\nimport type { Engine } from \"@patterkit/runtime\";\nimport { SAVE_SCHEMA } from \"@patterkit/model\";\nimport type { SaveEnvelope } from \"@patterkit/model\";\n\n// The envelope (`schema` + `save`) and the save shape inside it are the FAMILY's contract, defined in\n// @patterkit/model and written identically by every Patterplay runtime. Re-exported so existing\n// imports keep working.\nexport { SAVE_SCHEMA };\nexport type { SaveEnvelope };\n\n/** Capture the whole game as a tagged envelope (wraps `engine.saveGame()`). */\nexport function saveState(engine: Engine): SaveEnvelope {\n return { schema: SAVE_SCHEMA, save: engine.saveGame() };\n}\n\n/** Restore a {@link saveState} envelope into an engine (fresh or live). Throws on a foreign/blank blob. */\nexport function loadState(engine: Engine, env: SaveEnvelope): void {\n if (!env || env.schema !== SAVE_SCHEMA || !env.save) {\n throw new Error(`loadState: not a ${SAVE_SCHEMA} envelope`);\n }\n engine.loadGame(env.save);\n}\n\n/** Serialise the whole game to a JSON string (envelope + save-game) - drop into localStorage or a file. */\nexport function serializeState(engine: Engine): string {\n return JSON.stringify(saveState(engine));\n}\n\n/** Parse + restore a {@link serializeState} string. Throws on malformed JSON or a foreign envelope. */\nexport function deserializeState(engine: Engine, json: string): void {\n loadState(engine, JSON.parse(json) as SaveEnvelope);\n}\n","// ---------------------------------------------------------------------------\n// Runtime property setters: read / write `@patter` globals, `@scene` props, or\n// a wired foreign scope at runtime - e.g. the game pushing inventory into the\n// dialogue, or reading a flag the dialogue set. Thin pass-throughs, plus a batch\n// setter for convenience.\n//\n// They take an Engine or a Flow. Game-wide properties (`@hp`, `@world.x`) go\n// through either; `@scene` props belong to a flow, since each flow has its own,\n// so they need the Flow: the Engine refuses an `@scene` ref.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@patterkit/runtime\";\n\n/** A property value the engine accepts (a Patter scalar: number / boolean / string / string[] flags). */\nexport type PropertyValue = Parameters<Engine[\"setProperty\"]>[1];\n\n/** Where properties are read and written: the Engine (game-wide refs), or a Flow (those plus `@scene`). */\nexport type PropertyHolder = Engine | Flow;\n\n/** Read a runtime property (`@hp`, a foreign `@world.x`, or with a Flow `@scene.locked`). Undefined when unset. */\nexport function getProperty(holder: PropertyHolder, ref: string): PropertyValue | undefined {\n return holder.getProperty(ref);\n}\n\n/** Set one runtime property. Mirrors `setProperty` on the Engine or Flow, exported for discoverability + symmetry. */\nexport function setProperty(holder: PropertyHolder, ref: string, value: PropertyValue): void {\n holder.setProperty(ref, value);\n}\n\n/** Set many at once, e.g. `setProperties(engine, { \"@hp\": 10 })` or `setProperties(flow, { \"@scene.locked\": false })`. */\nexport function setProperties(holder: PropertyHolder, values: Record<string, PropertyValue>): void {\n for (const [ref, value] of Object.entries(values)) holder.setProperty(ref, value);\n}\n","// ---------------------------------------------------------------------------\n// The Patterplay state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry, plus `logStep`, which is this product's own.\n//\n// It used to diff whole `engine.saveGame()` snapshots. That could only ever say\n// what changed BETWEEN captures, which means it could not see a value that\n// changed and changed back, could not name the reason a host attached to a\n// write, and reported a write only once something asked. The kernel logs a\n// property write as it LANDS, on the PropertyBag audit hook, and keeps the diff\n// for state that has no hook - here, the visit counts.\n//\n// The Storylet Engine's logger was already this shape; its core is what moved\n// into the kernel. Same core, two adapters.\n//\n// Paths, unchanged:\n// @patter.x the shared globals\n// @scene:<sceneId>.x the shared scene props\n// visit:<nodeId> shared visit counts\n// <flowId>/... the same three, per flow (its not-shared halves)\n// ---------------------------------------------------------------------------\n\nimport type { Engine, StepResult } from \"@patterkit/runtime\";\nimport { createStateLogger as createKernelStateLogger, diffState } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, ScalarValue, StateChange, StateSnapshot } from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @patterkit/play-helpers should not have to care that they moved.\nexport { diffState };\nexport type { StateChange, StateSnapshot };\n\n/** A flattened runtime-state value: a Patter scalar. The kernel calls this `ScalarValue`;\n * the alias stays because it is exported API and reads in Patter's own vocabulary. */\nexport type StateValue = ScalarValue;\n\n/** Flatten the engine's whole-game state into a path -> value map (shared scopes + every live\n * flow). The logger does not diff this - it mounts the bags directly - but it stays as the public\n * \"what is the state right now\" call, and it reads the same mounts, so the two agree on the path\n * space. A bag loaded into the registry but not yet claimed (a scene no flow has re-entered since\n * a load) appears once it is. */\nexport function snapshotState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const m of allMounts(engine)) {\n const prefix = m.pathPrefix ?? m.bag.pathPrefix;\n for (const [name, v] of Object.entries(m.bag.save())) out[`${prefix}${name}`] = v;\n }\n Object.assign(out, visitState(engine));\n return out;\n}\n\n/** Every bag the engine and its flows hold, with the path each answers to in a log. */\nfunction allMounts(engine: Engine): LogMount[] {\n return [...engine.listBags(), ...engine.flows().flatMap((f) => f.listBags())];\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to `console.log`. */\n sink?: (line: string) => void;\n /** Prefix tag for every line, e.g. the flow / save-slot name. */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state (no logging). */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the property writes already logged as they landed,\n * plus the visit counts, diffed and re-baselined. */\n capture(): StateChange[];\n /** Trace one played step (line / text / game-event / choice / end), including any `gameData`. */\n logStep(step: StepResult): void;\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\nfunction describeStep(step: StepResult): string {\n switch (step.type) {\n case \"line\": return `line ${step.character ?? \"?\"}: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"text\": return `text: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"gameEvent\": return `game event ${step.id}${gd(step.gameData)}`;\n case \"choice\": return `choice (${step.options.length} option${step.options.length === 1 ? \"\" : \"s\"})`;\n case \"end\": return \"end\";\n }\n}\nconst gd = (data: unknown): string => (data ? ` gameData=${JSON.stringify(data)}` : \"\");\n\n/** The visit counts, which live in no bag and so have no audit hook: the kernel diffs\n * these on capture, exactly as the whole logger used to work. */\nfunction visitState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const [id, n] of Object.entries(engine.getVisitCounts())) out[`visit:${id}`] = n;\n for (const flow of engine.flows()) {\n for (const [id, n] of Object.entries(flow.getVisitCounts())) out[`${flow.id}/visit:${id}`] = n;\n }\n return out;\n}\n\n/** Create a state logger over an engine. Property writes log as they land; call `capture()`\n * after each `advance`/`choose` to pick up the visit counts and re-baseline. */\nexport function createStateLogger(engine: Engine, opts: StateLoggerOptions = {}): StateLogger {\n const tag = opts.label ? `[${opts.label}] ` : \"\";\n const kernel = createKernelStateLogger({\n // Re-read on every capture: openFlow and loadGame both replace bags, and the kernel\n // re-mounts whatever it is handed.\n mounts: (): LogMount[] => allMounts(engine),\n extra: () => visitState(engine),\n }, { sink: opts.sink, label: tag });\n\n return {\n snapshot: () => snapshotState(engine),\n capture: () => kernel.capture(),\n dispose: () => kernel.dispose(),\n logStep(step) { (opts.sink ?? ((l: string) => console.log(l)))(`${tag}${describeStep(step)}`); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live debug link (#181) - the game-side client.\n//\n// Streams a running game's story position to Patterpad over a localhost\n// WebSocket, so the editor can follow the live cursor like a debugger. It is\n// OBSERVE-ONLY: the game stays in control; the editor is a passive mirror.\n//\n// Wire protocol `patterplay/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:1, build, project?, flows? } - on connect\n// frame : { t:\"frame\", flow, sceneId, beatId, type, choiceId? } - per transition\n// flow : { t:\"flowOpen\" | \"flowClose\", flow } - lifecycle\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: live bundle refresh.\n// `data` is the full .patterc JSON. Pass an `onBundle` handler (or just feed it to\n// `applyLiveBundle`, which picks strings-only vs full hot swap itself), then call\n// `link.setBuild(newHash)` so the editor's match/stale pill updates.\n// play : { t:\"play\", v:1, id, sceneId, blockId, takeOver, build? } - EDITOR -> game: Play in game.\n// The writer asked the running game to start that block now (`takeOver`: stop what the\n// speakers are doing first). `build` is the editor's bundle hash, so a game still on an\n// older bundle can answer \"staleBuild\". Pass an `onPlay` handler; it answers with\n// `link.replyPlay(id, result)`, then or later. With no handler the link answers\n// { started:false, reason:\"unhandled\" } itself.\n// played: { t:\"played\", id, started, reason?, detail? } - game -> editor: the answer to `play`.\n// `reason` is a short code (any string passes through; Patterpad has plain words for\n// busy, unknownBlock, staleBuild, unhandled, and Patterstage's own); `detail` says more.\n//\n// The link is loopback-only on the editor side (127.0.0.1), so there's no pairing\n// token - only processes on the same machine can reach it. The client never throws\n// into the game loop - a missing/closed editor just means frames go nowhere. Wire\n// it up after each advance()/choose():\n//\n// const link = createDebugLink({ build: bundle.content.hash });\n// link.flowOpened(\"main\");\n// // ...after each step:\n// link.observe(\"main\", flow.currentScene, step.id ?? null, step.type);\n// ---------------------------------------------------------------------------\n\n/** A minimal structural type for a WebSocket implementation (browsers + Node 21+ have a global one). */\nexport interface DebugSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"close\" | \"error\", listener: () => void): void;\n /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype DebugSocketCtor = new (url: string) => DebugSocketLike;\n\n/** The editor asked the running game to play a block now (Play in game). Ids are the bundle's. */\nexport interface PlayRequest {\n /** Echo it in `replyPlay` so the editor can match the answer to its request. */\n id: string;\n sceneId: string;\n blockId: string;\n /** Stop what the speakers are doing first, rather than turn the request down as busy. */\n takeOver: boolean;\n /** The editor's bundle hash, when it sent one: a game on another build can answer \"staleBuild\". */\n build?: string;\n}\n\n/** The game's answer to a `PlayRequest`. */\nexport interface PlayResult {\n started: boolean;\n /** Why it didn't start, as a short code: \"busy\", \"unknownBlock\", \"staleBuild\", or the game's own. */\n reason?: string;\n /** More, in plain words (which speaker is busy, which block was missing). */\n detail?: string;\n}\n\n/** The `reason` the link answers with when nothing handles `play`. */\nconst PLAY_UNHANDLED = \"unhandled\";\n\nexport interface DebugLinkOptions {\n /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */\n build: string;\n /** Optional project name, shown in the editor's debug panel. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4471`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (Node < 21, or tests with `ws`). */\n WebSocket?: DebugSocketCtor;\n /** Live bundle refresh: the editor pushed a freshly compiled bundle. `data` is the .patterc JSON;\n * hand it (with your current engine + bundle) to `applyLiveBundle`, re-bind your flow handles if it\n * hot-swapped, then call `link.setBuild(build)`. Never called with malformed payloads. */\n onBundle?: (msg: { build: string; data: string }) => void;\n /** Play in game: the writer asked the game to start a block now. Answer with `link.replyPlay(req.id,\n * result)`, in the handler or later (an answer after the editor stopped waiting is ignored). Without a\n * handler the link answers `{ started: false, reason: \"unhandled\" }`. Never called with malformed\n * payloads. */\n onPlay?: (req: PlayRequest) => void;\n}\n\nexport interface DebugLink {\n /** Tell the editor a flow opened (so it can list it in the follow selector). */\n flowOpened(flowId: string): void;\n /** Report the current position of a flow - call after each advance()/choose(). */\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void;\n /** Tell the editor a flow closed. */\n flowClosed(flowId: string): void;\n /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's\n * match/stale pill updates and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Answer a `play` request (see `onPlay`). Safe to call at any time; a no-op once the link is closed. */\n replyPlay(id: string, result: PlayResult): void;\n /** Close the link. */\n close(): void;\n /** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game \"the editor\n * is not listening\" and \"I never attached\" look the same, and this tells them apart. */\n readonly state: \"connecting\" | \"connected\" | \"closed\";\n /** The build the link handshakes with (the bundle's content hash), as `setBuild` last left it. */\n readonly build: string;\n /** The address the link dials. */\n readonly url: string;\n}\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/**\n * Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects\n * or if it was never listening - safe to leave wired into a shipping build behind a flag.\n */\nexport function createDebugLink(opts: DebugLinkOptions): DebugLink {\n const url = opts.url ?? \"ws://127.0.0.1:4471\";\n const Ctor: DebugSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: DebugSocketCtor }).WebSocket;\n const flows = new Set<string>();\n let queue: string[] = [];\n let sock: DebugSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live bundle refresh lands\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n // Queue only while there is a socket that is connecting or open. Once the editor is gone (or never\n // answered), the socket is dropped and a message has nowhere to go, so it is dropped too: queueing it\n // anyway grew the queue by one string per step for the rest of a game that shipped with the link wired\n // in, which is exactly the build the docs say this is safe in.\n const post = (msg: object): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n queue.push(JSON.stringify(msg));\n flush();\n };\n\n if (!Ctor) {\n // No WebSocket available (old Node, no impl passed) - degrade to a silent no-op link.\n return { flowOpened() {}, observe() {}, flowClosed() {}, setBuild() {}, replyPlay() {}, close() { closed = true; }, state: \"closed\", build, url };\n }\n\n const replyPlay = (id: string, result: PlayResult): void => {\n post({\n t: \"played\", id, started: result.started === true,\n ...(typeof result.reason === \"string\" ? { reason: result.reason } : {}),\n ...(typeof result.detail === \"string\" ? { detail: result.detail } : {}),\n });\n };\n\n const sendHello = (): void => {\n const hello = JSON.stringify({ t: \"hello\", v: 1, build, project: opts.project, flows: [...flows] });\n try { sock?.send(hello); } catch { /* race: closed immediately */ }\n };\n\n try {\n sock = new Ctor(url);\n sock.addEventListener(\"open\", () => {\n // Handshake first, so the editor can verify the build + seed the flow list before frames arrive.\n sendHello();\n flush();\n });\n // Editor -> game: a live bundle refresh, or Play in game. Validate the shape here so the host's\n // handlers never see a malformed payload; everything else the editor might send is ignored.\n sock.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (typeof ev.data !== \"string\") return;\n let msg: Record<string, unknown>;\n try { msg = JSON.parse(ev.data) as Record<string, unknown>; } catch { return; }\n if (msg === null || typeof msg !== \"object\") return;\n if (msg.t === \"bundle\") {\n if (opts.onBundle && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } else if (msg.t === \"play\") {\n const req = parsePlayRequest(msg);\n if (!req) return;\n if (!opts.onPlay) { replyPlay(req.id, { started: false, reason: PLAY_UNHANDLED }); return; }\n try { opts.onPlay(req); } catch { /* never throw into the socket: the editor times out */ }\n }\n });\n sock.addEventListener(\"error\", () => { /* editor not listening - stay a no-op */ });\n sock.addEventListener(\"close\", () => { sock = null; queue = []; });\n } catch { sock = null; } // malformed URL etc. - never throw into the game\n\n return {\n flowOpened(flowId: string): void { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); },\n flowClosed(flowId: string): void { flows.delete(flowId); post({ t: \"flowClose\", flow: flowId }); },\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void {\n // A flow reports itself. `flowOpened` is the host's job and nothing could check it, so a game\n // that opened a flow and forgot to announce it left the editor's list short - and the omission\n // survived a reconnect, because the hello sends this set. Observing a flow we have not seen\n // announces it first, which makes the host's call a courtesy (it lists the flow before its\n // first step) rather than something to remember.\n if (!flows.has(flowId)) { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); }\n post({ t: \"frame\", flow: flowId, sceneId, beatId, type, choiceId });\n },\n replyPlay,\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n if (sock && sock.readyState === OPEN) sendHello(); // re-handshake: the editor re-reads the build\n },\n close(): void { closed = true; queue = []; try { sock?.close(); } catch { /* already gone */ } sock = null; },\n get state() { return closed || !sock ? \"closed\" : sock.readyState === OPEN ? \"connected\" : \"connecting\"; },\n get build() { return build; },\n url,\n };\n}\n\n/** A `play` message's fields, or null when it isn't a well-formed request. */\nfunction parsePlayRequest(msg: Record<string, unknown>): PlayRequest | null {\n const { id, sceneId, blockId } = msg;\n if (typeof id !== \"string\" || id === \"\" || typeof sceneId !== \"string\" || sceneId === \"\"\n || typeof blockId !== \"string\" || blockId === \"\") return null;\n return {\n id, sceneId, blockId, takeOver: msg.takeOver === true,\n ...(typeof msg.build === \"string\" && msg.build !== \"\" ? { build: msg.build } : {}),\n };\n}\n","// ---------------------------------------------------------------------------\n// Live bundle refresh (design/proposals/live-bundle-refresh.md) - the game-side\n// applier. The editor pushes a freshly compiled bundle over the debug link\n// (createDebugLink's `onBundle`); this helper picks the right tier and applies\n// it:\n// - same structureHash -> tier 1: engine.replaceStrings() - nothing restarts.\n// - changed structure -> tier 2: engine.hotSwap() - the run carried over,\n// content drift resolved by the save system (§9.8).\n// Returns the engine to keep using (the SAME one for \"text\", a REPLACEMENT for\n// \"structure\" - re-bind your Flow handles via engine.getFlow(id)) plus the\n// parsed bundle for the next comparison. Wire-up:\n//\n// let { engine, bundle } = boot();\n// const link = createDebugLink({\n// build: bundle.content.hash!,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, bundle, data);\n// engine = r.engine; bundle = r.bundle;\n// if (r.kind === \"structure\") flow = engine.getFlow(\"main\")!;\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport { Engine } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/runtime\";\n\nexport interface LiveBundleResult {\n /** The engine to keep using: the same instance for a \"text\" swap, a replacement for \"structure\". */\n engine: Engine;\n /** The parsed pushed bundle - hold on to it for the next apply's comparison. */\n bundle: Bundle;\n /** Which tier applied: \"text\" (strings-only, nothing restarted) or \"structure\" (full hot swap). */\n kind: \"text\" | \"structure\";\n}\n\n/**\n * Apply a bundle the editor pushed over the debug link. `current` is the bundle `engine` is running\n * (needed for the structure-hash comparison); `data` is the pushed .patterc JSON. Throws only on\n * unparseable JSON - a structural swap's edge cases are absorbed by the §9.8 drift policy inside\n * `hotSwap`, which never throws for ordinary edits.\n */\nexport function applyLiveBundle(engine: Engine, current: Bundle, data: string): LiveBundleResult {\n const next = JSON.parse(data) as Bundle;\n // Same structure = a text-only edit: swap the string tables in place and keep everything running.\n // Missing structureHash on either side (an older compiler) falls through to the full swap - safe,\n // just less gentle than it could be.\n const sameStructure = current.content.structureHash !== undefined\n && current.content.structureHash === next.content.structureHash;\n if (sameStructure) {\n engine.replaceStrings(next);\n return { engine, bundle: next, kind: \"text\" };\n }\n return { engine: engine.hotSwap(next), bundle: next, kind: \"structure\" };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// A live @patter property inspector for the browser: a small DOM panel that\n// watches AND edits a running Engine's shared @patter properties. Type-aware\n// editors (bool / number / text / enum / flags), a reset-to-default per row, and\n// a poll that refreshes values WITHOUT clobbering the field you're editing.\n//\n// The JS parity of the Unity PatterStateWindow / Godot PatterStatePanel. Those\n// use a PatterDebug registry because the editor window is separate from the game;\n// here the game runs in-process, so you just pass the engine directly:\n//\n// const inspector = createPropertyInspector(engine, { container: document.body });\n// // ...later: inspector.destroy();\n// ---------------------------------------------------------------------------\n\nimport { rungStrip } from \"@wildwinter/app-shell/value-control\";\nimport type { Engine, PropertyRow } from \"@patterkit/runtime\";\nimport { serializeState, deserializeState } from \"./save.js\";\nimport type { PropertyValue } from \"./properties.js\";\n\nexport interface PropertyInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Runtime state\". */\n title?: string;\n /** Live-refresh interval in ms; 0 disables polling (call `refresh()` yourself). Default 250. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** Re-read every property and update the editors, skipping the field you're editing. */\n refresh(): void;\n /** Stop polling and remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-inspector-style\";\nconst CSS = `\n.pp-insp{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:22rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-insp h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-insp-empty{color:#8a9691;font-style:italic}\n.pp-insp-io { display: flex; gap: 6px; margin: 0 0 8px; }\n .pp-insp-io button { font: inherit; padding: 2px 8px; cursor: pointer; }\n .pp-insp-row{display:flex;align-items:center;gap:.4rem;margin:.18rem 0}\n.pp-insp-ref{flex:0 0 8rem;font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.pp-insp-ctl{flex:1;min-width:0;display:flex}\n.pp-insp-ctl input[type=text],.pp-insp-ctl input[type=number],.pp-insp-ctl select{width:100%;box-sizing:border-box;font:inherit;padding:.15rem .3rem;border:1px solid #cfc7b8;border-radius:6px;background:#fff;color:inherit}\n.pp-insp-reset{flex:0 0 auto;border:1px solid #cfc7b8;background:#fff;border-radius:6px;cursor:pointer;color:#5c6b62;width:1.6rem;height:1.6rem;line-height:1;padding:0}\n.pp-insp-reset:disabled{opacity:.35;cursor:default}\n.pp-insp-ctl .seg{display:inline-flex;flex-wrap:wrap;border:1px solid #cfc7b8;border-radius:6px;overflow:hidden;background:#fff}\n.pp-insp-ctl .seg-opt{background:none;border:0;cursor:pointer;color:#5c6b62;font:400 .78rem/1.3 ui-monospace,monospace;padding:.12rem .4rem}\n.pp-insp-ctl .seg-opt+.seg-opt{border-left:1px solid #cfc7b8}\n.pp-insp-ctl .seg-opt.on{color:#214f4b;background:#dbe8e4;font-weight:600;cursor:default}\n.pp-insp-ctl .shell-rung.stray{color:#9a3b2e;text-decoration:line-through}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\n/** Has the DISPLAYED value changed? Order-sensitive on purpose, and deliberately\n * NOT the language's value equality, which compares flags as a set: this asks\n * whether the row needs redrawing, and the row renders the list in its stored\n * order, so a reordering is a change here even though it is not a change to\n * what the value means. */\nfunction sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => x === b[i]);\n return a === b;\n}\n\nexport function createPropertyInspector(engine: Engine, opts: PropertyInspectorOptions = {}): PropertyInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n\n const el = doc.createElement(\"div\");\n el.className = \"pp-insp\";\n const heading = doc.createElement(\"h4\");\n heading.textContent = opts.title ?? \"Runtime state\";\n const list = doc.createElement(\"div\");\n\n // Save/Load the whole run as a patter/save@0 envelope file - the parity of Unity's\n // PatterStateWindow buttons, Godot's state-panel dialogs, and Unreal's Slate panel.\n const io = doc.createElement(\"div\");\n io.className = \"pp-insp-io\";\n const saveBtn = doc.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.textContent = \"Save state\\u2026\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const a = doc.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"save.patterstate\";\n a.click();\n URL.revokeObjectURL(a.href);\n });\n const loadBtn = doc.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.textContent = \"Load state\\u2026\";\n loadBtn.addEventListener(\"click\", () => {\n const input = doc.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".patterstate,application/json\";\n input.addEventListener(\"change\", () => {\n const file = input.files?.[0];\n if (!file) return;\n void file.text().then((text) => {\n try { deserializeState(engine, text); refresh(); }\n catch (err) { console.error(\"Load state failed:\", err instanceof Error ? err.message : err); }\n });\n });\n input.click();\n });\n io.append(saveBtn, loadBtn);\n el.append(heading, io, list);\n\n const rowRefreshers: Array<() => void> = [];\n\n const buildRow = (row: PropertyRow): void => {\n const r = doc.createElement(\"div\");\n r.className = \"pp-insp-row\";\n const label = doc.createElement(\"span\");\n label.className = \"pp-insp-ref\";\n label.textContent = row.path;\n label.title = row.path;\n const ctl = doc.createElement(\"div\");\n ctl.className = \"pp-insp-ctl\";\n const reset = doc.createElement(\"button\");\n reset.className = \"pp-insp-reset\";\n reset.type = \"button\";\n reset.textContent = \"↺\"; // ↺\n reset.title = \"Reset to default\";\n r.append(label, ctl, reset);\n list.appendChild(r);\n\n // `read` pushes the live engine value into the editor, but never while that editor has focus\n // (so it can't wipe what you're typing). `syncReset` disables the ↺ when the value is the default.\n let read: () => void;\n const commit = (v: PropertyValue): void => { engine.setProperty(row.path, v); syncReset(); };\n const focused = (node: Element): boolean => doc.activeElement === node;\n function syncReset(): void { reset.disabled = sameValue(engine.getProperty(row.path), row.default); }\n reset.addEventListener(\"click\", () => { engine.setProperty(row.path, row.default); read(); syncReset(); });\n\n if (row.type === \"boolean\") {\n const cb = doc.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.addEventListener(\"change\", () => commit(cb.checked));\n ctl.appendChild(cb);\n read = () => { if (!focused(cb)) cb.checked = engine.getProperty(row.path) === true; };\n } else if (row.type === \"number\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"number\";\n inp.addEventListener(\"change\", () => commit(Number(inp.value)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"quality\") {\n // A quality shows as its LADDER, the current stage marked, as in both editors: the family's one strip\n // (@wildwinter/app-shell/value-control), built in this inspector's own document.\n const draw = (): void => {\n const v = engine.getProperty(row.path);\n ctl.replaceChildren(rungStrip({ stages: row.stages ?? [], value: typeof v === \"string\" ? v : undefined, decl: row,\n onPick: (stage) => { commit(stage); draw(); }, doc }));\n };\n read = draw;\n } else if (row.type === \"enum\") {\n // An enum edits as a dropdown of its values.\n const sel = doc.createElement(\"select\");\n for (const v of row.values ?? []) {\n const o = doc.createElement(\"option\");\n o.value = v; o.textContent = v;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", () => commit(sel.value));\n ctl.appendChild(sel);\n read = () => { if (!focused(sel)) sel.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"flags\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.placeholder = \"comma, separated, flags\";\n inp.addEventListener(\"change\", () => commit(inp.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) { const v = engine.getProperty(row.path); inp.value = Array.isArray(v) ? v.join(\", \") : \"\"; } };\n } else {\n // string (and any unrecognised type) -> a text field\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.addEventListener(\"change\", () => commit(inp.value));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n }\n\n read();\n syncReset();\n rowRefreshers.push(() => { read(); syncReset(); });\n };\n\n // Properties are fixed for a bundle, so build the rows once; polling just refreshes their values.\n const props = engine.listProperties();\n if (props.length === 0) {\n const empty = doc.createElement(\"div\");\n empty.className = \"pp-insp-empty\";\n empty.textContent = \"No @patter properties.\";\n list.appendChild(empty);\n } else {\n for (const row of props) buildRow(row);\n }\n\n const refresh = (): void => { for (const fn of rowRefreshers) fn(); };\n\n opts.container?.appendChild(el);\n const pollMs = opts.pollMs ?? 250;\n let timer: ReturnType<typeof setInterval> | undefined;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n return {\n el,\n refresh,\n destroy() { if (timer !== undefined) clearInterval(timer); el.remove(); },\n };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// The bundle inspector's VIEW, for the web: a read-only panel over\n// `describeBundle`, in the same visual grammar as the live property inspector.\n//\n// The other three runtimes hang their view off an imported asset - a Unity\n// CustomEditor, an Unreal details customisation, a Godot EditorInspectorPlugin.\n// JS has no asset pipeline, so there is no inspector to extend and the summary\n// function IS the surface. This exists because \"the function is the surface\"\n// leaves the JS side without the thing the other three give you for free: a\n// place to LOOK at a bundle. A drop-in page can now do\n//\n// const panel = createBundleInspector(bundle, { container: document.body });\n// // ...later: panel.destroy();\n//\n// Read-only on purpose. The live inspector edits because a running game has\n// state worth changing; this describes an artefact, and there is nothing here a\n// reader could sensibly write to.\n// ---------------------------------------------------------------------------\n\nimport { describeBundle } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/model\";\nimport type { BundleDescription, PropertySummary } from \"@patterkit/runtime\";\n\nexport interface BundleInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Bundle\". */\n title?: string;\n /** Sections open on mount. Default: identity and addresses, the two an\n * integrator reaches for first; the rest are a click away. */\n open?: BundleSection[];\n}\n\nexport type BundleSection = \"identity\" | \"addresses\" | \"hostScopes\" | \"properties\" | \"gameData\" | \"counts\";\n\nexport interface BundleInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** The description the panel is showing, for a caller that wants the data too. */\n readonly description: BundleDescription;\n /** Remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-bundle-style\";\n// Deliberately the property inspector's palette and metrics: the two panels are\n// often on the same page, and a second look-and-feel would read as a second tool.\nconst CSS = `\n.pp-bundle{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:26rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-bundle h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-bundle details{border-top:1px solid #e3dccf;padding:.3rem 0}\n.pp-bundle details:first-of-type{border-top:0}\n.pp-bundle summary{cursor:pointer;font-weight:600;color:#214f4b;list-style:none}\n.pp-bundle summary::-webkit-details-marker{display:none}\n.pp-bundle summary::before{content:\"\\\\25B8\";display:inline-block;width:1rem;color:#8a9691}\n.pp-bundle details[open]>summary::before{content:\"\\\\25BE\"}\n.pp-bundle-count{color:#8a9691;font-weight:400}\n.pp-bundle-row{display:flex;gap:.4rem;margin:.16rem 0 .16rem 1rem}\n.pp-bundle-key{flex:0 0 8rem;color:#5c6b62}\n.pp-bundle-val,.pp-bundle-addr{font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;word-break:break-all}\n.pp-bundle-sub{margin-left:2rem}\n.pp-bundle-empty{color:#8a9691;font-style:italic;margin-left:1rem}\n.pp-bundle-warn{color:#8a3a2f;font-weight:600}\n.pp-bundle-tag{font-size:.7rem;color:#5c6b62;border:1px solid #cfc7b8;border-radius:5px;padding:0 .25rem;margin-left:.3rem}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\nconst el = (doc: Document, tag: string, cls?: string, text?: string): HTMLElement => {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text !== undefined) e.textContent = text;\n return e;\n};\n\n/** One \"key: value\" line. */\nfunction row(doc: Document, key: string, value: string, valueClass = \"pp-bundle-val\"): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key\", key), el(doc, \"span\", valueClass, value));\n return r;\n}\n\n/** A property line: the name, and its type. A bundle compiled since 2026-10-19 resolves every default, host\n * scopes' included, so \"no default\" shows only on an older bundle, where it meant the game supplied it. */\nfunction propertyRow(doc: Document, p: PropertySummary): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", p.name));\n const meta = el(doc, \"span\", \"pp-bundle-val\", p.type);\n if (!p.hasDefault) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"no default\"));\n r.append(meta);\n return r;\n}\n\nfunction section(doc: Document, name: BundleSection, label: string, count: number | null, open: boolean): HTMLDetailsElement {\n const d = doc.createElement(\"details\");\n d.dataset[\"section\"] = name;\n d.open = open;\n const s = doc.createElement(\"summary\");\n s.textContent = label;\n if (count !== null) s.append(el(doc, \"span\", \"pp-bundle-count\", ` ${count}`));\n d.append(s);\n return d;\n}\n\n/**\n * Build a read-only panel describing a compiled bundle: what it is, what game\n * code may call on it, and what the host must supply.\n */\nexport function createBundleInspector(bundle: Bundle, opts: BundleInspectorOptions = {}): BundleInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n const d = describeBundle(bundle);\n const open = new Set<BundleSection>(opts.open ?? [\"identity\", \"addresses\"]);\n\n const root = el(doc, \"div\", \"pp-bundle\");\n root.append(el(doc, \"h4\", undefined, opts.title ?? \"Bundle\"));\n\n // --- identity ------------------------------------------------------------\n const id = section(doc, \"identity\", d.identity.project || \"(unnamed project)\", null, open.has(\"identity\"));\n if (d.identity.version) id.append(row(doc, \"version\", d.identity.version));\n id.append(row(doc, \"schema\", d.identity.schema));\n id.append(row(doc, \"locales\", `${d.identity.defaultLocale}${d.identity.locales.length > 1 ? ` (+${d.identity.locales.length - 1})` : \"\"}`));\n id.append(row(doc, \"strings\", d.identity.localisation));\n if (d.identity.voiced) id.append(row(doc, \"voiced\", \"yes\"));\n if (d.identity.hash) id.append(row(doc, \"hash\", d.identity.hash));\n // Equal structureHash + a different hash = a text-only edit, which is what makes\n // a live hot-swap safe. Both are shown so an integrator can tell those apart.\n if (d.identity.structureHash) id.append(row(doc, \"structure\", d.identity.structureHash));\n if (d.identity.sourceDebug) {\n id.append(row(doc, \"build\", \"SOURCE DEBUG - not shippable\", \"pp-bundle-val pp-bundle-warn\"));\n }\n root.append(id);\n\n // --- addresses -----------------------------------------------------------\n const addr = section(doc, \"addresses\", \"Addresses\", d.addresses.length, open.has(\"addresses\"));\n if (!d.addresses.length) addr.append(el(doc, \"div\", \"pp-bundle-empty\", \"no scenes\"));\n for (const a of d.addresses) {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", a.gameId), el(doc, \"span\", \"pp-bundle-val\", a.name));\n addr.append(r);\n // Nested, because a block address is scene-scoped: the pair is the address.\n for (const b of a.blocks) {\n const br = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n br.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", b.gameId), el(doc, \"span\", \"pp-bundle-val\", b.name));\n addr.append(br);\n }\n }\n root.append(addr);\n\n // --- host scopes ---------------------------------------------------------\n // Counted in PROPERTIES rather than scopes: the heading says \"properties\", and a\n // reader comparing it with the rows below should not have to work out which it meant.\n // An opaque scope contributes none, and says so on its own row.\n const hostCount = d.hostScopes.reduce((n, s) => n + s.properties.length, 0);\n const host = section(doc, \"hostScopes\", \"Host properties\", hostCount, open.has(\"hostScopes\"));\n if (!d.hostScopes.length) host.append(el(doc, \"div\", \"pp-bundle-empty\", \"the game supplies nothing\"));\n for (const s of d.hostScopes) {\n const head = el(doc, \"div\", \"pp-bundle-row\");\n head.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", `@${s.token}`));\n const meta = el(doc, \"span\", \"pp-bundle-val\", s.opaque ? \"any name, unchecked\" : `${s.properties.length} declared`);\n if (!s.writable) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"read-only\"));\n head.append(meta);\n host.append(head);\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n host.append(pr);\n }\n }\n root.append(host);\n\n // --- owned properties ----------------------------------------------------\n const ownedCount = d.properties.patter.length + d.properties.scene.reduce((n, s) => n + s.properties.length, 0);\n const owned = section(doc, \"properties\", \"Story properties\", ownedCount, open.has(\"properties\"));\n if (!ownedCount) owned.append(el(doc, \"div\", \"pp-bundle-empty\", \"none declared\"));\n for (const p of d.properties.patter) owned.append(propertyRow(doc, p));\n for (const s of d.properties.scene) {\n owned.append(row(doc, `@scene`, s.gameId, \"pp-bundle-val pp-bundle-addr\"));\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n owned.append(pr);\n }\n }\n root.append(owned);\n\n // --- gameData ------------------------------------------------------------\n if (d.gameData.length) {\n const gd = section(doc, \"gameData\", \"Game data\", d.gameData.reduce((n, g) => n + g.fields.length, 0), open.has(\"gameData\"));\n for (const g of d.gameData) {\n gd.append(row(doc, \"on\", g.kind));\n for (const f of g.fields) {\n const fr = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n fr.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", f.name), el(doc, \"span\", \"pp-bundle-val\", f.type));\n gd.append(fr);\n }\n }\n root.append(gd);\n }\n\n // --- counts --------------------------------------------------------------\n const counts = section(doc, \"counts\", \"Counts\", null, open.has(\"counts\"));\n for (const [key, value] of Object.entries(d.counts)) counts.append(row(doc, key, String(value)));\n root.append(counts);\n\n opts.container?.append(root);\n return {\n el: root,\n description: d,\n destroy() { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// The thin runner: the flow API turned from pull to push, for straightforward\n// games. Start a conversation, and the runner hands each beat to the game as an\n// event; the game shows it however it likes and calls next() when it's ready.\n// A choice waits for choose(id). Nothing else: no timing, no auto-advance, no\n// UI, no audio. A line carries its beat id, so a game plays its recording with\n// the audio resolver, or its own. The same runner in every Patterplay runtime.\n//\n// const runner = new Runner(engine);\n// runner.on(\"line\", (step) => { show(step.speakerLabel, step.text); });\n// runner.on(\"choice\", (step) => offer(step.options)); // then runner.choose(id)\n// runner.on(\"ended\", (reason) => close()); // \"ended\", or \"ran-dry\": a choice had nothing left\n// runner.begin(); // the project's start scene; next() on each click\n//\n// A fuller dialogue manager for more complex games, Patterstage, is coming.\n// ---------------------------------------------------------------------------\n\nimport type { ChoiceOption, FlowEndReason, Engine, Flow, StepResult } from \"@patterkit/runtime\";\n\ntype Step<T extends StepResult[\"type\"]> = Extract<StepResult, { type: T }>;\n\n/** What each runner event hands its handler. */\nexport interface RunnerEvents {\n line: Step<\"line\">;\n text: Step<\"text\">;\n gameEvent: Step<\"gameEvent\">;\n choice: { groupId: string; options: ChoiceOption[] };\n /** Why the flow ended, as its `end` step says: `\"ran-dry\"` when a choice had nothing left to offer and\n * nothing came after it, otherwise `\"ended\"`. */\n ended: FlowEndReason;\n}\n\nexport type RunnerEvent = keyof RunnerEvents;\n\n/**\n * Plays one flow, a beat at a time, as events. Every delivered beat (a line, a text or a game event)\n * waits for {@link next}; a choice waits for {@link choose}. Handlers may call `next()` or `choose()`\n * themselves: the runner finishes the event it is delivering first, so a long run never deepens the\n * stack.\n *\n * The runner keeps no state a save needs: `engine.saveGame()` and `loadGame()` stay the one way to\n * save. After a load, call `next()` to carry on (re-show the last line with `flow.stepFor(id)` if you\n * like). After `engine.hotSwap()`, hand the new engine to {@link rebind}.\n */\nexport class Runner {\n private readonly handlers: { [E in RunnerEvent]: Set<(payload: RunnerEvents[E]) => void> } = {\n line: new Set(), text: new Set(), gameEvent: new Set(), choice: new Set(), ended: new Set(),\n };\n /** next() calls asked for while an event was being delivered, played once it returns. */\n private queued = 0;\n private delivering = false;\n\n constructor(private engine: Engine, readonly flowId: string = \"main\") {}\n\n /** The flow the runner plays, looked up by id each time: a load or a hot swap replaces it. */\n get flow(): Flow | undefined {\n return this.engine.getFlow(this.flowId);\n }\n\n /** Listen for an event; returns a function that stops listening. */\n on<E extends RunnerEvent>(event: E, handler: (payload: RunnerEvents[E]) => void): () => void {\n const set = this.handlers[event] as Set<(payload: RunnerEvents[E]) => void>;\n set.add(handler);\n return () => { set.delete(handler); };\n }\n\n /**\n * Start a conversation at a scene (and block), and deliver its first beat. The first begin opens the\n * flow. A later one moves the same flow there, as an authored jump does, so visit counts, variations\n * and the flow's own properties carry on through the playthrough.\n *\n * With no scene, the story starts from the beginning, at the project's start scene: the first time\n * opens the flow there, and a later time resets it (`flow.reset()`), forgetting the flow's own state,\n * as a new game does. Throws on an address that doesn't resolve, before anything changes.\n */\n begin(scene?: string, block?: string): void {\n const flow = this.flow;\n if (!flow || flow.isClosed) {\n this.engine.openFlow(this.flowId, { scene, block });\n } else if (scene === undefined) {\n if (block !== undefined) throw new Error(\"Runner.begin: a block needs its scene\");\n flow.reset();\n } else if (!flow.goto(scene, block)) {\n throw new Error(`Runner.begin: address not found: ${scene}${block === undefined ? \"\" : ` / ${block}`}`);\n }\n this.queued = 0;\n this.next();\n }\n\n /** Deliver the next beat, the pending choice, or the end. */\n next(): void {\n if (!this.flow) throw new Error(`Runner.next: flow '${this.flowId}' isn't open; call begin() first`);\n this.queued++;\n if (this.delivering) return;\n this.delivering = true;\n try {\n while (this.queued > 0) {\n this.queued--;\n this.deliver(this.flow.advance());\n }\n } finally {\n this.delivering = false;\n this.queued = 0;\n }\n }\n\n /** Pick an option of the pending choice, then deliver what it leads to. Throws as `flow.choose` does\n * (no choice pending, an unknown option, or one that isn't eligible). */\n choose(id: string): void {\n const flow = this.flow;\n if (!flow) throw new Error(`Runner.choose: flow '${this.flowId}' isn't open; call begin() first`);\n flow.choose(id);\n this.next();\n }\n\n /**\n * Stop delivering: any next() a handler asked for is dropped, and nothing more is delivered until the\n * game asks. The flow stays where it is, open and saveable, and `ended` is not raised; `next()` carries\n * on from there and `begin()` begins elsewhere.\n */\n stop(): void {\n this.queued = 0;\n }\n\n /** Play on a replacement engine (what `engine.hotSwap()` or `applyLiveBundle` returns). */\n rebind(engine: Engine): void {\n this.engine = engine;\n }\n\n private deliver(step: StepResult): void {\n switch (step.type) {\n case \"line\": return this.emit(\"line\", step);\n case \"text\": return this.emit(\"text\", step);\n case \"gameEvent\": return this.emit(\"gameEvent\", step);\n case \"choice\": return this.emit(\"choice\", { groupId: step.groupId, options: step.options });\n case \"end\": return this.emit(\"ended\", step.reason);\n }\n }\n\n private emit<E extends RunnerEvent>(event: E, payload: RunnerEvents[E]): void {\n for (const handler of [...this.handlers[event]]) (handler as (p: RunnerEvents[E]) => void)(payload);\n }\n}\n","// ---------------------------------------------------------------------------\n// Audio resolver (#206): map a beat id to the path/URL of its winning audio\n// take, using the `patteraudio.json` manifest Patterpad (or the CLI) emits next\n// to the Audio Folders. It RESOLVES ONLY - playback stays yours (Web Audio, an\n// <audio> element, etc.), which can't be portable across platforms anyway.\n//\n// The manifest already encodes the highest-rung \"winner\" per beat, so there's no\n// folder search at runtime. Deploy your audio folder somewhere your game can read\n// (e.g. under /public), fetch the manifest once, and:\n//\n// const audio = createAudioResolver(manifestJson, \"audio\"); // base = where the folder is served\n// const src = audio.resolve(step.id); // \"audio/final/L1.wav\", or null when the beat has no recording\n// if (src) new Audio(src).play();\n// ---------------------------------------------------------------------------\n\nexport interface AudioResolver {\n /** The full path/URL of a beat's winning audio take, or null when it has none. */\n resolve(beatId: string): string | null;\n}\n\ninterface AudioManifest {\n schema?: string;\n clips?: Record<string, { file: string; status?: string }>;\n}\n\n/**\n * Build an audio resolver from a `patteraudio.json` manifest string plus the base path/URL its files live\n * under (wherever you deployed the audio folder). `resolve(beatId)` joins the base with the winning clip's\n * relative path, or returns null when the beat has no recording. Never throws on a missing beat.\n */\nexport function createAudioResolver(manifestJson: string, basePath: string): AudioResolver {\n const manifest = JSON.parse(manifestJson) as AudioManifest;\n const clips = manifest.clips ?? {};\n // A base that already ends in a separator is joined as it stands: trimming it turned a root such as \"/\"\n // or \"res://\" into \"\" or \"res:\", and the path lost its root.\n const sep = basePath === \"\" || /[/\\\\]$/.test(basePath) ? \"\" : \"/\";\n return {\n resolve(beatId: string): string | null {\n const clip = clips[beatId];\n if (!clip || !clip.file) return null;\n return `${basePath}${sep}${clip.file}`;\n },\n };\n}\n"],"mappings":"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","names":["createKernelStateLogger","el","row","import_runtime","STYLE_ID","CSS","injectStyle","gd"]}
package/dist/index.d.cts CHANGED
@@ -66,6 +66,25 @@ interface DebugSocketLike {
66
66
  }) => void): void;
67
67
  }
68
68
  type DebugSocketCtor = new (url: string) => DebugSocketLike;
69
+ /** The editor asked the running game to play a block now (Play in game). Ids are the bundle's. */
70
+ interface PlayRequest {
71
+ /** Echo it in `replyPlay` so the editor can match the answer to its request. */
72
+ id: string;
73
+ sceneId: string;
74
+ blockId: string;
75
+ /** Stop what the speakers are doing first, rather than turn the request down as busy. */
76
+ takeOver: boolean;
77
+ /** The editor's bundle hash, when it sent one: a game on another build can answer "staleBuild". */
78
+ build?: string;
79
+ }
80
+ /** The game's answer to a `PlayRequest`. */
81
+ interface PlayResult {
82
+ started: boolean;
83
+ /** Why it didn't start, as a short code: "busy", "unknownBlock", "staleBuild", or the game's own. */
84
+ reason?: string;
85
+ /** More, in plain words (which speaker is busy, which block was missing). */
86
+ detail?: string;
87
+ }
69
88
  interface DebugLinkOptions {
70
89
  /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */
71
90
  build: string;
@@ -82,6 +101,11 @@ interface DebugLinkOptions {
82
101
  build: string;
83
102
  data: string;
84
103
  }) => void;
104
+ /** Play in game: the writer asked the game to start a block now. Answer with `link.replyPlay(req.id,
105
+ * result)`, in the handler or later (an answer after the editor stopped waiting is ignored). Without a
106
+ * handler the link answers `{ started: false, reason: "unhandled" }`. Never called with malformed
107
+ * payloads. */
108
+ onPlay?: (req: PlayRequest) => void;
85
109
  }
86
110
  interface DebugLink {
87
111
  /** Tell the editor a flow opened (so it can list it in the follow selector). */
@@ -93,6 +117,8 @@ interface DebugLink {
93
117
  /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's
94
118
  * match/stale pill updates and it stops re-pushing the same bundle). */
95
119
  setBuild(build: string): void;
120
+ /** Answer a `play` request (see `onPlay`). Safe to call at any time; a no-op once the link is closed. */
121
+ replyPlay(id: string, result: PlayResult): void;
96
122
  /** Close the link. */
97
123
  close(): void;
98
124
  /** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game "the editor
@@ -244,4 +270,4 @@ interface AudioResolver {
244
270
  */
245
271
  declare function createAudioResolver(manifestJson: string, basePath: string): AudioResolver;
246
272
 
247
- export { type AudioResolver, type BundleInspector, type BundleInspectorOptions, type BundleSection, type DebugLink, type DebugLinkOptions, type DebugSocketLike, type LiveBundleResult, type PropertyInspector, type PropertyInspectorOptions, type PropertyValue, Runner, type RunnerEvent, type RunnerEvents, type StateLogger, type StateLoggerOptions, type StateValue, applyLiveBundle, createAudioResolver, createBundleInspector, createDebugLink, createPropertyInspector, createStateLogger, deserializeState, getProperty, loadState, saveState, serializeState, setProperties, setProperty, snapshotState };
273
+ export { type AudioResolver, type BundleInspector, type BundleInspectorOptions, type BundleSection, type DebugLink, type DebugLinkOptions, type DebugSocketLike, type LiveBundleResult, type PlayRequest, type PlayResult, type PropertyInspector, type PropertyInspectorOptions, type PropertyValue, Runner, type RunnerEvent, type RunnerEvents, type StateLogger, type StateLoggerOptions, type StateValue, applyLiveBundle, createAudioResolver, createBundleInspector, createDebugLink, createPropertyInspector, createStateLogger, deserializeState, getProperty, loadState, saveState, serializeState, setProperties, setProperty, snapshotState };
package/dist/index.d.ts CHANGED
@@ -66,6 +66,25 @@ interface DebugSocketLike {
66
66
  }) => void): void;
67
67
  }
68
68
  type DebugSocketCtor = new (url: string) => DebugSocketLike;
69
+ /** The editor asked the running game to play a block now (Play in game). Ids are the bundle's. */
70
+ interface PlayRequest {
71
+ /** Echo it in `replyPlay` so the editor can match the answer to its request. */
72
+ id: string;
73
+ sceneId: string;
74
+ blockId: string;
75
+ /** Stop what the speakers are doing first, rather than turn the request down as busy. */
76
+ takeOver: boolean;
77
+ /** The editor's bundle hash, when it sent one: a game on another build can answer "staleBuild". */
78
+ build?: string;
79
+ }
80
+ /** The game's answer to a `PlayRequest`. */
81
+ interface PlayResult {
82
+ started: boolean;
83
+ /** Why it didn't start, as a short code: "busy", "unknownBlock", "staleBuild", or the game's own. */
84
+ reason?: string;
85
+ /** More, in plain words (which speaker is busy, which block was missing). */
86
+ detail?: string;
87
+ }
69
88
  interface DebugLinkOptions {
70
89
  /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */
71
90
  build: string;
@@ -82,6 +101,11 @@ interface DebugLinkOptions {
82
101
  build: string;
83
102
  data: string;
84
103
  }) => void;
104
+ /** Play in game: the writer asked the game to start a block now. Answer with `link.replyPlay(req.id,
105
+ * result)`, in the handler or later (an answer after the editor stopped waiting is ignored). Without a
106
+ * handler the link answers `{ started: false, reason: "unhandled" }`. Never called with malformed
107
+ * payloads. */
108
+ onPlay?: (req: PlayRequest) => void;
85
109
  }
86
110
  interface DebugLink {
87
111
  /** Tell the editor a flow opened (so it can list it in the follow selector). */
@@ -93,6 +117,8 @@ interface DebugLink {
93
117
  /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's
94
118
  * match/stale pill updates and it stops re-pushing the same bundle). */
95
119
  setBuild(build: string): void;
120
+ /** Answer a `play` request (see `onPlay`). Safe to call at any time; a no-op once the link is closed. */
121
+ replyPlay(id: string, result: PlayResult): void;
96
122
  /** Close the link. */
97
123
  close(): void;
98
124
  /** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game "the editor
@@ -244,4 +270,4 @@ interface AudioResolver {
244
270
  */
245
271
  declare function createAudioResolver(manifestJson: string, basePath: string): AudioResolver;
246
272
 
247
- export { type AudioResolver, type BundleInspector, type BundleInspectorOptions, type BundleSection, type DebugLink, type DebugLinkOptions, type DebugSocketLike, type LiveBundleResult, type PropertyInspector, type PropertyInspectorOptions, type PropertyValue, Runner, type RunnerEvent, type RunnerEvents, type StateLogger, type StateLoggerOptions, type StateValue, applyLiveBundle, createAudioResolver, createBundleInspector, createDebugLink, createPropertyInspector, createStateLogger, deserializeState, getProperty, loadState, saveState, serializeState, setProperties, setProperty, snapshotState };
273
+ export { type AudioResolver, type BundleInspector, type BundleInspectorOptions, type BundleSection, type DebugLink, type DebugLinkOptions, type DebugSocketLike, type LiveBundleResult, type PlayRequest, type PlayResult, type PropertyInspector, type PropertyInspectorOptions, type PropertyValue, Runner, type RunnerEvent, type RunnerEvents, type StateLogger, type StateLoggerOptions, type StateValue, applyLiveBundle, createAudioResolver, createBundleInspector, createDebugLink, createPropertyInspector, createStateLogger, deserializeState, getProperty, loadState, saveState, serializeState, setProperties, setProperty, snapshotState };