@storylet-studio/play-helpers 0.14.0 → 0.15.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/README.md +3 -2
- package/dist/index.cjs +21 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -48
- package/dist/index.d.ts +57 -48
- package/dist/index.js +22 -8
- package/dist/index.js.map +1 -1
- package/dist/storyletengine.min.js +4 -4
- package/dist/storyletengine.min.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Engine, Flow, LogEntry, EngineLogEntry, BundleDescription, PropertySummary, PropertyScopeSummary, TraceEvent, EngineOptions } from '@storylet-studio/runtime';
|
|
2
|
-
import { StateLoggerOptions, StateLogger, StateSnapshot, PropertyBag
|
|
2
|
+
import { StateLoggerOptions, StateLogger, StateSnapshot, PropertyBag } from '@wildwinter/scoperegistry';
|
|
3
3
|
export { StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions, StateSnapshot, createStateLogger as createKernelStateLogger, diffState } from '@wildwinter/scoperegistry';
|
|
4
|
-
import {
|
|
4
|
+
import { PropertyBag as PropertyBag$1, Bundle, LoadReport, SaveFile } from '@storylet-studio/model';
|
|
5
5
|
import { ScopeResolver } from '@wildwinter/expr';
|
|
6
6
|
|
|
7
7
|
/** The full flattened snapshot of ONE FLOW's view - the shared partitions
|
|
@@ -24,9 +24,48 @@ declare function snapshotState(engine: Engine, flow: Flow): StateSnapshot;
|
|
|
24
24
|
* world container's bag through createKernelStateLogger itself. */
|
|
25
25
|
declare function createStateLogger(engine: Engine, flow: Flow, opts?: StateLoggerOptions): StateLogger;
|
|
26
26
|
|
|
27
|
+
interface WorldContainer {
|
|
28
|
+
/** Pass as `new Engine(bundle, { hostScopes: { world: container.resolver } })`. */
|
|
29
|
+
resolver: ScopeResolver;
|
|
30
|
+
/** The kernel bag itself (subscribe, audit, rows live there) - mount it
|
|
31
|
+
* into a state logger or examiner beside the engine's own bags. Writing it
|
|
32
|
+
* DIRECTLY is writing the kernel, so a `writable: false` declaration asks
|
|
33
|
+
* the kernel's question: pass `{ host: true }` to say the game is speaking
|
|
34
|
+
* (`bag.set(name, value, { host: true })`). Through `resolver` or an
|
|
35
|
+
* engine's setProperty that is already answered. */
|
|
36
|
+
bag: PropertyBag;
|
|
37
|
+
/** The current values, for saving beside the engine's envelope. */
|
|
38
|
+
values(): PropertyBag$1;
|
|
39
|
+
/** Restore saved values over fresh defaults: orphaned keys drop, new
|
|
40
|
+
* declarations keep their defaults - the same drift rule as loadGame. */
|
|
41
|
+
load(values: PropertyBag$1): void;
|
|
42
|
+
}
|
|
43
|
+
/** A container seeded from one host scope's declarations in the bundle:
|
|
44
|
+
* `@world`'s unless `token` names another (`"player"`).
|
|
45
|
+
*
|
|
46
|
+
* The container is the GAME's state, so it WRITES - even a declaration
|
|
47
|
+
* carrying `writable: false`. That flag is the STORY's promise not to write
|
|
48
|
+
* the value (Reboot.md 10), and the engine keeps it where the story writes: an
|
|
49
|
+
* outcome is refused against the engine's read-only table before it ever
|
|
50
|
+
* reaches this resolver. Enforcing it here as well refused the HOST too - the
|
|
51
|
+
* clock the game must move, the harness driving the value it is testing
|
|
52
|
+
* against - which is the opposite of what the flag says.
|
|
53
|
+
*
|
|
54
|
+
* So the declarations are seeded AS DECLARED, which is what an examiner over
|
|
55
|
+
* this container should read, and the writes go through as HOST writes
|
|
56
|
+
* (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the
|
|
57
|
+
* engine's own doorway and passes the flag too: the engine has already sorted
|
|
58
|
+
* story from host by then - a story write was refused earlier, a host write is
|
|
59
|
+
* the only kind that arrives - and a resolver takes a name and a value with no
|
|
60
|
+
* room to say which. A game wanting a rule of its own binds its own resolver
|
|
61
|
+
* rather than this one; the ports' container (Unreal's UStoryletWorld) draws
|
|
62
|
+
* the same line, with HostSet never refused and StorySet asking the game's own
|
|
63
|
+
* read-only list. */
|
|
64
|
+
declare function createWorldContainer(bundle: Bundle, token?: string): WorldContainer;
|
|
65
|
+
|
|
27
66
|
/** The current engine state (and the host's @world values, if given) as
|
|
28
67
|
* pretty-printed .storyletsave JSON. */
|
|
29
|
-
declare function serializeState(engine: Engine, world?: PropertyBag): string;
|
|
68
|
+
declare function serializeState(engine: Engine, world?: PropertyBag$1): string;
|
|
30
69
|
/**
|
|
31
70
|
* Capture the whole engine (and the host's @world values, if it keeps any) as
|
|
32
71
|
* the tagged save-file OBJECT.
|
|
@@ -40,7 +79,7 @@ declare function serializeState(engine: Engine, world?: PropertyBag): string;
|
|
|
40
79
|
* things across the four Storylets runtimes, and neither matched the family.
|
|
41
80
|
* Godot and Unreal already had Patter's shape; these two were brought to it.
|
|
42
81
|
*/
|
|
43
|
-
declare function saveState(engine: Engine, world?: PropertyBag): SaveFile;
|
|
82
|
+
declare function saveState(engine: Engine, world?: PropertyBag$1): SaveFile;
|
|
44
83
|
/** What a load may be told to do besides restoring. */
|
|
45
84
|
interface LoadStateOptions {
|
|
46
85
|
/** Handed the engine's LoadReport for this file as the load happens: what
|
|
@@ -49,6 +88,13 @@ interface LoadStateOptions {
|
|
|
49
88
|
* `engine.previewLoad` return. A callback rather than a return value
|
|
50
89
|
* because these functions already return the file's @world values. */
|
|
51
90
|
onReport?: (report: LoadReport) => void;
|
|
91
|
+
/** The host's @world container (`createWorldContainer`, or anything with
|
|
92
|
+
* the same `values` and `load`). Given, the file's @world values go into it
|
|
93
|
+
* BEFORE the engine loads, so the load's check of each held card reads the
|
|
94
|
+
* saved world rather than the one the game had; a refused load puts the
|
|
95
|
+
* container back as it was. Without it the values are only returned, and
|
|
96
|
+
* the game must apply them before loading for the same check. */
|
|
97
|
+
world?: Pick<WorldContainer, "values" | "load">;
|
|
52
98
|
}
|
|
53
99
|
/** Restore a {@link saveState} file into an engine. EVERY FLOW IS REBUILT, so
|
|
54
100
|
* the Flow handles you held before are inert: re-take them with
|
|
@@ -57,13 +103,14 @@ interface LoadStateOptions {
|
|
|
57
103
|
* the failure lands later, as `play()` refusing a card as "not dealt". (The
|
|
58
104
|
* engine's `onReplacedFlow` hook reports exactly this.) Throws on a foreign or malformed
|
|
59
105
|
* file, and the runtime's own project check still applies. Returns the file's
|
|
60
|
-
* @world values, if any
|
|
106
|
+
* @world values, if any. Pass the host's container as `opts.world` and they
|
|
107
|
+
* are applied before the load (see LoadStateOptions.world); the engine itself
|
|
61
108
|
* never touches them. */
|
|
62
|
-
declare function loadState(engine: Engine, file: SaveFile, opts?: LoadStateOptions): PropertyBag | undefined;
|
|
109
|
+
declare function loadState(engine: Engine, file: SaveFile, opts?: LoadStateOptions): PropertyBag$1 | undefined;
|
|
63
110
|
/** Parse + restore a {@link serializeState} string: the TEXT twin of
|
|
64
111
|
* loadState, as Patterplay pairs them. Throws on malformed JSON, a foreign
|
|
65
112
|
* file or a project mismatch. Returns the file's @world values for the host. */
|
|
66
|
-
declare function deserializeState(engine: Engine, json: string, opts?: LoadStateOptions): PropertyBag | undefined;
|
|
113
|
+
declare function deserializeState(engine: Engine, json: string, opts?: LoadStateOptions): PropertyBag$1 | undefined;
|
|
67
114
|
|
|
68
115
|
interface PropertyInspectorOptions {
|
|
69
116
|
/** Mount point; defaults to document.body. */
|
|
@@ -103,8 +150,8 @@ interface BundleInspector {
|
|
|
103
150
|
* (design/engine-server.md 4.2). Nothing is added for the ordinary
|
|
104
151
|
* run-scoped property: that is what a property is. */
|
|
105
152
|
declare function formatPropertySummary(p: PropertySummary): string;
|
|
106
|
-
/** The scope label a declaration block files under ("world", "box box",
|
|
107
|
-
* "tag docks (zone)"). */
|
|
153
|
+
/** The scope label a declaration block files under ("world", "player", "box box",
|
|
154
|
+
* "tag docks (zone)"). A host scope is named by its token, as @world is. */
|
|
108
155
|
declare function formatScopeLabel(scope: PropertyScopeSummary): string;
|
|
109
156
|
/** Render a read-only summary of a compiled bundle: what an integrator may
|
|
110
157
|
* call, with no session and no game running. */
|
|
@@ -240,47 +287,9 @@ type LiveBundleResult =
|
|
|
240
287
|
* the game's registry the old engine hands its keys to the replacement, which a
|
|
241
288
|
* plain save and load into a second engine cannot do (the two would clash).
|
|
242
289
|
*
|
|
243
|
-
* The engine remembers the options it was built with (seed, log,
|
|
290
|
+
* The engine remembers the options it was built with (seed, log, hostScopes,
|
|
244
291
|
* registry), so `opts` is only for overriding one of them for the replacement.
|
|
245
292
|
*/
|
|
246
293
|
declare function applyLiveBundle(engine: Engine, bundleJson: string, opts?: EngineOptions): LiveBundleResult;
|
|
247
294
|
|
|
248
|
-
interface WorldContainer {
|
|
249
|
-
/** Pass as `new Engine(bundle, { world: container.resolver })`. */
|
|
250
|
-
resolver: ScopeResolver;
|
|
251
|
-
/** The kernel bag itself (subscribe, audit, rows live there) - mount it
|
|
252
|
-
* into a state logger or examiner beside the engine's own bags. Writing it
|
|
253
|
-
* DIRECTLY is writing the kernel, so a `writable: false` declaration asks
|
|
254
|
-
* the kernel's question: pass `{ host: true }` to say the game is speaking
|
|
255
|
-
* (`bag.set(name, value, { host: true })`). Through `resolver` or an
|
|
256
|
-
* engine's setProperty that is already answered. */
|
|
257
|
-
bag: PropertyBag$1;
|
|
258
|
-
/** The current values, for saving beside the engine's envelope. */
|
|
259
|
-
values(): PropertyBag;
|
|
260
|
-
/** Restore saved values over fresh defaults: orphaned keys drop, new
|
|
261
|
-
* declarations keep their defaults - the same drift rule as loadGame. */
|
|
262
|
-
load(values: PropertyBag): void;
|
|
263
|
-
}
|
|
264
|
-
/** A world container seeded from the bundle's @world declarations.
|
|
265
|
-
*
|
|
266
|
-
* The container is the GAME's state, so it WRITES - even a declaration
|
|
267
|
-
* carrying `writable: false`. That flag is the STORY's promise not to write
|
|
268
|
-
* the value (Reboot.md 10), and the engine keeps it where the story writes: an
|
|
269
|
-
* outcome is refused against the engine's read-only table before it ever
|
|
270
|
-
* reaches this resolver. Enforcing it here as well refused the HOST too - the
|
|
271
|
-
* clock the game must move, the harness driving the value it is testing
|
|
272
|
-
* against - which is the opposite of what the flag says.
|
|
273
|
-
*
|
|
274
|
-
* So the declarations are seeded AS DECLARED, which is what an examiner over
|
|
275
|
-
* this container should read, and the writes go through as HOST writes
|
|
276
|
-
* (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the
|
|
277
|
-
* engine's own doorway and passes the flag too: the engine has already sorted
|
|
278
|
-
* story from host by then - a story write was refused earlier, a host write is
|
|
279
|
-
* the only kind that arrives - and a resolver takes a name and a value with no
|
|
280
|
-
* room to say which. A game wanting a rule of its own binds its own resolver
|
|
281
|
-
* rather than this one; the ports' container (Unreal's UStoryletWorld) draws
|
|
282
|
-
* the same line, with HostSet never refused and StorySet asking the game's own
|
|
283
|
-
* read-only list. */
|
|
284
|
-
declare function createWorldContainer(bundle: Bundle): WorldContainer;
|
|
285
|
-
|
|
286
295
|
export { type BundleInspector, type BundleInspectorOptions, type LiveBundleResult, type LiveFrame, type LiveLink, type LiveLinkOptions, type LiveLinkState, type LiveSocketLike, type LoadStateOptions, type PropertyInspector, type PropertyInspectorOptions, type WorldContainer, applyLiveBundle, boardFrame, createBundleInspector, createLiveLink, createPropertyInspector, createStateLogger, createWorldContainer, deserializeState, ensureInspectorStyle, formatLogEntry, formatPropertySummary, formatScopeLabel, loadState, saveState, serializeState, snapshotState };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Engine, Flow, LogEntry, EngineLogEntry, BundleDescription, PropertySummary, PropertyScopeSummary, TraceEvent, EngineOptions } from '@storylet-studio/runtime';
|
|
2
|
-
import { StateLoggerOptions, StateLogger, StateSnapshot, PropertyBag
|
|
2
|
+
import { StateLoggerOptions, StateLogger, StateSnapshot, PropertyBag } from '@wildwinter/scoperegistry';
|
|
3
3
|
export { StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions, StateSnapshot, createStateLogger as createKernelStateLogger, diffState } from '@wildwinter/scoperegistry';
|
|
4
|
-
import {
|
|
4
|
+
import { PropertyBag as PropertyBag$1, Bundle, LoadReport, SaveFile } from '@storylet-studio/model';
|
|
5
5
|
import { ScopeResolver } from '@wildwinter/expr';
|
|
6
6
|
|
|
7
7
|
/** The full flattened snapshot of ONE FLOW's view - the shared partitions
|
|
@@ -24,9 +24,48 @@ declare function snapshotState(engine: Engine, flow: Flow): StateSnapshot;
|
|
|
24
24
|
* world container's bag through createKernelStateLogger itself. */
|
|
25
25
|
declare function createStateLogger(engine: Engine, flow: Flow, opts?: StateLoggerOptions): StateLogger;
|
|
26
26
|
|
|
27
|
+
interface WorldContainer {
|
|
28
|
+
/** Pass as `new Engine(bundle, { hostScopes: { world: container.resolver } })`. */
|
|
29
|
+
resolver: ScopeResolver;
|
|
30
|
+
/** The kernel bag itself (subscribe, audit, rows live there) - mount it
|
|
31
|
+
* into a state logger or examiner beside the engine's own bags. Writing it
|
|
32
|
+
* DIRECTLY is writing the kernel, so a `writable: false` declaration asks
|
|
33
|
+
* the kernel's question: pass `{ host: true }` to say the game is speaking
|
|
34
|
+
* (`bag.set(name, value, { host: true })`). Through `resolver` or an
|
|
35
|
+
* engine's setProperty that is already answered. */
|
|
36
|
+
bag: PropertyBag;
|
|
37
|
+
/** The current values, for saving beside the engine's envelope. */
|
|
38
|
+
values(): PropertyBag$1;
|
|
39
|
+
/** Restore saved values over fresh defaults: orphaned keys drop, new
|
|
40
|
+
* declarations keep their defaults - the same drift rule as loadGame. */
|
|
41
|
+
load(values: PropertyBag$1): void;
|
|
42
|
+
}
|
|
43
|
+
/** A container seeded from one host scope's declarations in the bundle:
|
|
44
|
+
* `@world`'s unless `token` names another (`"player"`).
|
|
45
|
+
*
|
|
46
|
+
* The container is the GAME's state, so it WRITES - even a declaration
|
|
47
|
+
* carrying `writable: false`. That flag is the STORY's promise not to write
|
|
48
|
+
* the value (Reboot.md 10), and the engine keeps it where the story writes: an
|
|
49
|
+
* outcome is refused against the engine's read-only table before it ever
|
|
50
|
+
* reaches this resolver. Enforcing it here as well refused the HOST too - the
|
|
51
|
+
* clock the game must move, the harness driving the value it is testing
|
|
52
|
+
* against - which is the opposite of what the flag says.
|
|
53
|
+
*
|
|
54
|
+
* So the declarations are seeded AS DECLARED, which is what an examiner over
|
|
55
|
+
* this container should read, and the writes go through as HOST writes
|
|
56
|
+
* (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the
|
|
57
|
+
* engine's own doorway and passes the flag too: the engine has already sorted
|
|
58
|
+
* story from host by then - a story write was refused earlier, a host write is
|
|
59
|
+
* the only kind that arrives - and a resolver takes a name and a value with no
|
|
60
|
+
* room to say which. A game wanting a rule of its own binds its own resolver
|
|
61
|
+
* rather than this one; the ports' container (Unreal's UStoryletWorld) draws
|
|
62
|
+
* the same line, with HostSet never refused and StorySet asking the game's own
|
|
63
|
+
* read-only list. */
|
|
64
|
+
declare function createWorldContainer(bundle: Bundle, token?: string): WorldContainer;
|
|
65
|
+
|
|
27
66
|
/** The current engine state (and the host's @world values, if given) as
|
|
28
67
|
* pretty-printed .storyletsave JSON. */
|
|
29
|
-
declare function serializeState(engine: Engine, world?: PropertyBag): string;
|
|
68
|
+
declare function serializeState(engine: Engine, world?: PropertyBag$1): string;
|
|
30
69
|
/**
|
|
31
70
|
* Capture the whole engine (and the host's @world values, if it keeps any) as
|
|
32
71
|
* the tagged save-file OBJECT.
|
|
@@ -40,7 +79,7 @@ declare function serializeState(engine: Engine, world?: PropertyBag): string;
|
|
|
40
79
|
* things across the four Storylets runtimes, and neither matched the family.
|
|
41
80
|
* Godot and Unreal already had Patter's shape; these two were brought to it.
|
|
42
81
|
*/
|
|
43
|
-
declare function saveState(engine: Engine, world?: PropertyBag): SaveFile;
|
|
82
|
+
declare function saveState(engine: Engine, world?: PropertyBag$1): SaveFile;
|
|
44
83
|
/** What a load may be told to do besides restoring. */
|
|
45
84
|
interface LoadStateOptions {
|
|
46
85
|
/** Handed the engine's LoadReport for this file as the load happens: what
|
|
@@ -49,6 +88,13 @@ interface LoadStateOptions {
|
|
|
49
88
|
* `engine.previewLoad` return. A callback rather than a return value
|
|
50
89
|
* because these functions already return the file's @world values. */
|
|
51
90
|
onReport?: (report: LoadReport) => void;
|
|
91
|
+
/** The host's @world container (`createWorldContainer`, or anything with
|
|
92
|
+
* the same `values` and `load`). Given, the file's @world values go into it
|
|
93
|
+
* BEFORE the engine loads, so the load's check of each held card reads the
|
|
94
|
+
* saved world rather than the one the game had; a refused load puts the
|
|
95
|
+
* container back as it was. Without it the values are only returned, and
|
|
96
|
+
* the game must apply them before loading for the same check. */
|
|
97
|
+
world?: Pick<WorldContainer, "values" | "load">;
|
|
52
98
|
}
|
|
53
99
|
/** Restore a {@link saveState} file into an engine. EVERY FLOW IS REBUILT, so
|
|
54
100
|
* the Flow handles you held before are inert: re-take them with
|
|
@@ -57,13 +103,14 @@ interface LoadStateOptions {
|
|
|
57
103
|
* the failure lands later, as `play()` refusing a card as "not dealt". (The
|
|
58
104
|
* engine's `onReplacedFlow` hook reports exactly this.) Throws on a foreign or malformed
|
|
59
105
|
* file, and the runtime's own project check still applies. Returns the file's
|
|
60
|
-
* @world values, if any
|
|
106
|
+
* @world values, if any. Pass the host's container as `opts.world` and they
|
|
107
|
+
* are applied before the load (see LoadStateOptions.world); the engine itself
|
|
61
108
|
* never touches them. */
|
|
62
|
-
declare function loadState(engine: Engine, file: SaveFile, opts?: LoadStateOptions): PropertyBag | undefined;
|
|
109
|
+
declare function loadState(engine: Engine, file: SaveFile, opts?: LoadStateOptions): PropertyBag$1 | undefined;
|
|
63
110
|
/** Parse + restore a {@link serializeState} string: the TEXT twin of
|
|
64
111
|
* loadState, as Patterplay pairs them. Throws on malformed JSON, a foreign
|
|
65
112
|
* file or a project mismatch. Returns the file's @world values for the host. */
|
|
66
|
-
declare function deserializeState(engine: Engine, json: string, opts?: LoadStateOptions): PropertyBag | undefined;
|
|
113
|
+
declare function deserializeState(engine: Engine, json: string, opts?: LoadStateOptions): PropertyBag$1 | undefined;
|
|
67
114
|
|
|
68
115
|
interface PropertyInspectorOptions {
|
|
69
116
|
/** Mount point; defaults to document.body. */
|
|
@@ -103,8 +150,8 @@ interface BundleInspector {
|
|
|
103
150
|
* (design/engine-server.md 4.2). Nothing is added for the ordinary
|
|
104
151
|
* run-scoped property: that is what a property is. */
|
|
105
152
|
declare function formatPropertySummary(p: PropertySummary): string;
|
|
106
|
-
/** The scope label a declaration block files under ("world", "box box",
|
|
107
|
-
* "tag docks (zone)"). */
|
|
153
|
+
/** The scope label a declaration block files under ("world", "player", "box box",
|
|
154
|
+
* "tag docks (zone)"). A host scope is named by its token, as @world is. */
|
|
108
155
|
declare function formatScopeLabel(scope: PropertyScopeSummary): string;
|
|
109
156
|
/** Render a read-only summary of a compiled bundle: what an integrator may
|
|
110
157
|
* call, with no session and no game running. */
|
|
@@ -240,47 +287,9 @@ type LiveBundleResult =
|
|
|
240
287
|
* the game's registry the old engine hands its keys to the replacement, which a
|
|
241
288
|
* plain save and load into a second engine cannot do (the two would clash).
|
|
242
289
|
*
|
|
243
|
-
* The engine remembers the options it was built with (seed, log,
|
|
290
|
+
* The engine remembers the options it was built with (seed, log, hostScopes,
|
|
244
291
|
* registry), so `opts` is only for overriding one of them for the replacement.
|
|
245
292
|
*/
|
|
246
293
|
declare function applyLiveBundle(engine: Engine, bundleJson: string, opts?: EngineOptions): LiveBundleResult;
|
|
247
294
|
|
|
248
|
-
interface WorldContainer {
|
|
249
|
-
/** Pass as `new Engine(bundle, { world: container.resolver })`. */
|
|
250
|
-
resolver: ScopeResolver;
|
|
251
|
-
/** The kernel bag itself (subscribe, audit, rows live there) - mount it
|
|
252
|
-
* into a state logger or examiner beside the engine's own bags. Writing it
|
|
253
|
-
* DIRECTLY is writing the kernel, so a `writable: false` declaration asks
|
|
254
|
-
* the kernel's question: pass `{ host: true }` to say the game is speaking
|
|
255
|
-
* (`bag.set(name, value, { host: true })`). Through `resolver` or an
|
|
256
|
-
* engine's setProperty that is already answered. */
|
|
257
|
-
bag: PropertyBag$1;
|
|
258
|
-
/** The current values, for saving beside the engine's envelope. */
|
|
259
|
-
values(): PropertyBag;
|
|
260
|
-
/** Restore saved values over fresh defaults: orphaned keys drop, new
|
|
261
|
-
* declarations keep their defaults - the same drift rule as loadGame. */
|
|
262
|
-
load(values: PropertyBag): void;
|
|
263
|
-
}
|
|
264
|
-
/** A world container seeded from the bundle's @world declarations.
|
|
265
|
-
*
|
|
266
|
-
* The container is the GAME's state, so it WRITES - even a declaration
|
|
267
|
-
* carrying `writable: false`. That flag is the STORY's promise not to write
|
|
268
|
-
* the value (Reboot.md 10), and the engine keeps it where the story writes: an
|
|
269
|
-
* outcome is refused against the engine's read-only table before it ever
|
|
270
|
-
* reaches this resolver. Enforcing it here as well refused the HOST too - the
|
|
271
|
-
* clock the game must move, the harness driving the value it is testing
|
|
272
|
-
* against - which is the opposite of what the flag says.
|
|
273
|
-
*
|
|
274
|
-
* So the declarations are seeded AS DECLARED, which is what an examiner over
|
|
275
|
-
* this container should read, and the writes go through as HOST writes
|
|
276
|
-
* (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the
|
|
277
|
-
* engine's own doorway and passes the flag too: the engine has already sorted
|
|
278
|
-
* story from host by then - a story write was refused earlier, a host write is
|
|
279
|
-
* the only kind that arrives - and a resolver takes a name and a value with no
|
|
280
|
-
* room to say which. A game wanting a rule of its own binds its own resolver
|
|
281
|
-
* rather than this one; the ports' container (Unreal's UStoryletWorld) draws
|
|
282
|
-
* the same line, with HostSet never refused and StorySet asking the game's own
|
|
283
|
-
* read-only list. */
|
|
284
|
-
declare function createWorldContainer(bundle: Bundle): WorldContainer;
|
|
285
|
-
|
|
286
295
|
export { type BundleInspector, type BundleInspectorOptions, type LiveBundleResult, type LiveFrame, type LiveLink, type LiveLinkOptions, type LiveLinkState, type LiveSocketLike, type LoadStateOptions, type PropertyInspector, type PropertyInspectorOptions, type WorldContainer, applyLiveBundle, boardFrame, createBundleInspector, createLiveLink, createPropertyInspector, createStateLogger, createWorldContainer, deserializeState, ensureInspectorStyle, formatLogEntry, formatPropertySummary, formatScopeLabel, loadState, saveState, serializeState, snapshotState };
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ function createStateLogger(engine, flow, opts = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// src/save.ts
|
|
40
|
-
import { SAVEFILE_SCHEMA, SAVE_SCHEMA, SAVE_SCHEMA_V1 } from "@storylet-studio/model";
|
|
40
|
+
import { SAVEFILE_SCHEMA, SAVE_SCHEMA, SAVE_SCHEMA_V1, SAVE_SCHEMA_V2 } from "@storylet-studio/model";
|
|
41
41
|
function serializeState(engine, world) {
|
|
42
42
|
return JSON.stringify(saveState(engine, world), null, 2);
|
|
43
43
|
}
|
|
@@ -49,10 +49,19 @@ function saveState(engine, world) {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
function loadState(engine, file, opts = {}) {
|
|
52
|
-
if (!file || typeof file !== "object" || file.schema !== SAVEFILE_SCHEMA || file.engine?.schema !== SAVE_SCHEMA && file.engine?.schema !== SAVE_SCHEMA_V1) {
|
|
52
|
+
if (!file || typeof file !== "object" || file.schema !== SAVEFILE_SCHEMA || file.engine?.schema !== SAVE_SCHEMA && file.engine?.schema !== SAVE_SCHEMA_V2 && file.engine?.schema !== SAVE_SCHEMA_V1) {
|
|
53
53
|
throw new Error(`not a storylets save (expected schema "${SAVEFILE_SCHEMA}")`);
|
|
54
54
|
}
|
|
55
|
-
const
|
|
55
|
+
const container = opts.world;
|
|
56
|
+
const before = container !== void 0 ? structuredClone(container.values()) : void 0;
|
|
57
|
+
if (container !== void 0 && file.world !== void 0) container.load(file.world);
|
|
58
|
+
let report;
|
|
59
|
+
try {
|
|
60
|
+
report = engine.loadGame(file.engine);
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (container !== void 0 && before !== void 0) container.load(before);
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
56
65
|
opts.onReport?.(report);
|
|
57
66
|
return file.world;
|
|
58
67
|
}
|
|
@@ -122,7 +131,7 @@ var LOG_KIND_LABELS = {
|
|
|
122
131
|
turns: "Turns",
|
|
123
132
|
diagnostic: "Diag"
|
|
124
133
|
};
|
|
125
|
-
var logKindOf = (e) => e.type === "peek" ? "deal" : e.type;
|
|
134
|
+
var logKindOf = (e) => e.type === "peek" ? "deal" : e.type === "discard" ? "play" : e.type;
|
|
126
135
|
var showVal = (v) => v === void 0 ? "<unset>" : JSON.stringify(v);
|
|
127
136
|
function formatLogEntry(e) {
|
|
128
137
|
return formatLogBody(e, "flow" in e && e.flow ? `${e.flow} ` : "");
|
|
@@ -148,6 +157,9 @@ function formatLogBody(e, flow) {
|
|
|
148
157
|
return `${stamp}write ${e.path}: ${showVal(e.prev)} -> ${showVal(e.value)}`;
|
|
149
158
|
case "turns":
|
|
150
159
|
return `${stamp}turns ${e.box} -> ${e.turn}`;
|
|
160
|
+
// A card the game took out of its hand unplayed: filed with the plays.
|
|
161
|
+
case "discard":
|
|
162
|
+
return `${stamp}discard ${e.card} from ${e.hand}`;
|
|
151
163
|
default:
|
|
152
164
|
return `${stamp}diagnostic ${e.where}: ${e.message}`;
|
|
153
165
|
}
|
|
@@ -496,6 +508,7 @@ function formatPropertySummary(p) {
|
|
|
496
508
|
}
|
|
497
509
|
function formatScopeLabel(scope) {
|
|
498
510
|
if (scope.scope === "world" || scope.scope === "story") return scope.scope;
|
|
511
|
+
if (scope.scope === "host") return scope.owner;
|
|
499
512
|
const group = scope.group !== void 0 ? ` (${scope.group})` : "";
|
|
500
513
|
return `${scope.scope} ${scope.owner}${group}`;
|
|
501
514
|
}
|
|
@@ -587,7 +600,7 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
587
600
|
line(countsBody, `boxes ${totals.boxes} - decks ${totals.decks} - cards ${totals.cards}`);
|
|
588
601
|
line(countsBody, `hands ${totals.hands} - templates ${totals.templates} - tag groups ${totals.tagGroups}`);
|
|
589
602
|
for (const box of description.boxes) {
|
|
590
|
-
line(countsBody, `${box.gameId}: decks ${box.counts.decks}, cards ${box.counts.cards}, hands ${box.counts.hands}, templates ${box.counts.templates}, tag groups ${box.counts.tagGroups}, ranking.specificity ${box.ranking.specificity}` + (box.turn !== void 0 ? `, turn = ${box.turn.seconds}s` : "") + (box.durableCards !== void 0 ? `, durable cards ${box.durableCards}` : ""));
|
|
603
|
+
line(countsBody, `${box.gameId}: decks ${box.counts.decks}, cards ${box.counts.cards}, hands ${box.counts.hands}, templates ${box.counts.templates}, tag groups ${box.counts.tagGroups}, ranking.specificity ${box.ranking.specificity}` + (box.turn === void 0 ? "" : box.turn.seconds !== void 0 ? `, turn = ${box.turn.seconds}s` : ", turn moved by the game") + (box.durableCards !== void 0 ? `, durable cards ${box.durableCards}` : ""));
|
|
591
604
|
}
|
|
592
605
|
(opts.container ?? document.body).append(el);
|
|
593
606
|
return {
|
|
@@ -603,7 +616,7 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
603
616
|
var OPEN = 1;
|
|
604
617
|
var QUEUE_CAP = 512;
|
|
605
618
|
var DEFAULT_URL = "ws://127.0.0.1:4472";
|
|
606
|
-
var BOARD_EVENTS = /* @__PURE__ */ new Set(["deal", "play", "evict", "turns"]);
|
|
619
|
+
var BOARD_EVENTS = /* @__PURE__ */ new Set(["deal", "play", "discard", "evict", "turns"]);
|
|
607
620
|
function boardFrame(flow) {
|
|
608
621
|
const id = flow.id;
|
|
609
622
|
const hands = {};
|
|
@@ -794,8 +807,9 @@ function applyLiveBundle(engine, bundleJson, opts = {}) {
|
|
|
794
807
|
|
|
795
808
|
// src/world.ts
|
|
796
809
|
import { PropertyBag as StateBag } from "@wildwinter/scoperegistry";
|
|
797
|
-
|
|
798
|
-
|
|
810
|
+
import { hostScopeDecls } from "@storylet-studio/model";
|
|
811
|
+
function createWorldContainer(bundle, token = "world") {
|
|
812
|
+
const bag = new StateBag(hostScopeDecls(bundle, token), { normalise: (n) => n });
|
|
799
813
|
return {
|
|
800
814
|
resolver: {
|
|
801
815
|
get: (n) => bag.get(n),
|