@patterkit/play-helpers 0.7.10 → 0.8.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 +14 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +14 -8
- package/dist/index.js.map +1 -1
- package/dist/patterplay.min.js +4 -4
- package/dist/patterplay.min.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -98,11 +98,10 @@ function describeStep(step) {
|
|
|
98
98
|
}
|
|
99
99
|
var gd = (data) => data ? ` gameData=${JSON.stringify(data)}` : "";
|
|
100
100
|
function visitState(engine) {
|
|
101
|
-
const save = engine.saveGame();
|
|
102
101
|
const out = {};
|
|
103
|
-
for (const [id, n] of Object.entries(
|
|
104
|
-
for (const
|
|
105
|
-
for (const [id, n] of Object.entries(
|
|
102
|
+
for (const [id, n] of Object.entries(engine.getVisitCounts())) out[`visit:${id}`] = n;
|
|
103
|
+
for (const flow of engine.flows()) {
|
|
104
|
+
for (const [id, n] of Object.entries(flow.getVisitCounts())) out[`${flow.id}/visit:${id}`] = n;
|
|
106
105
|
}
|
|
107
106
|
return out;
|
|
108
107
|
}
|
|
@@ -156,7 +155,7 @@ function createDebugLink(opts) {
|
|
|
156
155
|
}, setBuild() {
|
|
157
156
|
}, close() {
|
|
158
157
|
closed = true;
|
|
159
|
-
} };
|
|
158
|
+
}, state: "closed", build, url };
|
|
160
159
|
}
|
|
161
160
|
const sendHello = () => {
|
|
162
161
|
const hello = JSON.stringify({ t: "hello", v: 1, build, project: opts.project, flows: [...flows] });
|
|
@@ -219,7 +218,14 @@ function createDebugLink(opts) {
|
|
|
219
218
|
} catch {
|
|
220
219
|
}
|
|
221
220
|
sock = null;
|
|
222
|
-
}
|
|
221
|
+
},
|
|
222
|
+
get state() {
|
|
223
|
+
return closed || !sock ? "closed" : sock.readyState === OPEN ? "connected" : "connecting";
|
|
224
|
+
},
|
|
225
|
+
get build() {
|
|
226
|
+
return build;
|
|
227
|
+
},
|
|
228
|
+
url
|
|
223
229
|
};
|
|
224
230
|
}
|
|
225
231
|
|
|
@@ -567,12 +573,12 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
567
573
|
function createAudioResolver(manifestJson, basePath) {
|
|
568
574
|
const manifest = JSON.parse(manifestJson);
|
|
569
575
|
const clips = manifest.clips ?? {};
|
|
570
|
-
const
|
|
576
|
+
const sep = basePath === "" || /[/\\]$/.test(basePath) ? "" : "/";
|
|
571
577
|
return {
|
|
572
578
|
resolve(beatId) {
|
|
573
579
|
const clip = clips[beatId];
|
|
574
580
|
if (!clip || !clip.file) return null;
|
|
575
|
-
return
|
|
581
|
+
return `${basePath}${sep}${clip.file}`;
|
|
576
582
|
}
|
|
577
583
|
};
|
|
578
584
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -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/audio.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @patterkit/play-helpers - the Patterplay JS companion.\n//\n// Thin game-integration helpers around @patterkit/runtime's Engine: save/load\n// serialisation, runtime property setters, a state logger, and the live-debug\n// link. None are required to play a bundle - they smooth the common host wiring\n// (localStorage saves, pushing game state into dialogue, debugging what changed,\n// streaming the cursor to Patterpad's live debugger).\n// ---------------------------------------------------------------------------\n\nexport { SAVE_SCHEMA, saveState, loadState, serializeState, deserializeState } from \"./save.js\";\nexport type { SaveEnvelope } from \"./save.js\";\n\nexport { getProperty, setProperty, setProperties } from \"./properties.js\";\nexport type { PropertyValue } from \"./properties.js\";\n\nexport { snapshotState, diffState, createStateLogger } from \"./logger.js\";\nexport type { StateSnapshot, StateChange, StateValue, StateLogger, StateLoggerOptions } from \"./logger.js\";\n\nexport { createDebugLink } from \"./debug.js\";\nexport type { DebugLink, DebugLinkOptions, DebugSocketLike } from \"./debug.js\";\n\nexport { applyLiveBundle } from \"./refresh.js\";\nexport type { LiveBundleResult } from \"./refresh.js\";\n\nexport { createPropertyInspector } from \"./inspector.js\";\nexport type { PropertyInspector, PropertyInspectorOptions } from \"./inspector.js\";\n\nexport { createBundleInspector } from \"./bundle-inspector.js\";\nexport type { BundleInspector, BundleInspectorOptions, BundleSection } from \"./bundle-inspector.js\";\n\nexport { 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 save = engine.saveGame();\n const out: StateSnapshot = {};\n for (const [id, n] of Object.entries(save.sharedVisits)) out[`visit:${id}`] = n;\n for (const [fid, snap] of Object.entries(save.flows)) {\n for (const [id, n] of Object.entries(snap.visits)) out[`${fid}/visit:${id}`] = n;\n }\n return out;\n}\n\n/** Create a state logger over an engine. Property writes log as they land; call `capture()`\n * after each `advance`/`choose` to pick up the visit counts and re-baseline. */\nexport function createStateLogger(engine: Engine, opts: StateLoggerOptions = {}): StateLogger {\n const tag = opts.label ? `[${opts.label}] ` : \"\";\n const kernel = createKernelStateLogger({\n // Re-read on every capture: openFlow and loadGame both replace bags, and the kernel\n // re-mounts whatever it is handed.\n mounts: (): LogMount[] => allMounts(engine),\n extra: () => visitState(engine),\n }, { sink: opts.sink, label: tag });\n\n return {\n snapshot: () => snapshotState(engine),\n capture: () => kernel.capture(),\n dispose: () => kernel.dispose(),\n logStep(step) { (opts.sink ?? ((l: string) => console.log(l)))(`${tag}${describeStep(step)}`); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live debug link (#181) - the game-side client.\n//\n// Streams a running game's story position to Patterpad over a localhost\n// WebSocket, so the editor can follow the live cursor like a debugger. It is\n// OBSERVE-ONLY: the game stays in control; the editor is a passive mirror.\n//\n// Wire protocol `patterplay/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:1, build, project?, flows? } - on connect\n// frame : { t:\"frame\", flow, sceneId, beatId, type, choiceId? } - per transition\n// flow : { t:\"flowOpen\" | \"flowClose\", flow } - lifecycle\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: live bundle refresh.\n// `data` is the full .patterc JSON. Pass an `onBundle` handler (or just feed it to\n// `applyLiveBundle`, which picks strings-only vs full hot swap itself), then call\n// `link.setBuild(newHash)` so the editor's match/stale pill updates.\n//\n// The link is loopback-only on the editor side (127.0.0.1), so there's no pairing\n// token - only processes on the same machine can reach it. The client never throws\n// into the game loop - a missing/closed editor just means frames go nowhere. Wire\n// it up after each advance()/choose():\n//\n// const link = createDebugLink({ build: bundle.content.hash });\n// link.flowOpened(\"main\");\n// // ...after each step:\n// link.observe(\"main\", flow.currentScene, step.id ?? null, step.type);\n// ---------------------------------------------------------------------------\n\n/** A minimal structural type for a WebSocket implementation (browsers + Node 21+ have a global one). */\nexport interface DebugSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"close\" | \"error\", listener: () => void): void;\n /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype DebugSocketCtor = new (url: string) => DebugSocketLike;\n\nexport interface DebugLinkOptions {\n /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */\n build: string;\n /** Optional project name, shown in the editor's debug panel. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4471`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (Node < 21, or tests with `ws`). */\n WebSocket?: DebugSocketCtor;\n /** Live bundle refresh: the editor pushed a freshly compiled bundle. `data` is the .patterc JSON;\n * hand it (with your current engine + bundle) to `applyLiveBundle`, re-bind your flow handles if it\n * hot-swapped, then call `link.setBuild(build)`. Never called with malformed payloads. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\nexport interface DebugLink {\n /** Tell the editor a flow opened (so it can list it in the follow selector). */\n flowOpened(flowId: string): void;\n /** Report the current position of a flow - call after each advance()/choose(). */\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void;\n /** Tell the editor a flow closed. */\n flowClosed(flowId: string): void;\n /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's\n * match/stale pill updates and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link. */\n close(): void;\n}\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/**\n * Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects\n * or if it was never listening - safe to leave wired into a shipping build behind a flag.\n */\nexport function createDebugLink(opts: DebugLinkOptions): DebugLink {\n const url = opts.url ?? \"ws://127.0.0.1:4471\";\n const Ctor: DebugSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: DebugSocketCtor }).WebSocket;\n const flows = new Set<string>();\n let queue: string[] = [];\n let sock: DebugSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live bundle refresh lands\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n // Queue only while there is a socket that is connecting or open. Once the editor is gone (or never\n // answered), the socket is dropped and a message has nowhere to go, so it is dropped too: queueing it\n // anyway grew the queue by one string per step for the rest of a game that shipped with the link wired\n // in, which is exactly the build the docs say this is safe in.\n const post = (msg: object): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n queue.push(JSON.stringify(msg));\n flush();\n };\n\n if (!Ctor) {\n // No WebSocket available (old Node, no impl passed) - degrade to a silent no-op link.\n return { flowOpened() {}, observe() {}, flowClosed() {}, setBuild() {}, close() { closed = true; } };\n }\n\n const sendHello = (): void => {\n const hello = JSON.stringify({ t: \"hello\", v: 1, build, project: opts.project, flows: [...flows] });\n try { sock?.send(hello); } catch { /* race: closed immediately */ }\n };\n\n try {\n sock = new Ctor(url);\n sock.addEventListener(\"open\", () => {\n // Handshake first, so the editor can verify the build + seed the flow list before frames arrive.\n sendHello();\n flush();\n });\n // Live bundle refresh: the editor pushed a new bundle. Validate the shape here so the host's\n // handler never sees a malformed payload; everything else the editor might send is ignored.\n sock.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n sock.addEventListener(\"error\", () => { /* editor not listening - stay a no-op */ });\n sock.addEventListener(\"close\", () => { sock = null; queue = []; });\n } catch { sock = null; } // malformed URL etc. - never throw into the game\n\n return {\n flowOpened(flowId: string): void { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); },\n flowClosed(flowId: string): void { flows.delete(flowId); post({ t: \"flowClose\", flow: flowId }); },\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void {\n // A flow reports itself. `flowOpened` is the host's job and nothing could check it, so a game\n // that opened a flow and forgot to announce it left the editor's list short - and the omission\n // survived a reconnect, because the hello sends this set. Observing a flow we have not seen\n // announces it first, which makes the host's call a courtesy (it lists the flow before its\n // first step) rather than something to remember.\n if (!flows.has(flowId)) { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); }\n post({ t: \"frame\", flow: flowId, sceneId, beatId, type, choiceId });\n },\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n if (sock && sock.readyState === OPEN) sendHello(); // re-handshake: the editor re-reads the build\n },\n close(): void { closed = true; queue = []; try { sock?.close(); } catch { /* already gone */ } sock = null; },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live bundle refresh (design/proposals/live-bundle-refresh.md) - the game-side\n// applier. The editor pushes a freshly compiled bundle over the debug link\n// (createDebugLink's `onBundle`); this helper picks the right tier and applies\n// it:\n// - same structureHash -> tier 1: engine.replaceStrings() - nothing restarts.\n// - changed structure -> tier 2: engine.hotSwap() - the run carried over,\n// content drift resolved by the save system (§9.8).\n// Returns the engine to keep using (the SAME one for \"text\", a REPLACEMENT for\n// \"structure\" - re-bind your Flow handles via engine.getFlow(id)) plus the\n// parsed bundle for the next comparison. Wire-up:\n//\n// let { engine, bundle } = boot();\n// const link = createDebugLink({\n// build: bundle.content.hash!,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, bundle, data);\n// engine = r.engine; bundle = r.bundle;\n// if (r.kind === \"structure\") flow = engine.getFlow(\"main\")!;\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport { Engine } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/runtime\";\n\nexport interface LiveBundleResult {\n /** The engine to keep using: the same instance for a \"text\" swap, a replacement for \"structure\". */\n engine: Engine;\n /** The parsed pushed bundle - hold on to it for the next apply's comparison. */\n bundle: Bundle;\n /** Which tier applied: \"text\" (strings-only, nothing restarted) or \"structure\" (full hot swap). */\n kind: \"text\" | \"structure\";\n}\n\n/**\n * Apply a bundle the editor pushed over the debug link. `current` is the bundle `engine` is running\n * (needed for the structure-hash comparison); `data` is the pushed .patterc JSON. Throws only on\n * unparseable JSON - a structural swap's edge cases are absorbed by the §9.8 drift policy inside\n * `hotSwap`, which never throws for ordinary edits.\n */\nexport function applyLiveBundle(engine: Engine, current: Bundle, data: string): LiveBundleResult {\n const next = JSON.parse(data) as Bundle;\n // Same structure = a text-only edit: swap the string tables in place and keep everything running.\n // Missing structureHash on either side (an older compiler) falls through to the full swap - safe,\n // just less gentle than it could be.\n const sameStructure = current.content.structureHash !== undefined\n && current.content.structureHash === next.content.structureHash;\n if (sameStructure) {\n engine.replaceStrings(next);\n return { engine, bundle: next, kind: \"text\" };\n }\n return { engine: engine.hotSwap(next), bundle: next, kind: \"structure\" };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// A live @patter property inspector for the browser: a small DOM panel that\n// watches AND edits a running Engine's shared @patter properties. Type-aware\n// editors (bool / number / text / enum / flags), a reset-to-default per row, and\n// a poll that refreshes values WITHOUT clobbering the field you're editing.\n//\n// The JS parity of the Unity PatterStateWindow / Godot PatterStatePanel. Those\n// use a PatterDebug registry because the editor window is separate from the game;\n// here the game runs in-process, so you just pass the engine directly:\n//\n// const inspector = createPropertyInspector(engine, { container: document.body });\n// // ...later: inspector.destroy();\n// ---------------------------------------------------------------------------\n\nimport type { Engine, PropertyRow } from \"@patterkit/runtime\";\nimport { serializeState, deserializeState } from \"./save.js\";\nimport type { PropertyValue } from \"./properties.js\";\n\nexport interface PropertyInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Runtime state\". */\n title?: string;\n /** Live-refresh interval in ms; 0 disables polling (call `refresh()` yourself). Default 250. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** Re-read every property and update the editors, skipping the field you're editing. */\n refresh(): void;\n /** Stop polling and remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-inspector-style\";\nconst CSS = `\n.pp-insp{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:22rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-insp h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-insp-empty{color:#8a9691;font-style:italic}\n.pp-insp-io { display: flex; gap: 6px; margin: 0 0 8px; }\n .pp-insp-io button { font: inherit; padding: 2px 8px; cursor: pointer; }\n .pp-insp-row{display:flex;align-items:center;gap:.4rem;margin:.18rem 0}\n.pp-insp-ref{flex:0 0 8rem;font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.pp-insp-ctl{flex:1;min-width:0;display:flex}\n.pp-insp-ctl input[type=text],.pp-insp-ctl input[type=number],.pp-insp-ctl select{width:100%;box-sizing:border-box;font:inherit;padding:.15rem .3rem;border:1px solid #cfc7b8;border-radius:6px;background:#fff;color:inherit}\n.pp-insp-reset{flex:0 0 auto;border:1px solid #cfc7b8;background:#fff;border-radius:6px;cursor:pointer;color:#5c6b62;width:1.6rem;height:1.6rem;line-height:1;padding:0}\n.pp-insp-reset:disabled{opacity:.35;cursor:default}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\n/** Has the DISPLAYED value changed? Order-sensitive on purpose, and deliberately\n * NOT the language's value equality, which compares flags as a set: this asks\n * whether the row needs redrawing, and the row renders the list in its stored\n * order, so a reordering is a change here even though it is not a change to\n * what the value means. */\nfunction sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => x === b[i]);\n return a === b;\n}\n\nexport function createPropertyInspector(engine: Engine, opts: PropertyInspectorOptions = {}): PropertyInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n\n const el = doc.createElement(\"div\");\n el.className = \"pp-insp\";\n const heading = doc.createElement(\"h4\");\n heading.textContent = opts.title ?? \"Runtime state\";\n const list = doc.createElement(\"div\");\n\n // Save/Load the whole run as a patter/save@0 envelope file - the parity of Unity's\n // PatterStateWindow buttons, Godot's state-panel dialogs, and Unreal's Slate panel.\n const io = doc.createElement(\"div\");\n io.className = \"pp-insp-io\";\n const saveBtn = doc.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.textContent = \"Save state\\u2026\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const a = doc.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"save.patterstate\";\n a.click();\n URL.revokeObjectURL(a.href);\n });\n const loadBtn = doc.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.textContent = \"Load state\\u2026\";\n loadBtn.addEventListener(\"click\", () => {\n const input = doc.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".patterstate,application/json\";\n input.addEventListener(\"change\", () => {\n const file = input.files?.[0];\n if (!file) return;\n void file.text().then((text) => {\n try { deserializeState(engine, text); refresh(); }\n catch (err) { console.error(\"Load state failed:\", err instanceof Error ? err.message : err); }\n });\n });\n input.click();\n });\n io.append(saveBtn, loadBtn);\n el.append(heading, io, list);\n\n const rowRefreshers: Array<() => void> = [];\n\n const buildRow = (row: PropertyRow): void => {\n const r = doc.createElement(\"div\");\n r.className = \"pp-insp-row\";\n const label = doc.createElement(\"span\");\n label.className = \"pp-insp-ref\";\n label.textContent = row.path;\n label.title = row.path;\n const ctl = doc.createElement(\"div\");\n ctl.className = \"pp-insp-ctl\";\n const reset = doc.createElement(\"button\");\n reset.className = \"pp-insp-reset\";\n reset.type = \"button\";\n reset.textContent = \"↺\"; // ↺\n reset.title = \"Reset to default\";\n r.append(label, ctl, reset);\n list.appendChild(r);\n\n // `read` pushes the live engine value into the editor, but never while that editor has focus\n // (so it can't wipe what you're typing). `syncReset` disables the ↺ when the value is the default.\n let read: () => void;\n const commit = (v: PropertyValue): void => { engine.setProperty(row.path, v); syncReset(); };\n const focused = (node: Element): boolean => doc.activeElement === node;\n function syncReset(): void { reset.disabled = sameValue(engine.getProperty(row.path), row.default); }\n reset.addEventListener(\"click\", () => { engine.setProperty(row.path, row.default); read(); syncReset(); });\n\n if (row.type === \"boolean\") {\n const cb = doc.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.addEventListener(\"change\", () => commit(cb.checked));\n ctl.appendChild(cb);\n read = () => { if (!focused(cb)) cb.checked = engine.getProperty(row.path) === true; };\n } else if (row.type === \"number\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"number\";\n inp.addEventListener(\"change\", () => commit(Number(inp.value)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"enum\" || row.type === \"quality\") {\n // A quality edits as a dropdown of its STAGES - the ladder is closed, like an enum's values.\n const sel = doc.createElement(\"select\");\n for (const v of (row.type === \"quality\" ? row.stages : row.values) ?? []) {\n const o = doc.createElement(\"option\");\n o.value = v; o.textContent = v;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", () => commit(sel.value));\n ctl.appendChild(sel);\n read = () => { if (!focused(sel)) sel.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"flags\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.placeholder = \"comma, separated, flags\";\n inp.addEventListener(\"change\", () => commit(inp.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) { const v = engine.getProperty(row.path); inp.value = Array.isArray(v) ? v.join(\", \") : \"\"; } };\n } else {\n // string (and any unrecognised type) -> a text field\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.addEventListener(\"change\", () => commit(inp.value));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n }\n\n read();\n syncReset();\n rowRefreshers.push(() => { read(); syncReset(); });\n };\n\n // Properties are fixed for a bundle, so build the rows once; polling just refreshes their values.\n const props = engine.listProperties();\n if (props.length === 0) {\n const empty = doc.createElement(\"div\");\n empty.className = \"pp-insp-empty\";\n empty.textContent = \"No @patter properties.\";\n list.appendChild(empty);\n } else {\n for (const row of props) buildRow(row);\n }\n\n const refresh = (): void => { for (const fn of rowRefreshers) fn(); };\n\n opts.container?.appendChild(el);\n const pollMs = opts.pollMs ?? 250;\n let timer: ReturnType<typeof setInterval> | undefined;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n return {\n el,\n refresh,\n destroy() { if (timer !== undefined) clearInterval(timer); el.remove(); },\n };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// The bundle inspector's VIEW, for the web: a read-only panel over\n// `describeBundle`, in the same visual grammar as the live property inspector.\n//\n// The other three runtimes hang their view off an imported asset - a Unity\n// CustomEditor, an Unreal details customisation, a Godot EditorInspectorPlugin.\n// JS has no asset pipeline, so there is no inspector to extend and the summary\n// function IS the surface. This exists because \"the function is the surface\"\n// leaves the JS side without the thing the other three give you for free: a\n// place to LOOK at a bundle. A drop-in page can now do\n//\n// const panel = createBundleInspector(bundle, { container: document.body });\n// // ...later: panel.destroy();\n//\n// Read-only on purpose. The live inspector edits because a running game has\n// state worth changing; this describes an artefact, and there is nothing here a\n// reader could sensibly write to.\n// ---------------------------------------------------------------------------\n\nimport { describeBundle } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/model\";\nimport type { BundleDescription, PropertySummary } from \"@patterkit/runtime\";\n\nexport interface BundleInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Bundle\". */\n title?: string;\n /** Sections open on mount. Default: identity and addresses, the two an\n * integrator reaches for first; the rest are a click away. */\n open?: BundleSection[];\n}\n\nexport type BundleSection = \"identity\" | \"addresses\" | \"hostScopes\" | \"properties\" | \"gameData\" | \"counts\";\n\nexport interface BundleInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** The description the panel is showing, for a caller that wants the data too. */\n readonly description: BundleDescription;\n /** Remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-bundle-style\";\n// Deliberately the property inspector's palette and metrics: the two panels are\n// often on the same page, and a second look-and-feel would read as a second tool.\nconst CSS = `\n.pp-bundle{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:26rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-bundle h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-bundle details{border-top:1px solid #e3dccf;padding:.3rem 0}\n.pp-bundle details:first-of-type{border-top:0}\n.pp-bundle summary{cursor:pointer;font-weight:600;color:#214f4b;list-style:none}\n.pp-bundle summary::-webkit-details-marker{display:none}\n.pp-bundle summary::before{content:\"\\\\25B8\";display:inline-block;width:1rem;color:#8a9691}\n.pp-bundle details[open]>summary::before{content:\"\\\\25BE\"}\n.pp-bundle-count{color:#8a9691;font-weight:400}\n.pp-bundle-row{display:flex;gap:.4rem;margin:.16rem 0 .16rem 1rem}\n.pp-bundle-key{flex:0 0 8rem;color:#5c6b62}\n.pp-bundle-val,.pp-bundle-addr{font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;word-break:break-all}\n.pp-bundle-sub{margin-left:2rem}\n.pp-bundle-empty{color:#8a9691;font-style:italic;margin-left:1rem}\n.pp-bundle-warn{color:#8a3a2f;font-weight:600}\n.pp-bundle-tag{font-size:.7rem;color:#5c6b62;border:1px solid #cfc7b8;border-radius:5px;padding:0 .25rem;margin-left:.3rem}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\nconst el = (doc: Document, tag: string, cls?: string, text?: string): HTMLElement => {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text !== undefined) e.textContent = text;\n return e;\n};\n\n/** One \"key: value\" line. */\nfunction row(doc: Document, key: string, value: string, valueClass = \"pp-bundle-val\"): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key\", key), el(doc, \"span\", valueClass, value));\n return r;\n}\n\n/** A property line: the name, its type, and whether the HOST must supply a value.\n * \"no default\" is the part an integrator is actually scanning for. */\nfunction propertyRow(doc: Document, p: PropertySummary): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", p.name));\n const meta = el(doc, \"span\", \"pp-bundle-val\", p.type);\n if (!p.hasDefault) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"no default\"));\n r.append(meta);\n return r;\n}\n\nfunction section(doc: Document, name: BundleSection, label: string, count: number | null, open: boolean): HTMLDetailsElement {\n const d = doc.createElement(\"details\");\n d.dataset[\"section\"] = name;\n d.open = open;\n const s = doc.createElement(\"summary\");\n s.textContent = label;\n if (count !== null) s.append(el(doc, \"span\", \"pp-bundle-count\", ` ${count}`));\n d.append(s);\n return d;\n}\n\n/**\n * Build a read-only panel describing a compiled bundle: what it is, what game\n * code may call on it, and what the host must supply.\n */\nexport function createBundleInspector(bundle: Bundle, opts: BundleInspectorOptions = {}): BundleInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n const d = describeBundle(bundle);\n const open = new Set<BundleSection>(opts.open ?? [\"identity\", \"addresses\"]);\n\n const root = el(doc, \"div\", \"pp-bundle\");\n root.append(el(doc, \"h4\", undefined, opts.title ?? \"Bundle\"));\n\n // --- identity ------------------------------------------------------------\n const id = section(doc, \"identity\", d.identity.project || \"(unnamed project)\", null, open.has(\"identity\"));\n if (d.identity.version) id.append(row(doc, \"version\", d.identity.version));\n id.append(row(doc, \"schema\", d.identity.schema));\n id.append(row(doc, \"locales\", `${d.identity.defaultLocale}${d.identity.locales.length > 1 ? ` (+${d.identity.locales.length - 1})` : \"\"}`));\n id.append(row(doc, \"strings\", d.identity.localisation));\n if (d.identity.voiced) id.append(row(doc, \"voiced\", \"yes\"));\n if (d.identity.hash) id.append(row(doc, \"hash\", d.identity.hash));\n // Equal structureHash + a different hash = a text-only edit, which is what makes\n // a live hot-swap safe. Both are shown so an integrator can tell those apart.\n if (d.identity.structureHash) id.append(row(doc, \"structure\", d.identity.structureHash));\n if (d.identity.sourceDebug) {\n id.append(row(doc, \"build\", \"SOURCE DEBUG - not shippable\", \"pp-bundle-val pp-bundle-warn\"));\n }\n root.append(id);\n\n // --- addresses -----------------------------------------------------------\n const addr = section(doc, \"addresses\", \"Addresses\", d.addresses.length, open.has(\"addresses\"));\n if (!d.addresses.length) addr.append(el(doc, \"div\", \"pp-bundle-empty\", \"no scenes\"));\n for (const a of d.addresses) {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", a.gameId), el(doc, \"span\", \"pp-bundle-val\", a.name));\n addr.append(r);\n // Nested, because a block address is scene-scoped: the pair is the address.\n for (const b of a.blocks) {\n const br = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n br.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", b.gameId), el(doc, \"span\", \"pp-bundle-val\", b.name));\n addr.append(br);\n }\n }\n root.append(addr);\n\n // --- host scopes ---------------------------------------------------------\n // Counted in PROPERTIES rather than scopes: the heading says \"properties\", and a\n // reader comparing it with the rows below should not have to work out which it meant.\n // An opaque scope contributes none, and says so on its own row.\n const hostCount = d.hostScopes.reduce((n, s) => n + s.properties.length, 0);\n const host = section(doc, \"hostScopes\", \"Host properties\", hostCount, open.has(\"hostScopes\"));\n if (!d.hostScopes.length) host.append(el(doc, \"div\", \"pp-bundle-empty\", \"the game supplies nothing\"));\n for (const s of d.hostScopes) {\n const head = el(doc, \"div\", \"pp-bundle-row\");\n head.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", `@${s.token}`));\n const meta = el(doc, \"span\", \"pp-bundle-val\", s.opaque ? \"any name, unchecked\" : `${s.properties.length} declared`);\n if (!s.writable) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"read-only\"));\n head.append(meta);\n host.append(head);\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n host.append(pr);\n }\n }\n root.append(host);\n\n // --- owned properties ----------------------------------------------------\n const ownedCount = d.properties.patter.length + d.properties.scene.reduce((n, s) => n + s.properties.length, 0);\n const owned = section(doc, \"properties\", \"Story properties\", ownedCount, open.has(\"properties\"));\n if (!ownedCount) owned.append(el(doc, \"div\", \"pp-bundle-empty\", \"none declared\"));\n for (const p of d.properties.patter) owned.append(propertyRow(doc, p));\n for (const s of d.properties.scene) {\n owned.append(row(doc, `@scene`, s.gameId, \"pp-bundle-val pp-bundle-addr\"));\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n owned.append(pr);\n }\n }\n root.append(owned);\n\n // --- gameData ------------------------------------------------------------\n if (d.gameData.length) {\n const gd = section(doc, \"gameData\", \"Game data\", d.gameData.reduce((n, g) => n + g.fields.length, 0), open.has(\"gameData\"));\n for (const g of d.gameData) {\n gd.append(row(doc, \"on\", g.kind));\n for (const f of g.fields) {\n const fr = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n fr.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", f.name), el(doc, \"span\", \"pp-bundle-val\", f.type));\n gd.append(fr);\n }\n }\n root.append(gd);\n }\n\n // --- counts --------------------------------------------------------------\n const counts = section(doc, \"counts\", \"Counts\", null, open.has(\"counts\"));\n for (const [key, value] of Object.entries(d.counts)) counts.append(row(doc, key, String(value)));\n root.append(counts);\n\n opts.container?.append(root);\n return {\n el: root,\n description: d,\n destroy() { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Audio resolver (#206): map a beat id to the path/URL of its winning audio\n// take, using the `patteraudio.json` manifest Patterpad (or the CLI) emits next\n// to the Audio Folders. It RESOLVES ONLY - playback stays yours (Web Audio, an\n// <audio> element, etc.), which can't be portable across platforms anyway.\n//\n// The manifest already encodes the highest-rung \"winner\" per beat, so there's no\n// folder search at runtime. Deploy your audio folder somewhere your game can read\n// (e.g. under /public), fetch the manifest once, and:\n//\n// const audio = createAudioResolver(manifestJson, \"audio\"); // base = where the folder is served\n// const src = audio.resolve(step.id); // \"audio/final/L1.wav\", or null when the beat has no recording\n// if (src) new Audio(src).play();\n// ---------------------------------------------------------------------------\n\nexport interface AudioResolver {\n /** The full path/URL of a beat's winning audio take, or null when it has none. */\n resolve(beatId: string): string | null;\n}\n\ninterface AudioManifest {\n schema?: string;\n clips?: Record<string, { file: string; status?: string }>;\n}\n\n/**\n * Build an audio resolver from a `patteraudio.json` manifest string plus the base path/URL its files live\n * under (wherever you deployed the audio folder). `resolve(beatId)` joins the base with the winning clip's\n * relative path, or returns null when the beat has no recording. Never throws on a missing beat.\n */\nexport function createAudioResolver(manifestJson: string, basePath: string): AudioResolver {\n const manifest = JSON.parse(manifestJson) as AudioManifest;\n const clips = manifest.clips ?? {};\n const base = basePath.replace(/[/\\\\]+$/, \"\"); // trim trailing slash(es); we add exactly one when joining\n return {\n resolve(beatId: string): string | null {\n const clip = clips[beatId];\n if (!clip || !clip.file) return null;\n return base ? `${base}/${clip.file}` : clip.file;\n },\n };\n}\n"],"mappings":"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","names":["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/audio.ts"],"sourcesContent":["// ---------------------------------------------------------------------------\n// @patterkit/play-helpers - the Patterplay JS companion.\n//\n// Thin game-integration helpers around @patterkit/runtime's Engine: save/load\n// serialisation, runtime property setters, a state logger, and the live-debug\n// link. None are required to play a bundle - they smooth the common host wiring\n// (localStorage saves, pushing game state into dialogue, debugging what changed,\n// streaming the cursor to Patterpad's live debugger).\n// ---------------------------------------------------------------------------\n\nexport { SAVE_SCHEMA, saveState, loadState, serializeState, deserializeState } from \"./save.js\";\nexport type { SaveEnvelope } from \"./save.js\";\n\nexport { getProperty, setProperty, setProperties } from \"./properties.js\";\nexport type { PropertyValue } from \"./properties.js\";\n\nexport { snapshotState, diffState, createStateLogger } from \"./logger.js\";\nexport type { StateSnapshot, StateChange, StateValue, StateLogger, StateLoggerOptions } from \"./logger.js\";\n\nexport { createDebugLink } from \"./debug.js\";\nexport type { DebugLink, DebugLinkOptions, DebugSocketLike } from \"./debug.js\";\n\nexport { applyLiveBundle } from \"./refresh.js\";\nexport type { LiveBundleResult } from \"./refresh.js\";\n\nexport { createPropertyInspector } from \"./inspector.js\";\nexport type { PropertyInspector, PropertyInspectorOptions } from \"./inspector.js\";\n\nexport { createBundleInspector } from \"./bundle-inspector.js\";\nexport type { BundleInspector, BundleInspectorOptions, BundleSection } from \"./bundle-inspector.js\";\n\nexport { createAudioResolver } from \"./audio.js\";\nexport type { AudioResolver } from \"./audio.js\";\n","// ---------------------------------------------------------------------------\n// Save / load: wrap the Engine's whole-game snapshot in a tagged, versioned\n// envelope so a host can drop it into localStorage / a file and restore it\n// safely (a foreign blob throws instead of corrupting a run).\n// ---------------------------------------------------------------------------\n\nimport type { Engine } from \"@patterkit/runtime\";\nimport { SAVE_SCHEMA } from \"@patterkit/model\";\nimport type { SaveEnvelope } from \"@patterkit/model\";\n\n// The envelope (`schema` + `save`) and the save shape inside it are the FAMILY's contract, defined in\n// @patterkit/model and written identically by every Patterplay runtime. Re-exported so existing\n// imports keep working.\nexport { SAVE_SCHEMA };\nexport type { SaveEnvelope };\n\n/** Capture the whole game as a tagged envelope (wraps `engine.saveGame()`). */\nexport function saveState(engine: Engine): SaveEnvelope {\n return { schema: SAVE_SCHEMA, save: engine.saveGame() };\n}\n\n/** Restore a {@link saveState} envelope into an engine (fresh or live). Throws on a foreign/blank blob. */\nexport function loadState(engine: Engine, env: SaveEnvelope): void {\n if (!env || env.schema !== SAVE_SCHEMA || !env.save) {\n throw new Error(`loadState: not a ${SAVE_SCHEMA} envelope`);\n }\n engine.loadGame(env.save);\n}\n\n/** Serialise the whole game to a JSON string (envelope + save-game) - drop into localStorage or a file. */\nexport function serializeState(engine: Engine): string {\n return JSON.stringify(saveState(engine));\n}\n\n/** Parse + restore a {@link serializeState} string. Throws on malformed JSON or a foreign envelope. */\nexport function deserializeState(engine: Engine, json: string): void {\n loadState(engine, JSON.parse(json) as SaveEnvelope);\n}\n","// ---------------------------------------------------------------------------\n// Runtime property setters: read / write `@patter` globals, `@scene` props, or\n// a wired foreign scope at runtime - e.g. the game pushing inventory into the\n// dialogue, or reading a flag the dialogue set. Thin pass-throughs, plus a batch\n// setter for convenience.\n//\n// They take an Engine or a Flow. Game-wide properties (`@hp`, `@world.x`) go\n// through either; `@scene` props belong to a flow, since each flow has its own,\n// so they need the Flow: the Engine refuses an `@scene` ref.\n// ---------------------------------------------------------------------------\n\nimport type { Engine, Flow } from \"@patterkit/runtime\";\n\n/** A property value the engine accepts (a Patter scalar: number / boolean / string / string[] flags). */\nexport type PropertyValue = Parameters<Engine[\"setProperty\"]>[1];\n\n/** Where properties are read and written: the Engine (game-wide refs), or a Flow (those plus `@scene`). */\nexport type PropertyHolder = Engine | Flow;\n\n/** Read a runtime property (`@hp`, a foreign `@world.x`, or with a Flow `@scene.locked`). Undefined when unset. */\nexport function getProperty(holder: PropertyHolder, ref: string): PropertyValue | undefined {\n return holder.getProperty(ref);\n}\n\n/** Set one runtime property. Mirrors `setProperty` on the Engine or Flow, exported for discoverability + symmetry. */\nexport function setProperty(holder: PropertyHolder, ref: string, value: PropertyValue): void {\n holder.setProperty(ref, value);\n}\n\n/** Set many at once, e.g. `setProperties(engine, { \"@hp\": 10 })` or `setProperties(flow, { \"@scene.locked\": false })`. */\nexport function setProperties(holder: PropertyHolder, values: Record<string, PropertyValue>): void {\n for (const [ref, value] of Object.entries(values)) holder.setProperty(ref, value);\n}\n","// ---------------------------------------------------------------------------\n// The Patterplay state logger: an ADAPTER over the kernel logger in\n// @wildwinter/scoperegistry, plus `logStep`, which is this product's own.\n//\n// It used to diff whole `engine.saveGame()` snapshots. That could only ever say\n// what changed BETWEEN captures, which means it could not see a value that\n// changed and changed back, could not name the reason a host attached to a\n// write, and reported a write only once something asked. The kernel logs a\n// property write as it LANDS, on the PropertyBag audit hook, and keeps the diff\n// for state that has no hook - here, the visit counts.\n//\n// The Storylet Engine's logger was already this shape; its core is what moved\n// into the kernel. Same core, two adapters.\n//\n// Paths, unchanged:\n// @patter.x the shared globals\n// @scene:<sceneId>.x the shared scene props\n// visit:<nodeId> shared visit counts\n// <flowId>/... the same three, per flow (its not-shared halves)\n// ---------------------------------------------------------------------------\n\nimport type { Engine, StepResult } from \"@patterkit/runtime\";\nimport { createStateLogger as createKernelStateLogger, diffState } from \"@wildwinter/scoperegistry\";\nimport type { LogMount, ScalarValue, StateChange, StateSnapshot } from \"@wildwinter/scoperegistry\";\n\n// Re-exported: these were declared here, and a host importing them from\n// @patterkit/play-helpers should not have to care that they moved.\nexport { diffState };\nexport type { StateChange, StateSnapshot };\n\n/** A flattened runtime-state value: a Patter scalar. The kernel calls this `ScalarValue`;\n * the alias stays because it is exported API and reads in Patter's own vocabulary. */\nexport type StateValue = ScalarValue;\n\n/** Flatten the engine's whole-game state into a path -> value map (shared scopes + every live\n * flow). The logger does not diff this - it mounts the bags directly - but it stays as the public\n * \"what is the state right now\" call, and it reads the same mounts, so the two agree on the path\n * space. A bag loaded into the registry but not yet claimed (a scene no flow has re-entered since\n * a load) appears once it is. */\nexport function snapshotState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const m of allMounts(engine)) {\n const prefix = m.pathPrefix ?? m.bag.pathPrefix;\n for (const [name, v] of Object.entries(m.bag.save())) out[`${prefix}${name}`] = v;\n }\n Object.assign(out, visitState(engine));\n return out;\n}\n\n/** Every bag the engine and its flows hold, with the path each answers to in a log. */\nfunction allMounts(engine: Engine): LogMount[] {\n return [...engine.listBags(), ...engine.flows().flatMap((f) => f.listBags())];\n}\n\nexport interface StateLoggerOptions {\n /** Where lines go; defaults to `console.log`. */\n sink?: (line: string) => void;\n /** Prefix tag for every line, e.g. the flow / save-slot name. */\n label?: string;\n}\n\nexport interface StateLogger {\n /** The current flattened state (no logging). */\n snapshot(): StateSnapshot;\n /** Everything since the last capture: the property writes already logged as they landed,\n * plus the visit counts, diffed and re-baselined. */\n capture(): StateChange[];\n /** Trace one played step (line / text / game-event / choice / end), including any `gameData`. */\n logStep(step: StepResult): void;\n /** Unhook the bag auditors. The logger is inert afterwards. */\n dispose(): void;\n}\n\nfunction describeStep(step: StepResult): string {\n switch (step.type) {\n case \"line\": return `line ${step.character ?? \"?\"}: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"text\": return `text: ${JSON.stringify(step.text)}${gd(step.gameData)}`;\n case \"gameEvent\": return `game event ${step.id}${gd(step.gameData)}`;\n case \"choice\": return `choice (${step.options.length} option${step.options.length === 1 ? \"\" : \"s\"})`;\n case \"end\": return \"end\";\n }\n}\nconst gd = (data: unknown): string => (data ? ` gameData=${JSON.stringify(data)}` : \"\");\n\n/** The visit counts, which live in no bag and so have no audit hook: the kernel diffs\n * these on capture, exactly as the whole logger used to work. */\nfunction visitState(engine: Engine): StateSnapshot {\n const out: StateSnapshot = {};\n for (const [id, n] of Object.entries(engine.getVisitCounts())) out[`visit:${id}`] = n;\n for (const flow of engine.flows()) {\n for (const [id, n] of Object.entries(flow.getVisitCounts())) out[`${flow.id}/visit:${id}`] = n;\n }\n return out;\n}\n\n/** Create a state logger over an engine. Property writes log as they land; call `capture()`\n * after each `advance`/`choose` to pick up the visit counts and re-baseline. */\nexport function createStateLogger(engine: Engine, opts: StateLoggerOptions = {}): StateLogger {\n const tag = opts.label ? `[${opts.label}] ` : \"\";\n const kernel = createKernelStateLogger({\n // Re-read on every capture: openFlow and loadGame both replace bags, and the kernel\n // re-mounts whatever it is handed.\n mounts: (): LogMount[] => allMounts(engine),\n extra: () => visitState(engine),\n }, { sink: opts.sink, label: tag });\n\n return {\n snapshot: () => snapshotState(engine),\n capture: () => kernel.capture(),\n dispose: () => kernel.dispose(),\n logStep(step) { (opts.sink ?? ((l: string) => console.log(l)))(`${tag}${describeStep(step)}`); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Live debug link (#181) - the game-side client.\n//\n// Streams a running game's story position to Patterpad over a localhost\n// WebSocket, so the editor can follow the live cursor like a debugger. It is\n// OBSERVE-ONLY: the game stays in control; the editor is a passive mirror.\n//\n// Wire protocol `patterplay/debug@1` (one JSON object per message):\n// hello : { t:\"hello\", v:1, build, project?, flows? } - on connect\n// frame : { t:\"frame\", flow, sceneId, beatId, type, choiceId? } - per transition\n// flow : { t:\"flowOpen\" | \"flowClose\", flow } - lifecycle\n// bundle: { t:\"bundle\", v:1, build, data } - EDITOR -> game: live bundle refresh.\n// `data` is the full .patterc JSON. Pass an `onBundle` handler (or just feed it to\n// `applyLiveBundle`, which picks strings-only vs full hot swap itself), then call\n// `link.setBuild(newHash)` so the editor's match/stale pill updates.\n//\n// The link is loopback-only on the editor side (127.0.0.1), so there's no pairing\n// token - only processes on the same machine can reach it. The client never throws\n// into the game loop - a missing/closed editor just means frames go nowhere. Wire\n// it up after each advance()/choose():\n//\n// const link = createDebugLink({ build: bundle.content.hash });\n// link.flowOpened(\"main\");\n// // ...after each step:\n// link.observe(\"main\", flow.currentScene, step.id ?? null, step.type);\n// ---------------------------------------------------------------------------\n\n/** A minimal structural type for a WebSocket implementation (browsers + Node 21+ have a global one). */\nexport interface DebugSocketLike {\n readyState: number;\n send(data: string): void;\n close(): void;\n addEventListener(type: \"open\" | \"close\" | \"error\", listener: () => void): void;\n /** Incoming editor messages (live bundle refresh). Optional so a bare-bones send-only socket still fits. */\n addEventListener(type: \"message\", listener: (ev: { data: unknown }) => void): void;\n}\ntype DebugSocketCtor = new (url: string) => DebugSocketLike;\n\nexport interface DebugLinkOptions {\n /** The running bundle's build identity - pass `bundle.content.hash`. Lets the editor detect a stale build. */\n build: string;\n /** Optional project name, shown in the editor's debug panel. */\n project?: string;\n /** Editor WebSocket URL. Default `ws://127.0.0.1:4471`. */\n url?: string;\n /** A WebSocket constructor to use instead of the global one (Node < 21, or tests with `ws`). */\n WebSocket?: DebugSocketCtor;\n /** Live bundle refresh: the editor pushed a freshly compiled bundle. `data` is the .patterc JSON;\n * hand it (with your current engine + bundle) to `applyLiveBundle`, re-bind your flow handles if it\n * hot-swapped, then call `link.setBuild(build)`. Never called with malformed payloads. */\n onBundle?: (msg: { build: string; data: string }) => void;\n}\n\nexport interface DebugLink {\n /** Tell the editor a flow opened (so it can list it in the follow selector). */\n flowOpened(flowId: string): void;\n /** Report the current position of a flow - call after each advance()/choose(). */\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void;\n /** Tell the editor a flow closed. */\n flowClosed(flowId: string): void;\n /** After applying a pushed bundle: report the build now running (re-hellos, so the editor's\n * match/stale pill updates and it stops re-pushing the same bundle). */\n setBuild(build: string): void;\n /** Close the link. */\n close(): void;\n /** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game \"the editor\n * is not listening\" and \"I never attached\" look the same, and this tells them apart. */\n readonly state: \"connecting\" | \"connected\" | \"closed\";\n /** The build the link handshakes with (the bundle's content hash), as `setBuild` last left it. */\n readonly build: string;\n /** The address the link dials. */\n readonly url: string;\n}\n\nconst OPEN = 1; // WebSocket.OPEN\n\n/**\n * Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects\n * or if it was never listening - safe to leave wired into a shipping build behind a flag.\n */\nexport function createDebugLink(opts: DebugLinkOptions): DebugLink {\n const url = opts.url ?? \"ws://127.0.0.1:4471\";\n const Ctor: DebugSocketCtor | undefined = opts.WebSocket ?? (globalThis as { WebSocket?: DebugSocketCtor }).WebSocket;\n const flows = new Set<string>();\n let queue: string[] = [];\n let sock: DebugSocketLike | null = null;\n let closed = false;\n let build = opts.build; // mutable: setBuild() after a live bundle refresh lands\n\n const flush = (): void => {\n if (!sock || sock.readyState !== OPEN) return;\n for (const m of queue) { try { sock.send(m); } catch { /* socket went away */ } }\n queue = [];\n };\n // Queue only while there is a socket that is connecting or open. Once the editor is gone (or never\n // answered), the socket is dropped and a message has nowhere to go, so it is dropped too: queueing it\n // anyway grew the queue by one string per step for the rest of a game that shipped with the link wired\n // in, which is exactly the build the docs say this is safe in.\n const post = (msg: object): void => {\n if (closed || !sock || sock.readyState > OPEN) return;\n queue.push(JSON.stringify(msg));\n flush();\n };\n\n if (!Ctor) {\n // No WebSocket available (old Node, no impl passed) - degrade to a silent no-op link.\n return { flowOpened() {}, observe() {}, flowClosed() {}, setBuild() {}, close() { closed = true; }, state: \"closed\", build, url };\n }\n\n const sendHello = (): void => {\n const hello = JSON.stringify({ t: \"hello\", v: 1, build, project: opts.project, flows: [...flows] });\n try { sock?.send(hello); } catch { /* race: closed immediately */ }\n };\n\n try {\n sock = new Ctor(url);\n sock.addEventListener(\"open\", () => {\n // Handshake first, so the editor can verify the build + seed the flow list before frames arrive.\n sendHello();\n flush();\n });\n // Live bundle refresh: the editor pushed a new bundle. Validate the shape here so the host's\n // handler never sees a malformed payload; everything else the editor might send is ignored.\n sock.addEventListener(\"message\", (ev: { data: unknown }) => {\n if (!opts.onBundle || typeof ev.data !== \"string\") return;\n try {\n const msg = JSON.parse(ev.data) as Record<string, unknown>;\n if (msg.t === \"bundle\" && typeof msg.build === \"string\" && typeof msg.data === \"string\") {\n opts.onBundle({ build: msg.build, data: msg.data });\n }\n } catch { /* not for us */ }\n });\n sock.addEventListener(\"error\", () => { /* editor not listening - stay a no-op */ });\n sock.addEventListener(\"close\", () => { sock = null; queue = []; });\n } catch { sock = null; } // malformed URL etc. - never throw into the game\n\n return {\n flowOpened(flowId: string): void { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); },\n flowClosed(flowId: string): void { flows.delete(flowId); post({ t: \"flowClose\", flow: flowId }); },\n observe(flowId: string, sceneId: string | null, beatId: string | null, type: string, choiceId?: string): void {\n // A flow reports itself. `flowOpened` is the host's job and nothing could check it, so a game\n // that opened a flow and forgot to announce it left the editor's list short - and the omission\n // survived a reconnect, because the hello sends this set. Observing a flow we have not seen\n // announces it first, which makes the host's call a courtesy (it lists the flow before its\n // first step) rather than something to remember.\n if (!flows.has(flowId)) { flows.add(flowId); post({ t: \"flowOpen\", flow: flowId }); }\n post({ t: \"frame\", flow: flowId, sceneId, beatId, type, choiceId });\n },\n setBuild(next: string): void {\n if (closed || next === build) return;\n build = next;\n if (sock && sock.readyState === OPEN) sendHello(); // re-handshake: the editor re-reads the build\n },\n close(): void { closed = true; queue = []; try { sock?.close(); } catch { /* already gone */ } sock = null; },\n get state() { return closed || !sock ? \"closed\" : sock.readyState === OPEN ? \"connected\" : \"connecting\"; },\n get build() { return build; },\n url,\n };\n}\n","// ---------------------------------------------------------------------------\n// Live bundle refresh (design/proposals/live-bundle-refresh.md) - the game-side\n// applier. The editor pushes a freshly compiled bundle over the debug link\n// (createDebugLink's `onBundle`); this helper picks the right tier and applies\n// it:\n// - same structureHash -> tier 1: engine.replaceStrings() - nothing restarts.\n// - changed structure -> tier 2: engine.hotSwap() - the run carried over,\n// content drift resolved by the save system (§9.8).\n// Returns the engine to keep using (the SAME one for \"text\", a REPLACEMENT for\n// \"structure\" - re-bind your Flow handles via engine.getFlow(id)) plus the\n// parsed bundle for the next comparison. Wire-up:\n//\n// let { engine, bundle } = boot();\n// const link = createDebugLink({\n// build: bundle.content.hash!,\n// onBundle: ({ build, data }) => {\n// const r = applyLiveBundle(engine, bundle, data);\n// engine = r.engine; bundle = r.bundle;\n// if (r.kind === \"structure\") flow = engine.getFlow(\"main\")!;\n// link.setBuild(build);\n// },\n// });\n// ---------------------------------------------------------------------------\n\nimport { Engine } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/runtime\";\n\nexport interface LiveBundleResult {\n /** The engine to keep using: the same instance for a \"text\" swap, a replacement for \"structure\". */\n engine: Engine;\n /** The parsed pushed bundle - hold on to it for the next apply's comparison. */\n bundle: Bundle;\n /** Which tier applied: \"text\" (strings-only, nothing restarted) or \"structure\" (full hot swap). */\n kind: \"text\" | \"structure\";\n}\n\n/**\n * Apply a bundle the editor pushed over the debug link. `current` is the bundle `engine` is running\n * (needed for the structure-hash comparison); `data` is the pushed .patterc JSON. Throws only on\n * unparseable JSON - a structural swap's edge cases are absorbed by the §9.8 drift policy inside\n * `hotSwap`, which never throws for ordinary edits.\n */\nexport function applyLiveBundle(engine: Engine, current: Bundle, data: string): LiveBundleResult {\n const next = JSON.parse(data) as Bundle;\n // Same structure = a text-only edit: swap the string tables in place and keep everything running.\n // Missing structureHash on either side (an older compiler) falls through to the full swap - safe,\n // just less gentle than it could be.\n const sameStructure = current.content.structureHash !== undefined\n && current.content.structureHash === next.content.structureHash;\n if (sameStructure) {\n engine.replaceStrings(next);\n return { engine, bundle: next, kind: \"text\" };\n }\n return { engine: engine.hotSwap(next), bundle: next, kind: \"structure\" };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// A live @patter property inspector for the browser: a small DOM panel that\n// watches AND edits a running Engine's shared @patter properties. Type-aware\n// editors (bool / number / text / enum / flags), a reset-to-default per row, and\n// a poll that refreshes values WITHOUT clobbering the field you're editing.\n//\n// The JS parity of the Unity PatterStateWindow / Godot PatterStatePanel. Those\n// use a PatterDebug registry because the editor window is separate from the game;\n// here the game runs in-process, so you just pass the engine directly:\n//\n// const inspector = createPropertyInspector(engine, { container: document.body });\n// // ...later: inspector.destroy();\n// ---------------------------------------------------------------------------\n\nimport type { Engine, PropertyRow } from \"@patterkit/runtime\";\nimport { serializeState, deserializeState } from \"./save.js\";\nimport type { PropertyValue } from \"./properties.js\";\n\nexport interface PropertyInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Runtime state\". */\n title?: string;\n /** Live-refresh interval in ms; 0 disables polling (call `refresh()` yourself). Default 250. */\n pollMs?: number;\n}\n\nexport interface PropertyInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** Re-read every property and update the editors, skipping the field you're editing. */\n refresh(): void;\n /** Stop polling and remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-inspector-style\";\nconst CSS = `\n.pp-insp{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:22rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-insp h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-insp-empty{color:#8a9691;font-style:italic}\n.pp-insp-io { display: flex; gap: 6px; margin: 0 0 8px; }\n .pp-insp-io button { font: inherit; padding: 2px 8px; cursor: pointer; }\n .pp-insp-row{display:flex;align-items:center;gap:.4rem;margin:.18rem 0}\n.pp-insp-ref{flex:0 0 8rem;font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.pp-insp-ctl{flex:1;min-width:0;display:flex}\n.pp-insp-ctl input[type=text],.pp-insp-ctl input[type=number],.pp-insp-ctl select{width:100%;box-sizing:border-box;font:inherit;padding:.15rem .3rem;border:1px solid #cfc7b8;border-radius:6px;background:#fff;color:inherit}\n.pp-insp-reset{flex:0 0 auto;border:1px solid #cfc7b8;background:#fff;border-radius:6px;cursor:pointer;color:#5c6b62;width:1.6rem;height:1.6rem;line-height:1;padding:0}\n.pp-insp-reset:disabled{opacity:.35;cursor:default}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\n/** Has the DISPLAYED value changed? Order-sensitive on purpose, and deliberately\n * NOT the language's value equality, which compares flags as a set: this asks\n * whether the row needs redrawing, and the row renders the list in its stored\n * order, so a reordering is a change here even though it is not a change to\n * what the value means. */\nfunction sameValue(a: PropertyValue | undefined, b: PropertyValue | undefined): boolean {\n if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((x, i) => x === b[i]);\n return a === b;\n}\n\nexport function createPropertyInspector(engine: Engine, opts: PropertyInspectorOptions = {}): PropertyInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n\n const el = doc.createElement(\"div\");\n el.className = \"pp-insp\";\n const heading = doc.createElement(\"h4\");\n heading.textContent = opts.title ?? \"Runtime state\";\n const list = doc.createElement(\"div\");\n\n // Save/Load the whole run as a patter/save@0 envelope file - the parity of Unity's\n // PatterStateWindow buttons, Godot's state-panel dialogs, and Unreal's Slate panel.\n const io = doc.createElement(\"div\");\n io.className = \"pp-insp-io\";\n const saveBtn = doc.createElement(\"button\");\n saveBtn.type = \"button\";\n saveBtn.textContent = \"Save state\\u2026\";\n saveBtn.addEventListener(\"click\", () => {\n const blob = new Blob([serializeState(engine)], { type: \"application/json\" });\n const a = doc.createElement(\"a\");\n a.href = URL.createObjectURL(blob);\n a.download = \"save.patterstate\";\n a.click();\n URL.revokeObjectURL(a.href);\n });\n const loadBtn = doc.createElement(\"button\");\n loadBtn.type = \"button\";\n loadBtn.textContent = \"Load state\\u2026\";\n loadBtn.addEventListener(\"click\", () => {\n const input = doc.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".patterstate,application/json\";\n input.addEventListener(\"change\", () => {\n const file = input.files?.[0];\n if (!file) return;\n void file.text().then((text) => {\n try { deserializeState(engine, text); refresh(); }\n catch (err) { console.error(\"Load state failed:\", err instanceof Error ? err.message : err); }\n });\n });\n input.click();\n });\n io.append(saveBtn, loadBtn);\n el.append(heading, io, list);\n\n const rowRefreshers: Array<() => void> = [];\n\n const buildRow = (row: PropertyRow): void => {\n const r = doc.createElement(\"div\");\n r.className = \"pp-insp-row\";\n const label = doc.createElement(\"span\");\n label.className = \"pp-insp-ref\";\n label.textContent = row.path;\n label.title = row.path;\n const ctl = doc.createElement(\"div\");\n ctl.className = \"pp-insp-ctl\";\n const reset = doc.createElement(\"button\");\n reset.className = \"pp-insp-reset\";\n reset.type = \"button\";\n reset.textContent = \"↺\"; // ↺\n reset.title = \"Reset to default\";\n r.append(label, ctl, reset);\n list.appendChild(r);\n\n // `read` pushes the live engine value into the editor, but never while that editor has focus\n // (so it can't wipe what you're typing). `syncReset` disables the ↺ when the value is the default.\n let read: () => void;\n const commit = (v: PropertyValue): void => { engine.setProperty(row.path, v); syncReset(); };\n const focused = (node: Element): boolean => doc.activeElement === node;\n function syncReset(): void { reset.disabled = sameValue(engine.getProperty(row.path), row.default); }\n reset.addEventListener(\"click\", () => { engine.setProperty(row.path, row.default); read(); syncReset(); });\n\n if (row.type === \"boolean\") {\n const cb = doc.createElement(\"input\");\n cb.type = \"checkbox\";\n cb.addEventListener(\"change\", () => commit(cb.checked));\n ctl.appendChild(cb);\n read = () => { if (!focused(cb)) cb.checked = engine.getProperty(row.path) === true; };\n } else if (row.type === \"number\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"number\";\n inp.addEventListener(\"change\", () => commit(Number(inp.value)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"enum\" || row.type === \"quality\") {\n // A quality edits as a dropdown of its STAGES - the ladder is closed, like an enum's values.\n const sel = doc.createElement(\"select\");\n for (const v of (row.type === \"quality\" ? row.stages : row.values) ?? []) {\n const o = doc.createElement(\"option\");\n o.value = v; o.textContent = v;\n sel.appendChild(o);\n }\n sel.addEventListener(\"change\", () => commit(sel.value));\n ctl.appendChild(sel);\n read = () => { if (!focused(sel)) sel.value = String(engine.getProperty(row.path) ?? \"\"); };\n } else if (row.type === \"flags\") {\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.placeholder = \"comma, separated, flags\";\n inp.addEventListener(\"change\", () => commit(inp.value.split(\",\").map((s) => s.trim()).filter((s) => s.length > 0)));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) { const v = engine.getProperty(row.path); inp.value = Array.isArray(v) ? v.join(\", \") : \"\"; } };\n } else {\n // string (and any unrecognised type) -> a text field\n const inp = doc.createElement(\"input\");\n inp.type = \"text\";\n inp.addEventListener(\"change\", () => commit(inp.value));\n ctl.appendChild(inp);\n read = () => { if (!focused(inp)) inp.value = String(engine.getProperty(row.path) ?? \"\"); };\n }\n\n read();\n syncReset();\n rowRefreshers.push(() => { read(); syncReset(); });\n };\n\n // Properties are fixed for a bundle, so build the rows once; polling just refreshes their values.\n const props = engine.listProperties();\n if (props.length === 0) {\n const empty = doc.createElement(\"div\");\n empty.className = \"pp-insp-empty\";\n empty.textContent = \"No @patter properties.\";\n list.appendChild(empty);\n } else {\n for (const row of props) buildRow(row);\n }\n\n const refresh = (): void => { for (const fn of rowRefreshers) fn(); };\n\n opts.container?.appendChild(el);\n const pollMs = opts.pollMs ?? 250;\n let timer: ReturnType<typeof setInterval> | undefined;\n if (pollMs > 0) timer = setInterval(refresh, pollMs);\n\n return {\n el,\n refresh,\n destroy() { if (timer !== undefined) clearInterval(timer); el.remove(); },\n };\n}\n","/// <reference lib=\"dom\" />\n// ---------------------------------------------------------------------------\n// The bundle inspector's VIEW, for the web: a read-only panel over\n// `describeBundle`, in the same visual grammar as the live property inspector.\n//\n// The other three runtimes hang their view off an imported asset - a Unity\n// CustomEditor, an Unreal details customisation, a Godot EditorInspectorPlugin.\n// JS has no asset pipeline, so there is no inspector to extend and the summary\n// function IS the surface. This exists because \"the function is the surface\"\n// leaves the JS side without the thing the other three give you for free: a\n// place to LOOK at a bundle. A drop-in page can now do\n//\n// const panel = createBundleInspector(bundle, { container: document.body });\n// // ...later: panel.destroy();\n//\n// Read-only on purpose. The live inspector edits because a running game has\n// state worth changing; this describes an artefact, and there is nothing here a\n// reader could sensibly write to.\n// ---------------------------------------------------------------------------\n\nimport { describeBundle } from \"@patterkit/runtime\";\nimport type { Bundle } from \"@patterkit/model\";\nimport type { BundleDescription, PropertySummary } from \"@patterkit/runtime\";\n\nexport interface BundleInspectorOptions {\n /** Where to mount the panel. If omitted, append the returned `el` yourself. */\n container?: HTMLElement;\n /** Panel heading. Default \"Bundle\". */\n title?: string;\n /** Sections open on mount. Default: identity and addresses, the two an\n * integrator reaches for first; the rest are a click away. */\n open?: BundleSection[];\n}\n\nexport type BundleSection = \"identity\" | \"addresses\" | \"hostScopes\" | \"properties\" | \"gameData\" | \"counts\";\n\nexport interface BundleInspector {\n /** The panel root (already inside `container` if you passed one). */\n readonly el: HTMLElement;\n /** The description the panel is showing, for a caller that wants the data too. */\n readonly description: BundleDescription;\n /** Remove the panel from the DOM. */\n destroy(): void;\n}\n\nconst STYLE_ID = \"pp-bundle-style\";\n// Deliberately the property inspector's palette and metrics: the two panels are\n// often on the same page, and a second look-and-feel would read as a second tool.\nconst CSS = `\n.pp-bundle{font:13px/1.4 ui-sans-serif,system-ui,sans-serif;color:#15201e;background:#f4efe6;border:1px solid #cfc7b8;border-radius:10px;padding:.6rem .7rem;max-width:26rem;box-shadow:0 6px 20px rgba(21,32,30,.12)}\n.pp-bundle h4{margin:0 0 .4rem;font:600 .72rem/1 ui-sans-serif,system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#5c6b62}\n.pp-bundle details{border-top:1px solid #e3dccf;padding:.3rem 0}\n.pp-bundle details:first-of-type{border-top:0}\n.pp-bundle summary{cursor:pointer;font-weight:600;color:#214f4b;list-style:none}\n.pp-bundle summary::-webkit-details-marker{display:none}\n.pp-bundle summary::before{content:\"\\\\25B8\";display:inline-block;width:1rem;color:#8a9691}\n.pp-bundle details[open]>summary::before{content:\"\\\\25BE\"}\n.pp-bundle-count{color:#8a9691;font-weight:400}\n.pp-bundle-row{display:flex;gap:.4rem;margin:.16rem 0 .16rem 1rem}\n.pp-bundle-key{flex:0 0 8rem;color:#5c6b62}\n.pp-bundle-val,.pp-bundle-addr{font-family:ui-monospace,monospace;font-size:.78rem;color:#214f4b;word-break:break-all}\n.pp-bundle-sub{margin-left:2rem}\n.pp-bundle-empty{color:#8a9691;font-style:italic;margin-left:1rem}\n.pp-bundle-warn{color:#8a3a2f;font-weight:600}\n.pp-bundle-tag{font-size:.7rem;color:#5c6b62;border:1px solid #cfc7b8;border-radius:5px;padding:0 .25rem;margin-left:.3rem}\n`;\n\nfunction injectStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const s = doc.createElement(\"style\");\n s.id = STYLE_ID;\n s.textContent = CSS;\n (doc.head ?? doc.documentElement).appendChild(s);\n}\n\nconst el = (doc: Document, tag: string, cls?: string, text?: string): HTMLElement => {\n const e = doc.createElement(tag);\n if (cls) e.className = cls;\n if (text !== undefined) e.textContent = text;\n return e;\n};\n\n/** One \"key: value\" line. */\nfunction row(doc: Document, key: string, value: string, valueClass = \"pp-bundle-val\"): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key\", key), el(doc, \"span\", valueClass, value));\n return r;\n}\n\n/** A property line: the name, its type, and whether the HOST must supply a value.\n * \"no default\" is the part an integrator is actually scanning for. */\nfunction propertyRow(doc: Document, p: PropertySummary): HTMLElement {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", p.name));\n const meta = el(doc, \"span\", \"pp-bundle-val\", p.type);\n if (!p.hasDefault) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"no default\"));\n r.append(meta);\n return r;\n}\n\nfunction section(doc: Document, name: BundleSection, label: string, count: number | null, open: boolean): HTMLDetailsElement {\n const d = doc.createElement(\"details\");\n d.dataset[\"section\"] = name;\n d.open = open;\n const s = doc.createElement(\"summary\");\n s.textContent = label;\n if (count !== null) s.append(el(doc, \"span\", \"pp-bundle-count\", ` ${count}`));\n d.append(s);\n return d;\n}\n\n/**\n * Build a read-only panel describing a compiled bundle: what it is, what game\n * code may call on it, and what the host must supply.\n */\nexport function createBundleInspector(bundle: Bundle, opts: BundleInspectorOptions = {}): BundleInspector {\n const doc = opts.container?.ownerDocument ?? document;\n injectStyle(doc);\n const d = describeBundle(bundle);\n const open = new Set<BundleSection>(opts.open ?? [\"identity\", \"addresses\"]);\n\n const root = el(doc, \"div\", \"pp-bundle\");\n root.append(el(doc, \"h4\", undefined, opts.title ?? \"Bundle\"));\n\n // --- identity ------------------------------------------------------------\n const id = section(doc, \"identity\", d.identity.project || \"(unnamed project)\", null, open.has(\"identity\"));\n if (d.identity.version) id.append(row(doc, \"version\", d.identity.version));\n id.append(row(doc, \"schema\", d.identity.schema));\n id.append(row(doc, \"locales\", `${d.identity.defaultLocale}${d.identity.locales.length > 1 ? ` (+${d.identity.locales.length - 1})` : \"\"}`));\n id.append(row(doc, \"strings\", d.identity.localisation));\n if (d.identity.voiced) id.append(row(doc, \"voiced\", \"yes\"));\n if (d.identity.hash) id.append(row(doc, \"hash\", d.identity.hash));\n // Equal structureHash + a different hash = a text-only edit, which is what makes\n // a live hot-swap safe. Both are shown so an integrator can tell those apart.\n if (d.identity.structureHash) id.append(row(doc, \"structure\", d.identity.structureHash));\n if (d.identity.sourceDebug) {\n id.append(row(doc, \"build\", \"SOURCE DEBUG - not shippable\", \"pp-bundle-val pp-bundle-warn\"));\n }\n root.append(id);\n\n // --- addresses -----------------------------------------------------------\n const addr = section(doc, \"addresses\", \"Addresses\", d.addresses.length, open.has(\"addresses\"));\n if (!d.addresses.length) addr.append(el(doc, \"div\", \"pp-bundle-empty\", \"no scenes\"));\n for (const a of d.addresses) {\n const r = el(doc, \"div\", \"pp-bundle-row\");\n r.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", a.gameId), el(doc, \"span\", \"pp-bundle-val\", a.name));\n addr.append(r);\n // Nested, because a block address is scene-scoped: the pair is the address.\n for (const b of a.blocks) {\n const br = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n br.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", b.gameId), el(doc, \"span\", \"pp-bundle-val\", b.name));\n addr.append(br);\n }\n }\n root.append(addr);\n\n // --- host scopes ---------------------------------------------------------\n // Counted in PROPERTIES rather than scopes: the heading says \"properties\", and a\n // reader comparing it with the rows below should not have to work out which it meant.\n // An opaque scope contributes none, and says so on its own row.\n const hostCount = d.hostScopes.reduce((n, s) => n + s.properties.length, 0);\n const host = section(doc, \"hostScopes\", \"Host properties\", hostCount, open.has(\"hostScopes\"));\n if (!d.hostScopes.length) host.append(el(doc, \"div\", \"pp-bundle-empty\", \"the game supplies nothing\"));\n for (const s of d.hostScopes) {\n const head = el(doc, \"div\", \"pp-bundle-row\");\n head.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", `@${s.token}`));\n const meta = el(doc, \"span\", \"pp-bundle-val\", s.opaque ? \"any name, unchecked\" : `${s.properties.length} declared`);\n if (!s.writable) meta.append(el(doc, \"span\", \"pp-bundle-tag\", \"read-only\"));\n head.append(meta);\n host.append(head);\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n host.append(pr);\n }\n }\n root.append(host);\n\n // --- owned properties ----------------------------------------------------\n const ownedCount = d.properties.patter.length + d.properties.scene.reduce((n, s) => n + s.properties.length, 0);\n const owned = section(doc, \"properties\", \"Story properties\", ownedCount, open.has(\"properties\"));\n if (!ownedCount) owned.append(el(doc, \"div\", \"pp-bundle-empty\", \"none declared\"));\n for (const p of d.properties.patter) owned.append(propertyRow(doc, p));\n for (const s of d.properties.scene) {\n owned.append(row(doc, `@scene`, s.gameId, \"pp-bundle-val pp-bundle-addr\"));\n for (const p of s.properties) {\n const pr = propertyRow(doc, p);\n pr.classList.add(\"pp-bundle-sub\");\n owned.append(pr);\n }\n }\n root.append(owned);\n\n // --- gameData ------------------------------------------------------------\n if (d.gameData.length) {\n const gd = section(doc, \"gameData\", \"Game data\", d.gameData.reduce((n, g) => n + g.fields.length, 0), open.has(\"gameData\"));\n for (const g of d.gameData) {\n gd.append(row(doc, \"on\", g.kind));\n for (const f of g.fields) {\n const fr = el(doc, \"div\", \"pp-bundle-row pp-bundle-sub\");\n fr.append(el(doc, \"span\", \"pp-bundle-key pp-bundle-addr\", f.name), el(doc, \"span\", \"pp-bundle-val\", f.type));\n gd.append(fr);\n }\n }\n root.append(gd);\n }\n\n // --- counts --------------------------------------------------------------\n const counts = section(doc, \"counts\", \"Counts\", null, open.has(\"counts\"));\n for (const [key, value] of Object.entries(d.counts)) counts.append(row(doc, key, String(value)));\n root.append(counts);\n\n opts.container?.append(root);\n return {\n el: root,\n description: d,\n destroy() { root.remove(); },\n };\n}\n","// ---------------------------------------------------------------------------\n// Audio resolver (#206): map a beat id to the path/URL of its winning audio\n// take, using the `patteraudio.json` manifest Patterpad (or the CLI) emits next\n// to the Audio Folders. It RESOLVES ONLY - playback stays yours (Web Audio, an\n// <audio> element, etc.), which can't be portable across platforms anyway.\n//\n// The manifest already encodes the highest-rung \"winner\" per beat, so there's no\n// folder search at runtime. Deploy your audio folder somewhere your game can read\n// (e.g. under /public), fetch the manifest once, and:\n//\n// const audio = createAudioResolver(manifestJson, \"audio\"); // base = where the folder is served\n// const src = audio.resolve(step.id); // \"audio/final/L1.wav\", or null when the beat has no recording\n// if (src) new Audio(src).play();\n// ---------------------------------------------------------------------------\n\nexport interface AudioResolver {\n /** The full path/URL of a beat's winning audio take, or null when it has none. */\n resolve(beatId: string): string | null;\n}\n\ninterface AudioManifest {\n schema?: string;\n clips?: Record<string, { file: string; status?: string }>;\n}\n\n/**\n * Build an audio resolver from a `patteraudio.json` manifest string plus the base path/URL its files live\n * under (wherever you deployed the audio folder). `resolve(beatId)` joins the base with the winning clip's\n * relative path, or returns null when the beat has no recording. Never throws on a missing beat.\n */\nexport function createAudioResolver(manifestJson: string, basePath: string): AudioResolver {\n const manifest = JSON.parse(manifestJson) as AudioManifest;\n const clips = manifest.clips ?? {};\n // A base that already ends in a separator is joined as it stands: trimming it turned a root such as \"/\"\n // or \"res://\" into \"\" or \"res:\", and the path lost its root.\n const sep = basePath === \"\" || /[/\\\\]$/.test(basePath) ? \"\" : \"/\";\n return {\n resolve(beatId: string): string | null {\n const clip = clips[beatId];\n if (!clip || !clip.file) return null;\n return `${basePath}${sep}${clip.file}`;\n },\n };\n}\n"],"mappings":"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","names":["createKernelStateLogger","el","row","import_runtime","STYLE_ID","CSS","injectStyle","gd"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -95,6 +95,13 @@ interface DebugLink {
|
|
|
95
95
|
setBuild(build: string): void;
|
|
96
96
|
/** Close the link. */
|
|
97
97
|
close(): void;
|
|
98
|
+
/** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game "the editor
|
|
99
|
+
* is not listening" and "I never attached" look the same, and this tells them apart. */
|
|
100
|
+
readonly state: "connecting" | "connected" | "closed";
|
|
101
|
+
/** The build the link handshakes with (the bundle's content hash), as `setBuild` last left it. */
|
|
102
|
+
readonly build: string;
|
|
103
|
+
/** The address the link dials. */
|
|
104
|
+
readonly url: string;
|
|
98
105
|
}
|
|
99
106
|
/**
|
|
100
107
|
* Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects
|
package/dist/index.d.ts
CHANGED
|
@@ -95,6 +95,13 @@ interface DebugLink {
|
|
|
95
95
|
setBuild(build: string): void;
|
|
96
96
|
/** Close the link. */
|
|
97
97
|
close(): void;
|
|
98
|
+
/** What the link is doing: `connecting`, `connected`, or `closed`. From inside a running game "the editor
|
|
99
|
+
* is not listening" and "I never attached" look the same, and this tells them apart. */
|
|
100
|
+
readonly state: "connecting" | "connected" | "closed";
|
|
101
|
+
/** The build the link handshakes with (the bundle's content hash), as `setBuild` last left it. */
|
|
102
|
+
readonly build: string;
|
|
103
|
+
/** The address the link dials. */
|
|
104
|
+
readonly url: string;
|
|
98
105
|
}
|
|
99
106
|
/**
|
|
100
107
|
* Open a live debug link to Patterpad. Returns a handle whose calls are no-ops once the editor disconnects
|
package/dist/index.js
CHANGED
|
@@ -57,11 +57,10 @@ function describeStep(step) {
|
|
|
57
57
|
}
|
|
58
58
|
var gd = (data) => data ? ` gameData=${JSON.stringify(data)}` : "";
|
|
59
59
|
function visitState(engine) {
|
|
60
|
-
const save = engine.saveGame();
|
|
61
60
|
const out = {};
|
|
62
|
-
for (const [id, n] of Object.entries(
|
|
63
|
-
for (const
|
|
64
|
-
for (const [id, n] of Object.entries(
|
|
61
|
+
for (const [id, n] of Object.entries(engine.getVisitCounts())) out[`visit:${id}`] = n;
|
|
62
|
+
for (const flow of engine.flows()) {
|
|
63
|
+
for (const [id, n] of Object.entries(flow.getVisitCounts())) out[`${flow.id}/visit:${id}`] = n;
|
|
65
64
|
}
|
|
66
65
|
return out;
|
|
67
66
|
}
|
|
@@ -115,7 +114,7 @@ function createDebugLink(opts) {
|
|
|
115
114
|
}, setBuild() {
|
|
116
115
|
}, close() {
|
|
117
116
|
closed = true;
|
|
118
|
-
} };
|
|
117
|
+
}, state: "closed", build, url };
|
|
119
118
|
}
|
|
120
119
|
const sendHello = () => {
|
|
121
120
|
const hello = JSON.stringify({ t: "hello", v: 1, build, project: opts.project, flows: [...flows] });
|
|
@@ -178,7 +177,14 @@ function createDebugLink(opts) {
|
|
|
178
177
|
} catch {
|
|
179
178
|
}
|
|
180
179
|
sock = null;
|
|
181
|
-
}
|
|
180
|
+
},
|
|
181
|
+
get state() {
|
|
182
|
+
return closed || !sock ? "closed" : sock.readyState === OPEN ? "connected" : "connecting";
|
|
183
|
+
},
|
|
184
|
+
get build() {
|
|
185
|
+
return build;
|
|
186
|
+
},
|
|
187
|
+
url
|
|
182
188
|
};
|
|
183
189
|
}
|
|
184
190
|
|
|
@@ -526,12 +532,12 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
526
532
|
function createAudioResolver(manifestJson, basePath) {
|
|
527
533
|
const manifest = JSON.parse(manifestJson);
|
|
528
534
|
const clips = manifest.clips ?? {};
|
|
529
|
-
const
|
|
535
|
+
const sep = basePath === "" || /[/\\]$/.test(basePath) ? "" : "/";
|
|
530
536
|
return {
|
|
531
537
|
resolve(beatId) {
|
|
532
538
|
const clip = clips[beatId];
|
|
533
539
|
if (!clip || !clip.file) return null;
|
|
534
|
-
return
|
|
540
|
+
return `${basePath}${sep}${clip.file}`;
|
|
535
541
|
}
|
|
536
542
|
};
|
|
537
543
|
}
|