@storylet-studio/play-helpers 0.4.1 → 0.5.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 +18 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -7
- package/dist/index.d.ts +40 -7
- package/dist/index.js +18 -18
- 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 +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -4,10 +4,17 @@ export { StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions, State
|
|
|
4
4
|
import { PropertyBag, SaveFile, Bundle } from '@storylet-studio/model';
|
|
5
5
|
|
|
6
6
|
/** The full flattened snapshot of ONE FLOW's view - the shared partitions
|
|
7
|
-
* plus that flow's own -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* plus that flow's own - plus its turns / cooldowns / board. @world is not
|
|
8
|
+
* here for the same reason it is not in a save envelope: the host owns that
|
|
9
|
+
* container and mounts/saves it itself (createWorldContainer).
|
|
10
|
+
*
|
|
11
|
+
* Taken off the BAGS, which is what a save envelope is made of, rather than
|
|
12
|
+
* off the envelope itself. The two used to be interchangeable; from 4.4 they
|
|
13
|
+
* are not, because a property ADDRESS names its owner by gameId while the
|
|
14
|
+
* envelope stays keyed by internal id (a save has to survive a rename). The
|
|
15
|
+
* bags carry the address, so reading them is what keeps this snapshot and
|
|
16
|
+
* the live logger's lines in ONE path space - which is the invariant the
|
|
17
|
+
* whole diff rests on. */
|
|
11
18
|
declare function snapshotState(engine: Engine, flow: Flow): StateSnapshot;
|
|
12
19
|
/** The storylets state logger: the kernel core mounted on the SHARED bags
|
|
13
20
|
* (engine.listBags()) and one flow's own (flow.listBags()) - the same
|
|
@@ -81,7 +88,10 @@ interface BundleInspector {
|
|
|
81
88
|
description: BundleDescription;
|
|
82
89
|
destroy(): void;
|
|
83
90
|
}
|
|
84
|
-
/** "name: type = default", plus enum/flags options where declared
|
|
91
|
+
/** "name: type = default", plus enum/flags options where declared, plus
|
|
92
|
+
* "(durable)" where the declaration says the value outlives a run
|
|
93
|
+
* (design/engine-server.md 4.2). Nothing is added for the ordinary
|
|
94
|
+
* run-scoped property: that is what a property is. */
|
|
85
95
|
declare function formatPropertySummary(p: PropertySummary): string;
|
|
86
96
|
/** The scope label a declaration block files under ("world", "box box",
|
|
87
97
|
* "tag docks (zone)"). */
|
|
@@ -237,7 +247,11 @@ interface WorldContainer {
|
|
|
237
247
|
/** Pass as `new Engine(bundle, { world: container.resolver })`. */
|
|
238
248
|
resolver: ScopeResolver;
|
|
239
249
|
/** The kernel bag itself (subscribe, audit, rows live there) - mount it
|
|
240
|
-
* into a state logger or examiner beside the engine's own bags.
|
|
250
|
+
* into a state logger or examiner beside the engine's own bags. Writing it
|
|
251
|
+
* DIRECTLY is writing the kernel, so a `writable: false` declaration asks
|
|
252
|
+
* the kernel's question: pass `{ host: true }` to say the game is speaking
|
|
253
|
+
* (`bag.set(name, value, { host: true })`). Through `resolver` or an
|
|
254
|
+
* engine's setProperty that is already answered. */
|
|
241
255
|
bag: PropertyBag$1;
|
|
242
256
|
/** The current values, for saving beside the engine's envelope. */
|
|
243
257
|
values(): PropertyBag;
|
|
@@ -245,7 +259,26 @@ interface WorldContainer {
|
|
|
245
259
|
* declarations keep their defaults - the same drift rule as loadGame. */
|
|
246
260
|
load(values: PropertyBag): void;
|
|
247
261
|
}
|
|
248
|
-
/** A world container seeded from the bundle's @world declarations.
|
|
262
|
+
/** A world container seeded from the bundle's @world declarations.
|
|
263
|
+
*
|
|
264
|
+
* The container is the GAME's state, so it WRITES - even a declaration
|
|
265
|
+
* carrying `writable: false`. That flag is the STORY's promise not to write
|
|
266
|
+
* the value (Reboot.md 10), and the engine keeps it where the story writes: an
|
|
267
|
+
* outcome is refused against the engine's read-only table before it ever
|
|
268
|
+
* reaches this resolver. Enforcing it here as well refused the HOST too - the
|
|
269
|
+
* clock the game must move, the harness driving the value it is testing
|
|
270
|
+
* against - which is the opposite of what the flag says.
|
|
271
|
+
*
|
|
272
|
+
* So the declarations are seeded AS DECLARED, which is what an examiner over
|
|
273
|
+
* this container should read, and the writes go through as HOST writes
|
|
274
|
+
* (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the
|
|
275
|
+
* engine's own doorway and passes the flag too: the engine has already sorted
|
|
276
|
+
* story from host by then - a story write was refused earlier, a host write is
|
|
277
|
+
* the only kind that arrives - and a resolver takes a name and a value with no
|
|
278
|
+
* room to say which. A game wanting a rule of its own binds its own resolver
|
|
279
|
+
* rather than this one; the ports' container (Unreal's UStoryletWorld) draws
|
|
280
|
+
* the same line, with HostSet never refused and StorySet asking the game's own
|
|
281
|
+
* read-only list. */
|
|
249
282
|
declare function createWorldContainer(bundle: Bundle): WorldContainer;
|
|
250
283
|
|
|
251
284
|
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,10 +4,17 @@ export { StateChange, StateLogger, StateLoggerAdapter, StateLoggerOptions, State
|
|
|
4
4
|
import { PropertyBag, SaveFile, Bundle } from '@storylet-studio/model';
|
|
5
5
|
|
|
6
6
|
/** The full flattened snapshot of ONE FLOW's view - the shared partitions
|
|
7
|
-
* plus that flow's own -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* plus that flow's own - plus its turns / cooldowns / board. @world is not
|
|
8
|
+
* here for the same reason it is not in a save envelope: the host owns that
|
|
9
|
+
* container and mounts/saves it itself (createWorldContainer).
|
|
10
|
+
*
|
|
11
|
+
* Taken off the BAGS, which is what a save envelope is made of, rather than
|
|
12
|
+
* off the envelope itself. The two used to be interchangeable; from 4.4 they
|
|
13
|
+
* are not, because a property ADDRESS names its owner by gameId while the
|
|
14
|
+
* envelope stays keyed by internal id (a save has to survive a rename). The
|
|
15
|
+
* bags carry the address, so reading them is what keeps this snapshot and
|
|
16
|
+
* the live logger's lines in ONE path space - which is the invariant the
|
|
17
|
+
* whole diff rests on. */
|
|
11
18
|
declare function snapshotState(engine: Engine, flow: Flow): StateSnapshot;
|
|
12
19
|
/** The storylets state logger: the kernel core mounted on the SHARED bags
|
|
13
20
|
* (engine.listBags()) and one flow's own (flow.listBags()) - the same
|
|
@@ -81,7 +88,10 @@ interface BundleInspector {
|
|
|
81
88
|
description: BundleDescription;
|
|
82
89
|
destroy(): void;
|
|
83
90
|
}
|
|
84
|
-
/** "name: type = default", plus enum/flags options where declared
|
|
91
|
+
/** "name: type = default", plus enum/flags options where declared, plus
|
|
92
|
+
* "(durable)" where the declaration says the value outlives a run
|
|
93
|
+
* (design/engine-server.md 4.2). Nothing is added for the ordinary
|
|
94
|
+
* run-scoped property: that is what a property is. */
|
|
85
95
|
declare function formatPropertySummary(p: PropertySummary): string;
|
|
86
96
|
/** The scope label a declaration block files under ("world", "box box",
|
|
87
97
|
* "tag docks (zone)"). */
|
|
@@ -237,7 +247,11 @@ interface WorldContainer {
|
|
|
237
247
|
/** Pass as `new Engine(bundle, { world: container.resolver })`. */
|
|
238
248
|
resolver: ScopeResolver;
|
|
239
249
|
/** The kernel bag itself (subscribe, audit, rows live there) - mount it
|
|
240
|
-
* into a state logger or examiner beside the engine's own bags.
|
|
250
|
+
* into a state logger or examiner beside the engine's own bags. Writing it
|
|
251
|
+
* DIRECTLY is writing the kernel, so a `writable: false` declaration asks
|
|
252
|
+
* the kernel's question: pass `{ host: true }` to say the game is speaking
|
|
253
|
+
* (`bag.set(name, value, { host: true })`). Through `resolver` or an
|
|
254
|
+
* engine's setProperty that is already answered. */
|
|
241
255
|
bag: PropertyBag$1;
|
|
242
256
|
/** The current values, for saving beside the engine's envelope. */
|
|
243
257
|
values(): PropertyBag;
|
|
@@ -245,7 +259,26 @@ interface WorldContainer {
|
|
|
245
259
|
* declarations keep their defaults - the same drift rule as loadGame. */
|
|
246
260
|
load(values: PropertyBag): void;
|
|
247
261
|
}
|
|
248
|
-
/** A world container seeded from the bundle's @world declarations.
|
|
262
|
+
/** A world container seeded from the bundle's @world declarations.
|
|
263
|
+
*
|
|
264
|
+
* The container is the GAME's state, so it WRITES - even a declaration
|
|
265
|
+
* carrying `writable: false`. That flag is the STORY's promise not to write
|
|
266
|
+
* the value (Reboot.md 10), and the engine keeps it where the story writes: an
|
|
267
|
+
* outcome is refused against the engine's read-only table before it ever
|
|
268
|
+
* reaches this resolver. Enforcing it here as well refused the HOST too - the
|
|
269
|
+
* clock the game must move, the harness driving the value it is testing
|
|
270
|
+
* against - which is the opposite of what the flag says.
|
|
271
|
+
*
|
|
272
|
+
* So the declarations are seeded AS DECLARED, which is what an examiner over
|
|
273
|
+
* this container should read, and the writes go through as HOST writes
|
|
274
|
+
* (scoperegistry 0.6.0's `{ host: true }`). The resolver's `set` is the
|
|
275
|
+
* engine's own doorway and passes the flag too: the engine has already sorted
|
|
276
|
+
* story from host by then - a story write was refused earlier, a host write is
|
|
277
|
+
* the only kind that arrives - and a resolver takes a name and a value with no
|
|
278
|
+
* room to say which. A game wanting a rule of its own binds its own resolver
|
|
279
|
+
* rather than this one; the ports' container (Unreal's UStoryletWorld) draws
|
|
280
|
+
* the same line, with HostSet never refused and StorySet asking the game's own
|
|
281
|
+
* read-only list. */
|
|
249
282
|
declare function createWorldContainer(bundle: Bundle): WorldContainer;
|
|
250
283
|
|
|
251
284
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -95,10 +95,14 @@ var PropertyBag = class _PropertyBag {
|
|
|
95
95
|
}
|
|
96
96
|
/** Write a property. Engine writes (the default) notify subscribers;
|
|
97
97
|
* pass `silent: true` for a host write, which reaches only the audit
|
|
98
|
-
* hook. Throws on a read-only property
|
|
98
|
+
* hook. Throws on a read-only property unless the caller says it is the
|
|
99
|
+
* HOST (`host: true`), for whom `writable: false` was never a rule - it is
|
|
100
|
+
* the story's promise, not the game's. `silent` and `host` are separate on
|
|
101
|
+
* purpose: one is about who hears the write, the other about who may make
|
|
102
|
+
* it. Returns the change. */
|
|
99
103
|
set(name, value, opts) {
|
|
100
104
|
const n = this.norm(name);
|
|
101
|
-
if (this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);
|
|
105
|
+
if (!opts?.host && this.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);
|
|
102
106
|
const change = {
|
|
103
107
|
name: n,
|
|
104
108
|
prev: this.values[n],
|
|
@@ -200,19 +204,13 @@ function defaultFor(d) {
|
|
|
200
204
|
|
|
201
205
|
// src/logger.ts
|
|
202
206
|
function snapshotState(engine, flow) {
|
|
203
|
-
const env = engine.saveGame();
|
|
204
|
-
const flowSave = env.flows[flow.id];
|
|
205
207
|
const out = {};
|
|
206
|
-
const bag
|
|
207
|
-
for (const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
bag("story", flowSave?.props.story);
|
|
211
|
-
for (const kind of ["box", "deck", "hand", "value"]) {
|
|
212
|
-
for (const [id, values] of Object.entries(env.shared.props[kind])) bag(`${kind}.${id}`, values);
|
|
213
|
-
for (const [id, values] of Object.entries(flowSave?.props[kind] ?? {})) bag(`${kind}.${id}`, values);
|
|
208
|
+
for (const { bag } of [...engine.listBags(), ...flow.listBags()]) {
|
|
209
|
+
for (const row of bag.rows()) {
|
|
210
|
+
if (row.value !== void 0) out[row.path] = row.value;
|
|
211
|
+
}
|
|
214
212
|
}
|
|
215
|
-
Object.assign(out, extraState(
|
|
213
|
+
Object.assign(out, extraState(engine.saveGame().flows[flow.id]));
|
|
216
214
|
return out;
|
|
217
215
|
}
|
|
218
216
|
function extraState(saved) {
|
|
@@ -666,7 +664,8 @@ import { describeBundle } from "@storylet-studio/runtime";
|
|
|
666
664
|
var showVal2 = (v) => v === void 0 ? "<unset>" : JSON.stringify(v);
|
|
667
665
|
function formatPropertySummary(p) {
|
|
668
666
|
const options = p.values !== void 0 && p.values.length > 0 ? ` [${p.values.join(", ")}]` : "";
|
|
669
|
-
|
|
667
|
+
const durable = p.durable === true ? " (durable)" : "";
|
|
668
|
+
return `${p.name}: ${p.type} = ${showVal2(p.default)}${options}${durable}`;
|
|
670
669
|
}
|
|
671
670
|
function formatScopeLabel(scope) {
|
|
672
671
|
if (scope.scope === "world" || scope.scope === "story") return scope.scope;
|
|
@@ -722,7 +721,8 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
722
721
|
}
|
|
723
722
|
for (const hand of description.hands) {
|
|
724
723
|
const template = hand.template !== void 0 ? `, template ${hand.template}` : "";
|
|
725
|
-
|
|
724
|
+
const moves = hand.movable === void 0 ? "" : `, moves ${hand.movable.map((m) => `${m.group} from ${m.from}`).join(" and ")}`;
|
|
725
|
+
line(handsBody, `${hand.gameId}: box ${hand.box}, slots ${hand.slots}${template}${moves}` + (hand.title !== void 0 ? ` - ${hand.title}` : ""));
|
|
726
726
|
}
|
|
727
727
|
const tagsBody = fold(el, "Tags by box (peek criteria)", open);
|
|
728
728
|
tagsBody.className = "sl-tags";
|
|
@@ -751,7 +751,7 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
751
751
|
mapsBody.className = "sl-maps";
|
|
752
752
|
line(mapsBody, "Geometry the build was asked to carry. The engine ignores it.", "sl-line sl-note");
|
|
753
753
|
for (const map of description.maps) {
|
|
754
|
-
line(mapsBody, `${map.box} - ${map.group}: zones ${map.zones}, pictures ${map.backgrounds}`);
|
|
754
|
+
line(mapsBody, `${map.box} - ${map.group}: zones ${map.zones}, pictures ${map.backgrounds}, sites ${map.sites}`);
|
|
755
755
|
}
|
|
756
756
|
}
|
|
757
757
|
const countsBody = fold(el, "Counts", open);
|
|
@@ -759,7 +759,7 @@ function createBundleInspector(bundle, opts = {}) {
|
|
|
759
759
|
line(countsBody, `boxes ${totals.boxes} - decks ${totals.decks} - cards ${totals.cards}`);
|
|
760
760
|
line(countsBody, `hands ${totals.hands} - templates ${totals.templates} - tag groups ${totals.tagGroups}`);
|
|
761
761
|
for (const box of description.boxes) {
|
|
762
|
-
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}`);
|
|
762
|
+
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}` : ""));
|
|
763
763
|
}
|
|
764
764
|
(opts.container ?? document.body).append(el);
|
|
765
765
|
return {
|
|
@@ -968,7 +968,7 @@ function createWorldContainer(bundle) {
|
|
|
968
968
|
resolver: {
|
|
969
969
|
get: (n) => bag.get(n),
|
|
970
970
|
set: (n, v) => {
|
|
971
|
-
bag.set(n, v);
|
|
971
|
+
bag.set(n, v, { host: true });
|
|
972
972
|
}
|
|
973
973
|
},
|
|
974
974
|
bag,
|