@storylet-studio/play-helpers 0.4.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.
@@ -0,0 +1,251 @@
1
+ import { Engine, Flow, LogEntry, EngineLogEntry, BundleDescription, PropertySummary, PropertyScopeSummary, TraceEvent, EngineOptions } from '@storylet-studio/runtime';
2
+ import { StateLoggerOptions, StateLogger, StateSnapshot, PropertyBag as PropertyBag$1 } from '@wildwinter/scoperegistry';
3
+ export { StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions, StateSnapshot, createStateLogger as createKernelStateLogger, diffState } from '@wildwinter/scoperegistry';
4
+ import { PropertyBag, SaveFile, Bundle } from '@storylet-studio/model';
5
+
6
+ /** The full flattened snapshot of ONE FLOW's view - the shared partitions
7
+ * plus that flow's own - straight off the save envelope, so "what the
8
+ * snapshot sees" is by construction "what a save persists". @world is not
9
+ * here for the same reason it is not in the envelope: the host owns that
10
+ * container and mounts/saves it itself (createWorldContainer). */
11
+ declare function snapshotState(engine: Engine, flow: Flow): StateSnapshot;
12
+ /** The storylets state logger: the kernel core mounted on the SHARED bags
13
+ * (engine.listBags()) and one flow's own (flow.listBags()) - the same
14
+ * prefixes, one path space, names disjoint - plus the flow's turns /
15
+ * cooldowns / board adapter. A host that wants @world lines mounts its
16
+ * world container's bag through createKernelStateLogger itself. */
17
+ declare function createStateLogger(engine: Engine, flow: Flow, opts?: StateLoggerOptions): StateLogger;
18
+
19
+ /** The current engine state (and the host's @world values, if given) as
20
+ * pretty-printed .storyletsave JSON. */
21
+ declare function serializeState(engine: Engine, world?: PropertyBag): string;
22
+ /**
23
+ * Capture the whole engine (and the host's @world values, if it keeps any) as
24
+ * the tagged save-file OBJECT.
25
+ *
26
+ * Four verbs, in Patterplay's pairing (`patter` play-helpers `save.ts`, and
27
+ * the same in all four of its runtimes): saveState / loadState work on the
28
+ * PARSED object, serializeState / deserializeState work on TEXT.
29
+ *
30
+ * This reference had a different shape until 2026-08-29 - `deserializeState`
31
+ * parsed and did not restore, `loadState` took text - so one name meant two
32
+ * things across the four Storylets runtimes, and neither matched the family.
33
+ * Godot and Unreal already had Patter's shape; these two were brought to it.
34
+ */
35
+ declare function saveState(engine: Engine, world?: PropertyBag): SaveFile;
36
+ /** Restore a {@link saveState} file into an engine. EVERY FLOW IS REBUILT, so
37
+ * the Flow handles you held before are inert: re-take them with
38
+ * `engine.getFlow(id)`, NOT `engine.openFlow(id)`. `openFlow` on an existing
39
+ * id REPLACES it, which here throws away the hand the file just restored, and
40
+ * the failure lands later, as `play()` refusing a card as "not dealt". (The
41
+ * engine's `onReplacedFlow` hook reports exactly this.) Throws on a foreign or malformed
42
+ * file, and the runtime's own project check still applies. Returns the file's
43
+ * @world values, if any - the HOST applies them to its container; the engine
44
+ * never touches them. */
45
+ declare function loadState(engine: Engine, file: SaveFile): PropertyBag | undefined;
46
+ /** Parse + restore a {@link serializeState} string: the TEXT twin of
47
+ * loadState, as Patterplay pairs them. Throws on malformed JSON, a foreign
48
+ * file or a project mismatch. Returns the file's @world values for the host. */
49
+ declare function deserializeState(engine: Engine, json: string): PropertyBag | undefined;
50
+
51
+ interface PropertyInspectorOptions {
52
+ /** Mount point; defaults to document.body. */
53
+ container?: HTMLElement;
54
+ title?: string;
55
+ /** Value-refresh poll; 0 disables polling. */
56
+ pollMs?: number;
57
+ }
58
+ interface PropertyInspector {
59
+ el: HTMLElement;
60
+ refresh(): void;
61
+ destroy(): void;
62
+ }
63
+ /** Inject the shared panel stylesheet once. Exported so the bundle inspector
64
+ * (bundle-inspector.ts) renders in the same CSS grammar. */
65
+ declare function ensureInspectorStyle(): void;
66
+ /** One line per entry, `[turn]`-stamped where the event has a box context
67
+ * (write lines share the state logger's `path: from -> to` reading). */
68
+ declare function formatLogEntry(e: LogEntry | EngineLogEntry): string;
69
+ declare function createPropertyInspector(engine: Engine, flow: Flow, opts?: PropertyInspectorOptions): PropertyInspector;
70
+
71
+ interface BundleInspectorOptions {
72
+ /** Mount point; defaults to document.body. */
73
+ container?: HTMLElement;
74
+ title?: string;
75
+ /** Start the collapsible sections open (default true). */
76
+ open?: boolean;
77
+ }
78
+ interface BundleInspector {
79
+ el: HTMLElement;
80
+ /** The description this panel rendered (the API is the parity member). */
81
+ description: BundleDescription;
82
+ destroy(): void;
83
+ }
84
+ /** "name: type = default", plus enum/flags options where declared. */
85
+ declare function formatPropertySummary(p: PropertySummary): string;
86
+ /** The scope label a declaration block files under ("world", "box box",
87
+ * "tag docks (zone)"). */
88
+ declare function formatScopeLabel(scope: PropertyScopeSummary): string;
89
+ /** Render a read-only summary of a compiled bundle: what an integrator may
90
+ * call, with no session and no game running. */
91
+ declare function createBundleInspector(bundle: Bundle, opts?: BundleInspectorOptions): BundleInspector;
92
+
93
+ /** A minimal structural type for a WebSocket implementation (browsers and
94
+ * Node 22+ have a global one). */
95
+ interface LiveSocketLike {
96
+ readyState: number;
97
+ send(data: string): void;
98
+ close(): void;
99
+ addEventListener(type: "open" | "close" | "error", listener: () => void): void;
100
+ /** Incoming editor messages (the pushed bundle). Optional so a bare
101
+ * send-only socket still fits. */
102
+ addEventListener(type: "message", listener: (ev: {
103
+ data: unknown;
104
+ }) => void): void;
105
+ }
106
+ type LiveSocketCtor = new (url: string) => LiveSocketLike;
107
+ interface LiveLinkOptions {
108
+ /** The running bundle's build identity: pass `bundle.content.hash`. The
109
+ * editor compares it with its own compiled hash (in sync / stale). */
110
+ build: string;
111
+ /** Optional project name, shown in the editor's connect-chip tooltip. */
112
+ project?: string;
113
+ /** Editor WebSocket URL. Default `ws://127.0.0.1:4472`. */
114
+ url?: string;
115
+ /** A WebSocket constructor to use instead of the global one (tests with a
116
+ * fake socket, or a host without a global WebSocket). */
117
+ WebSocket?: LiveSocketCtor;
118
+ /** Live refresh: the editor pushed a freshly compiled bundle. `data` is
119
+ * the .storyletsc JSON; hand it (with your current Engine) to
120
+ * `applyLiveBundle`, `attach` the engine it returns, then call
121
+ * `link.setBuild(build)`. Never called with a malformed frame. */
122
+ onBundle?: (msg: {
123
+ build: string;
124
+ data: string;
125
+ }) => void;
126
+ }
127
+ interface LiveLink {
128
+ /** Start forwarding this ENGINE's trace: every flow's events, each frame
129
+ * naming the flow it came from, so the editor can follow one participant
130
+ * and switch. An earlier engine is detached first. Sends a board snapshot
131
+ * per open flow straight away, queued behind the hello if the socket is
132
+ * not open yet.
133
+ *
134
+ * Flows are discovered rather than declared: the link diffs `engine.flows()`
135
+ * whenever anything happens and emits `flowOpen` / `flowClose` itself. That
136
+ * is a deliberate departure from Patterplay, whose host calls `FlowOpened`
137
+ * by hand - it has no engine-level trace tap to hang the diff on and we do,
138
+ * so the host has nothing to remember and cannot get the editor's flow list
139
+ * wrong. The one cost: a flow that opens and then does nothing at all is not
140
+ * announced until the next event anywhere in the run. */
141
+ attach(engine: Engine): void;
142
+ /** Stop forwarding. A refresh replaces the engine, so attach the new one
143
+ * afterwards. */
144
+ detach(): void;
145
+ /** After applying a pushed bundle: report the build now running (re-hellos
146
+ * with the new build and a fresh board snapshot, so the editor's chip goes
147
+ * back to in sync and it stops re-pushing the same bundle). */
148
+ setBuild(build: string): void;
149
+ /** Close the link; every later call is a no-op. */
150
+ close(): void;
151
+ }
152
+ /** One game-to-editor frame, as the client serialises it. Exported for the
153
+ * fixture test; hosts never build these by hand. */
154
+ type LiveFrame = {
155
+ t: "hello";
156
+ v: 2;
157
+ build: string;
158
+ project?: string;
159
+ boxes?: string[];
160
+ flows: string[];
161
+ } | {
162
+ t: "flowOpen";
163
+ flow: string;
164
+ } | {
165
+ t: "flowClose";
166
+ flow: string;
167
+ } | {
168
+ t: "trace";
169
+ flow: string;
170
+ event: TraceEvent;
171
+ } | {
172
+ t: "board";
173
+ flow: string;
174
+ hands: Record<string, string[]>;
175
+ turns: Record<string, number>;
176
+ };
177
+ /** The cheap snapshot: hands by gameId holding card gameIds in dealt order,
178
+ * and every box's clock by gameId. */
179
+ declare function boardFrame(flow: Flow): Extract<LiveFrame, {
180
+ t: "board";
181
+ }>;
182
+ /**
183
+ * Open a Live Link to Storyletter. Returns a handle whose calls are no-ops once
184
+ * the editor disconnects or if it was never listening: safe to leave wired into
185
+ * a shipping build behind a flag.
186
+ */
187
+ declare function createLiveLink(opts: LiveLinkOptions): LiveLink;
188
+
189
+ type LiveBundleResult =
190
+ /** The new engine, carrying the old one's run (all flows), and the bundle
191
+ * it runs. */
192
+ {
193
+ ok: true;
194
+ engine: Engine;
195
+ bundle: Bundle;
196
+ }
197
+ /** Nothing changed: keep the engine you have. */
198
+ | {
199
+ ok: false;
200
+ error: string;
201
+ };
202
+ /**
203
+ * Apply a bundle the editor pushed over the Live Link: `new Engine(parsed,
204
+ * opts)` then `loadGame(engine.saveGame())`, returning the new engine. Never
205
+ * throws; a failure (unparseable JSON, a bundle the runtime rejects, a
206
+ * different project) comes back as `{ ok: false, error }` and the old engine
207
+ * is untouched.
208
+ *
209
+ * `opts` are the options the old engine was created with. An engine does
210
+ * not expose its seed, and it does not matter here: the save envelope carries
211
+ * each flow's PRNG state, so `loadGame` resumes the draw sequences exactly
212
+ * where they were and `seed` only shapes fresh flows. `log` does matter (the
213
+ * retained log is per flow), and so does `world` (the host's binding does not
214
+ * ride the envelope), so pass them if you had them.
215
+ */
216
+ declare function applyLiveBundle(engine: Engine, bundleJson: string, opts?: EngineOptions): LiveBundleResult;
217
+
218
+ type ScalarValue = boolean | number | string | string[];
219
+
220
+ /**
221
+ * A scope backed by a host resolver rather than a static bag - the basis for
222
+ * *foreign* scopes (e.g. `@game` / `@world`) whose values live in a host or
223
+ * another engine and are read (and optionally written) at runtime. The
224
+ * evaluator treats a missing property the same as a bag does (the scope's
225
+ * missing-policy decides false-vs-throw). A scope entirely absent from the
226
+ * EvalContext still resolves to false regardless.
227
+ */
228
+ interface ScopeResolver {
229
+ /** Read a property's value, or undefined if the scope does not have it. */
230
+ get(name: string): ScalarValue | undefined;
231
+ /** Write a property (omit for a read-only scope). The core never calls this;
232
+ * it is for host/runtime effect application (e.g. a state container's `set`). */
233
+ set?(name: string, value: ScalarValue): void;
234
+ }
235
+
236
+ interface WorldContainer {
237
+ /** Pass as `new Engine(bundle, { world: container.resolver })`. */
238
+ resolver: ScopeResolver;
239
+ /** The kernel bag itself (subscribe, audit, rows live there) - mount it
240
+ * into a state logger or examiner beside the engine's own bags. */
241
+ bag: PropertyBag$1;
242
+ /** The current values, for saving beside the engine's envelope. */
243
+ values(): PropertyBag;
244
+ /** Restore saved values over fresh defaults: orphaned keys drop, new
245
+ * declarations keep their defaults - the same drift rule as loadGame. */
246
+ load(values: PropertyBag): void;
247
+ }
248
+ /** A world container seeded from the bundle's @world declarations. */
249
+ declare function createWorldContainer(bundle: Bundle): WorldContainer;
250
+
251
+ export { type BundleInspector, type BundleInspectorOptions, type LiveBundleResult, type LiveFrame, type LiveLink, type LiveLinkOptions, type LiveSocketLike, type PropertyInspector, type PropertyInspectorOptions, type WorldContainer, applyLiveBundle, boardFrame, createBundleInspector, createLiveLink, createPropertyInspector, createStateLogger, createWorldContainer, deserializeState, ensureInspectorStyle, formatLogEntry, formatPropertySummary, formatScopeLabel, loadState, saveState, serializeState, snapshotState };
@@ -0,0 +1,251 @@
1
+ import { Engine, Flow, LogEntry, EngineLogEntry, BundleDescription, PropertySummary, PropertyScopeSummary, TraceEvent, EngineOptions } from '@storylet-studio/runtime';
2
+ import { StateLoggerOptions, StateLogger, StateSnapshot, PropertyBag as PropertyBag$1 } from '@wildwinter/scoperegistry';
3
+ export { StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions, StateSnapshot, createStateLogger as createKernelStateLogger, diffState } from '@wildwinter/scoperegistry';
4
+ import { PropertyBag, SaveFile, Bundle } from '@storylet-studio/model';
5
+
6
+ /** The full flattened snapshot of ONE FLOW's view - the shared partitions
7
+ * plus that flow's own - straight off the save envelope, so "what the
8
+ * snapshot sees" is by construction "what a save persists". @world is not
9
+ * here for the same reason it is not in the envelope: the host owns that
10
+ * container and mounts/saves it itself (createWorldContainer). */
11
+ declare function snapshotState(engine: Engine, flow: Flow): StateSnapshot;
12
+ /** The storylets state logger: the kernel core mounted on the SHARED bags
13
+ * (engine.listBags()) and one flow's own (flow.listBags()) - the same
14
+ * prefixes, one path space, names disjoint - plus the flow's turns /
15
+ * cooldowns / board adapter. A host that wants @world lines mounts its
16
+ * world container's bag through createKernelStateLogger itself. */
17
+ declare function createStateLogger(engine: Engine, flow: Flow, opts?: StateLoggerOptions): StateLogger;
18
+
19
+ /** The current engine state (and the host's @world values, if given) as
20
+ * pretty-printed .storyletsave JSON. */
21
+ declare function serializeState(engine: Engine, world?: PropertyBag): string;
22
+ /**
23
+ * Capture the whole engine (and the host's @world values, if it keeps any) as
24
+ * the tagged save-file OBJECT.
25
+ *
26
+ * Four verbs, in Patterplay's pairing (`patter` play-helpers `save.ts`, and
27
+ * the same in all four of its runtimes): saveState / loadState work on the
28
+ * PARSED object, serializeState / deserializeState work on TEXT.
29
+ *
30
+ * This reference had a different shape until 2026-08-29 - `deserializeState`
31
+ * parsed and did not restore, `loadState` took text - so one name meant two
32
+ * things across the four Storylets runtimes, and neither matched the family.
33
+ * Godot and Unreal already had Patter's shape; these two were brought to it.
34
+ */
35
+ declare function saveState(engine: Engine, world?: PropertyBag): SaveFile;
36
+ /** Restore a {@link saveState} file into an engine. EVERY FLOW IS REBUILT, so
37
+ * the Flow handles you held before are inert: re-take them with
38
+ * `engine.getFlow(id)`, NOT `engine.openFlow(id)`. `openFlow` on an existing
39
+ * id REPLACES it, which here throws away the hand the file just restored, and
40
+ * the failure lands later, as `play()` refusing a card as "not dealt". (The
41
+ * engine's `onReplacedFlow` hook reports exactly this.) Throws on a foreign or malformed
42
+ * file, and the runtime's own project check still applies. Returns the file's
43
+ * @world values, if any - the HOST applies them to its container; the engine
44
+ * never touches them. */
45
+ declare function loadState(engine: Engine, file: SaveFile): PropertyBag | undefined;
46
+ /** Parse + restore a {@link serializeState} string: the TEXT twin of
47
+ * loadState, as Patterplay pairs them. Throws on malformed JSON, a foreign
48
+ * file or a project mismatch. Returns the file's @world values for the host. */
49
+ declare function deserializeState(engine: Engine, json: string): PropertyBag | undefined;
50
+
51
+ interface PropertyInspectorOptions {
52
+ /** Mount point; defaults to document.body. */
53
+ container?: HTMLElement;
54
+ title?: string;
55
+ /** Value-refresh poll; 0 disables polling. */
56
+ pollMs?: number;
57
+ }
58
+ interface PropertyInspector {
59
+ el: HTMLElement;
60
+ refresh(): void;
61
+ destroy(): void;
62
+ }
63
+ /** Inject the shared panel stylesheet once. Exported so the bundle inspector
64
+ * (bundle-inspector.ts) renders in the same CSS grammar. */
65
+ declare function ensureInspectorStyle(): void;
66
+ /** One line per entry, `[turn]`-stamped where the event has a box context
67
+ * (write lines share the state logger's `path: from -> to` reading). */
68
+ declare function formatLogEntry(e: LogEntry | EngineLogEntry): string;
69
+ declare function createPropertyInspector(engine: Engine, flow: Flow, opts?: PropertyInspectorOptions): PropertyInspector;
70
+
71
+ interface BundleInspectorOptions {
72
+ /** Mount point; defaults to document.body. */
73
+ container?: HTMLElement;
74
+ title?: string;
75
+ /** Start the collapsible sections open (default true). */
76
+ open?: boolean;
77
+ }
78
+ interface BundleInspector {
79
+ el: HTMLElement;
80
+ /** The description this panel rendered (the API is the parity member). */
81
+ description: BundleDescription;
82
+ destroy(): void;
83
+ }
84
+ /** "name: type = default", plus enum/flags options where declared. */
85
+ declare function formatPropertySummary(p: PropertySummary): string;
86
+ /** The scope label a declaration block files under ("world", "box box",
87
+ * "tag docks (zone)"). */
88
+ declare function formatScopeLabel(scope: PropertyScopeSummary): string;
89
+ /** Render a read-only summary of a compiled bundle: what an integrator may
90
+ * call, with no session and no game running. */
91
+ declare function createBundleInspector(bundle: Bundle, opts?: BundleInspectorOptions): BundleInspector;
92
+
93
+ /** A minimal structural type for a WebSocket implementation (browsers and
94
+ * Node 22+ have a global one). */
95
+ interface LiveSocketLike {
96
+ readyState: number;
97
+ send(data: string): void;
98
+ close(): void;
99
+ addEventListener(type: "open" | "close" | "error", listener: () => void): void;
100
+ /** Incoming editor messages (the pushed bundle). Optional so a bare
101
+ * send-only socket still fits. */
102
+ addEventListener(type: "message", listener: (ev: {
103
+ data: unknown;
104
+ }) => void): void;
105
+ }
106
+ type LiveSocketCtor = new (url: string) => LiveSocketLike;
107
+ interface LiveLinkOptions {
108
+ /** The running bundle's build identity: pass `bundle.content.hash`. The
109
+ * editor compares it with its own compiled hash (in sync / stale). */
110
+ build: string;
111
+ /** Optional project name, shown in the editor's connect-chip tooltip. */
112
+ project?: string;
113
+ /** Editor WebSocket URL. Default `ws://127.0.0.1:4472`. */
114
+ url?: string;
115
+ /** A WebSocket constructor to use instead of the global one (tests with a
116
+ * fake socket, or a host without a global WebSocket). */
117
+ WebSocket?: LiveSocketCtor;
118
+ /** Live refresh: the editor pushed a freshly compiled bundle. `data` is
119
+ * the .storyletsc JSON; hand it (with your current Engine) to
120
+ * `applyLiveBundle`, `attach` the engine it returns, then call
121
+ * `link.setBuild(build)`. Never called with a malformed frame. */
122
+ onBundle?: (msg: {
123
+ build: string;
124
+ data: string;
125
+ }) => void;
126
+ }
127
+ interface LiveLink {
128
+ /** Start forwarding this ENGINE's trace: every flow's events, each frame
129
+ * naming the flow it came from, so the editor can follow one participant
130
+ * and switch. An earlier engine is detached first. Sends a board snapshot
131
+ * per open flow straight away, queued behind the hello if the socket is
132
+ * not open yet.
133
+ *
134
+ * Flows are discovered rather than declared: the link diffs `engine.flows()`
135
+ * whenever anything happens and emits `flowOpen` / `flowClose` itself. That
136
+ * is a deliberate departure from Patterplay, whose host calls `FlowOpened`
137
+ * by hand - it has no engine-level trace tap to hang the diff on and we do,
138
+ * so the host has nothing to remember and cannot get the editor's flow list
139
+ * wrong. The one cost: a flow that opens and then does nothing at all is not
140
+ * announced until the next event anywhere in the run. */
141
+ attach(engine: Engine): void;
142
+ /** Stop forwarding. A refresh replaces the engine, so attach the new one
143
+ * afterwards. */
144
+ detach(): void;
145
+ /** After applying a pushed bundle: report the build now running (re-hellos
146
+ * with the new build and a fresh board snapshot, so the editor's chip goes
147
+ * back to in sync and it stops re-pushing the same bundle). */
148
+ setBuild(build: string): void;
149
+ /** Close the link; every later call is a no-op. */
150
+ close(): void;
151
+ }
152
+ /** One game-to-editor frame, as the client serialises it. Exported for the
153
+ * fixture test; hosts never build these by hand. */
154
+ type LiveFrame = {
155
+ t: "hello";
156
+ v: 2;
157
+ build: string;
158
+ project?: string;
159
+ boxes?: string[];
160
+ flows: string[];
161
+ } | {
162
+ t: "flowOpen";
163
+ flow: string;
164
+ } | {
165
+ t: "flowClose";
166
+ flow: string;
167
+ } | {
168
+ t: "trace";
169
+ flow: string;
170
+ event: TraceEvent;
171
+ } | {
172
+ t: "board";
173
+ flow: string;
174
+ hands: Record<string, string[]>;
175
+ turns: Record<string, number>;
176
+ };
177
+ /** The cheap snapshot: hands by gameId holding card gameIds in dealt order,
178
+ * and every box's clock by gameId. */
179
+ declare function boardFrame(flow: Flow): Extract<LiveFrame, {
180
+ t: "board";
181
+ }>;
182
+ /**
183
+ * Open a Live Link to Storyletter. Returns a handle whose calls are no-ops once
184
+ * the editor disconnects or if it was never listening: safe to leave wired into
185
+ * a shipping build behind a flag.
186
+ */
187
+ declare function createLiveLink(opts: LiveLinkOptions): LiveLink;
188
+
189
+ type LiveBundleResult =
190
+ /** The new engine, carrying the old one's run (all flows), and the bundle
191
+ * it runs. */
192
+ {
193
+ ok: true;
194
+ engine: Engine;
195
+ bundle: Bundle;
196
+ }
197
+ /** Nothing changed: keep the engine you have. */
198
+ | {
199
+ ok: false;
200
+ error: string;
201
+ };
202
+ /**
203
+ * Apply a bundle the editor pushed over the Live Link: `new Engine(parsed,
204
+ * opts)` then `loadGame(engine.saveGame())`, returning the new engine. Never
205
+ * throws; a failure (unparseable JSON, a bundle the runtime rejects, a
206
+ * different project) comes back as `{ ok: false, error }` and the old engine
207
+ * is untouched.
208
+ *
209
+ * `opts` are the options the old engine was created with. An engine does
210
+ * not expose its seed, and it does not matter here: the save envelope carries
211
+ * each flow's PRNG state, so `loadGame` resumes the draw sequences exactly
212
+ * where they were and `seed` only shapes fresh flows. `log` does matter (the
213
+ * retained log is per flow), and so does `world` (the host's binding does not
214
+ * ride the envelope), so pass them if you had them.
215
+ */
216
+ declare function applyLiveBundle(engine: Engine, bundleJson: string, opts?: EngineOptions): LiveBundleResult;
217
+
218
+ type ScalarValue = boolean | number | string | string[];
219
+
220
+ /**
221
+ * A scope backed by a host resolver rather than a static bag - the basis for
222
+ * *foreign* scopes (e.g. `@game` / `@world`) whose values live in a host or
223
+ * another engine and are read (and optionally written) at runtime. The
224
+ * evaluator treats a missing property the same as a bag does (the scope's
225
+ * missing-policy decides false-vs-throw). A scope entirely absent from the
226
+ * EvalContext still resolves to false regardless.
227
+ */
228
+ interface ScopeResolver {
229
+ /** Read a property's value, or undefined if the scope does not have it. */
230
+ get(name: string): ScalarValue | undefined;
231
+ /** Write a property (omit for a read-only scope). The core never calls this;
232
+ * it is for host/runtime effect application (e.g. a state container's `set`). */
233
+ set?(name: string, value: ScalarValue): void;
234
+ }
235
+
236
+ interface WorldContainer {
237
+ /** Pass as `new Engine(bundle, { world: container.resolver })`. */
238
+ resolver: ScopeResolver;
239
+ /** The kernel bag itself (subscribe, audit, rows live there) - mount it
240
+ * into a state logger or examiner beside the engine's own bags. */
241
+ bag: PropertyBag$1;
242
+ /** The current values, for saving beside the engine's envelope. */
243
+ values(): PropertyBag;
244
+ /** Restore saved values over fresh defaults: orphaned keys drop, new
245
+ * declarations keep their defaults - the same drift rule as loadGame. */
246
+ load(values: PropertyBag): void;
247
+ }
248
+ /** A world container seeded from the bundle's @world declarations. */
249
+ declare function createWorldContainer(bundle: Bundle): WorldContainer;
250
+
251
+ export { type BundleInspector, type BundleInspectorOptions, type LiveBundleResult, type LiveFrame, type LiveLink, type LiveLinkOptions, type LiveSocketLike, type PropertyInspector, type PropertyInspectorOptions, type WorldContainer, applyLiveBundle, boardFrame, createBundleInspector, createLiveLink, createPropertyInspector, createStateLogger, createWorldContainer, deserializeState, ensureInspectorStyle, formatLogEntry, formatPropertySummary, formatScopeLabel, loadState, saveState, serializeState, snapshotState };