@patterkit/play-helpers 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -127,6 +127,9 @@ function createStateLogger(engine, opts = {}) {
127
127
  // src/debug.ts
128
128
  var PLAY_UNHANDLED = "unhandled";
129
129
  var OPEN = 1;
130
+ var REPLACED = 4e3;
131
+ var RETRY_FIRST = 1e3;
132
+ var RETRY_MAX = 1e4;
130
133
  function createDebugLink(opts) {
131
134
  const url = opts.url ?? "ws://127.0.0.1:4471";
132
135
  const Ctor = opts.WebSocket ?? globalThis.WebSocket;
@@ -135,6 +138,11 @@ function createDebugLink(opts) {
135
138
  let sock = null;
136
139
  let closed = false;
137
140
  let build = opts.build;
141
+ let dropped = false;
142
+ let wasOpen = false;
143
+ let lost = false;
144
+ let wait = RETRY_FIRST;
145
+ let retry = null;
138
146
  const flush = () => {
139
147
  if (!sock || sock.readyState !== OPEN) return;
140
148
  for (const m of queue) {
@@ -147,6 +155,7 @@ function createDebugLink(opts) {
147
155
  };
148
156
  const post = (msg) => {
149
157
  if (closed || !sock || sock.readyState > OPEN) return;
158
+ if (dropped && sock.readyState !== OPEN) return;
150
159
  queue.push(JSON.stringify(msg));
151
160
  flush();
152
161
  };
@@ -173,16 +182,32 @@ function createDebugLink(opts) {
173
182
  const hello = JSON.stringify({ t: "hello", v: 1, build, project: opts.project, flows: [...flows] });
174
183
  try {
175
184
  sock?.send(hello);
185
+ wait = RETRY_FIRST;
176
186
  } catch {
177
187
  }
178
188
  };
179
- try {
180
- sock = new Ctor(url);
181
- sock.addEventListener("open", () => {
189
+ const connect = () => {
190
+ retry = null;
191
+ if (closed) return;
192
+ let s;
193
+ try {
194
+ s = new Ctor(url);
195
+ } catch {
196
+ sock = null;
197
+ return;
198
+ }
199
+ sock = s;
200
+ s.addEventListener("open", () => {
201
+ if (sock !== s) return;
202
+ wasOpen = true;
203
+ if (lost) {
204
+ lost = false;
205
+ console.info(`[patterplay] Live Link is back (${url}).`);
206
+ }
182
207
  sendHello();
183
208
  flush();
184
209
  });
185
- sock.addEventListener("message", (ev) => {
210
+ s.addEventListener("message", (ev) => {
186
211
  if (typeof ev.data !== "string") return;
187
212
  let msg;
188
213
  try {
@@ -208,15 +233,28 @@ function createDebugLink(opts) {
208
233
  }
209
234
  }
210
235
  });
211
- sock.addEventListener("error", () => {
236
+ s.addEventListener("error", () => {
212
237
  });
213
- sock.addEventListener("close", () => {
238
+ s.addEventListener("close", (ev) => {
239
+ if (sock !== s) return;
214
240
  sock = null;
215
241
  queue = [];
242
+ dropped = true;
243
+ if (closed) return;
244
+ if (ev?.code === REPLACED) {
245
+ closed = true;
246
+ console.info(`[patterplay] Live Link: another game took the editor's link (${url}).`);
247
+ return;
248
+ }
249
+ if (wasOpen && !lost) {
250
+ lost = true;
251
+ console.info(`[patterplay] Live Link lost (${url}); retrying.`);
252
+ }
253
+ retry = setTimeout(connect, wait);
254
+ wait = Math.min(wait * 2, RETRY_MAX);
216
255
  });
217
- } catch {
218
- sock = null;
219
- }
256
+ };
257
+ connect();
220
258
  return {
221
259
  flowOpened(flowId) {
222
260
  flows.add(flowId);
@@ -242,14 +280,19 @@ function createDebugLink(opts) {
242
280
  close() {
243
281
  closed = true;
244
282
  queue = [];
283
+ if (retry) {
284
+ clearTimeout(retry);
285
+ retry = null;
286
+ }
245
287
  try {
246
288
  sock?.close();
247
289
  } catch {
248
290
  }
249
291
  sock = null;
250
292
  },
293
+ // Between attempts there's no socket, but the link is still trying: connecting.
251
294
  get state() {
252
- return closed || !sock ? "closed" : sock.readyState === OPEN ? "connected" : "connecting";
295
+ return closed ? "closed" : sock && sock.readyState === OPEN ? "connected" : sock || retry ? "connecting" : "closed";
253
296
  },
254
297
  get build() {
255
298
  return build;
@@ -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, 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":";;;;;;;;;;;;;;;;;;;;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;;;AC3CA,IAAM,iBAAiB;AA6CvB,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,YAAY;AAAA,IAAC,GAAG,QAAQ;AAAE,eAAS;AAAA,IAAM,GAAG,OAAO,UAAU,OAAO,IAAI;AAAA,EAClJ;AAEA,QAAM,YAAY,CAAC,IAAY,WAA6B;AAC1D,SAAK;AAAA,MACH,GAAG;AAAA,MAAU;AAAA,MAAI,SAAS,OAAO,YAAY;AAAA,MAC7C,GAAI,OAAO,OAAO,WAAW,WAAW,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,MACrE,GAAI,OAAO,OAAO,WAAW,WAAW,EAAE,QAAQ,OAAO,OAAO,IAAI,CAAC;AAAA,IACvE,CAAC;AAAA,EACH;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,OAAO,GAAG,SAAS,SAAU;AACjC,UAAI;AACJ,UAAI;AAAE,cAAM,KAAK,MAAM,GAAG,IAAI;AAAA,MAA8B,QAAQ;AAAE;AAAA,MAAQ;AAC9E,UAAI,QAAQ,QAAQ,OAAO,QAAQ,SAAU;AAC7C,UAAI,IAAI,MAAM,UAAU;AACtB,YAAI,KAAK,YAAY,OAAO,IAAI,UAAU,YAAY,OAAO,IAAI,SAAS,UAAU;AAClF,eAAK,SAAS,EAAE,OAAO,IAAI,OAAO,MAAM,IAAI,KAAK,CAAC;AAAA,QACpD;AAAA,MACF,WAAW,IAAI,MAAM,QAAQ;AAC3B,cAAM,MAAM,iBAAiB,GAAG;AAChC,YAAI,CAAC,IAAK;AACV,YAAI,CAAC,KAAK,QAAQ;AAAE,oBAAU,IAAI,IAAI,EAAE,SAAS,OAAO,QAAQ,eAAe,CAAC;AAAG;AAAA,QAAQ;AAC3F,YAAI;AAAE,eAAK,OAAO,GAAG;AAAA,QAAG,QAAQ;AAAA,QAA0D;AAAA,MAC5F;AAAA,IACF,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;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;AAGA,SAAS,iBAAiB,KAAkD;AAC1E,QAAM,EAAE,IAAI,SAAS,QAAQ,IAAI;AACjC,MAAI,OAAO,OAAO,YAAY,OAAO,MAAM,OAAO,YAAY,YAAY,YAAY,MACjF,OAAO,YAAY,YAAY,YAAY,GAAI,QAAO;AAC3D,SAAO;AAAA,IACL;AAAA,IAAI;AAAA,IAAS;AAAA,IAAS,UAAU,IAAI,aAAa;AAAA,IACjD,GAAI,OAAO,IAAI,UAAU,YAAY,IAAI,UAAU,KAAK,EAAE,OAAO,IAAI,MAAM,IAAI,CAAC;AAAA,EAClF;AACF;;;ACzMA,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.\n//\n// It reconnects (the rule Storylets' live link shares): until the game closes it, a\n// socket that closes or never opens is tried again after 1 s, doubling to 10 s, and\n// back to 1 s once a hello goes. Each new socket says hello again with the build and\n// the open flows. One line is logged when an open link is lost and one when it's\n// back; a link that never opened logs nothing. Frames are queued only while the\n// first socket connects: after any attempt fails, nothing queues until a socket is\n// open. The editor closes a game it replaced (another game connected) with code\n// 4000, and that one doesn't retry, so two games don't take turns. Wire it up after\n// 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\" | \"error\", listener: () => void): void;\n /** The close event's `code` says why: 4000 is the editor replacing this game with another. */\n addEventListener(type: \"close\", listener: (ev?: { code?: number }) => 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/** The editor closed this game because another connected: don't retry. */\nconst REPLACED = 4000;\n/** The retry's first wait and its longest, in ms. */\nconst RETRY_FIRST = 1000;\nconst RETRY_MAX = 10000;\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 let dropped = false; // an attempt has failed or closed: queue nothing until a socket opens\n let wasOpen = false; // a socket has been open, so losing it is worth a line\n let lost = false; // logged as lost, waiting to log it back\n let wait = RETRY_FIRST;\n let retry: ReturnType<typeof setTimeout> | null = null;\n\n 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 if (dropped && 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); wait = RETRY_FIRST; } catch { /* race: closed immediately */ }\n };\n\n const connect = (): void => {\n retry = null;\n if (closed) return;\n let s: DebugSocketLike;\n try { s = new Ctor(url); } catch { sock = null; return; } // malformed URL etc. - never throw into the game\n sock = s;\n s.addEventListener(\"open\", () => {\n if (sock !== s) return;\n wasOpen = true;\n if (lost) { lost = false; console.info(`[patterplay] Live Link is back (${url}).`); }\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 s.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 s.addEventListener(\"error\", () => { /* editor not listening - the close that follows retries */ });\n s.addEventListener(\"close\", (ev?: { code?: number }) => {\n if (sock !== s) return;\n sock = null; queue = []; dropped = true;\n if (closed) return;\n if (ev?.code === REPLACED) { closed = true; console.info(`[patterplay] Live Link: another game took the editor's link (${url}).`); return; }\n if (wasOpen && !lost) { lost = true; console.info(`[patterplay] Live Link lost (${url}); retrying.`); }\n retry = setTimeout(connect, wait);\n wait = Math.min(wait * 2, RETRY_MAX);\n });\n };\n connect();\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 {\n closed = true; queue = [];\n if (retry) { clearTimeout(retry); retry = null; }\n try { sock?.close(); } catch { /* already gone */ }\n sock = null;\n },\n // Between attempts there's no socket, but the link is still trying: connecting.\n get state() { return closed ? \"closed\" : sock && sock.readyState === OPEN ? \"connected\" : sock || retry ? \"connecting\" : \"closed\"; },\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
@@ -59,7 +59,11 @@ interface DebugSocketLike {
59
59
  readyState: number;
60
60
  send(data: string): void;
61
61
  close(): void;
62
- addEventListener(type: "open" | "close" | "error", listener: () => void): void;
62
+ addEventListener(type: "open" | "error", listener: () => void): void;
63
+ /** The close event's `code` says why: 4000 is the editor replacing this game with another. */
64
+ addEventListener(type: "close", listener: (ev?: {
65
+ code?: number;
66
+ }) => void): void;
63
67
  /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */
64
68
  addEventListener(type: "message", listener: (ev: {
65
69
  data: unknown;
package/dist/index.d.ts CHANGED
@@ -59,7 +59,11 @@ interface DebugSocketLike {
59
59
  readyState: number;
60
60
  send(data: string): void;
61
61
  close(): void;
62
- addEventListener(type: "open" | "close" | "error", listener: () => void): void;
62
+ addEventListener(type: "open" | "error", listener: () => void): void;
63
+ /** The close event's `code` says why: 4000 is the editor replacing this game with another. */
64
+ addEventListener(type: "close", listener: (ev?: {
65
+ code?: number;
66
+ }) => void): void;
63
67
  /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */
64
68
  addEventListener(type: "message", listener: (ev: {
65
69
  data: unknown;