@patterkit/runtime 0.19.0 → 0.20.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/CHANGELOG.md +40 -0
- package/README.md +4 -3
- package/dist/index.cjs +85 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -18
- package/dist/index.d.ts +51 -18
- package/dist/index.js +85 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -127,12 +127,14 @@ interface ChoiceOption {
|
|
|
127
127
|
gameData?: GameData;
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* comes from the compiled bundle's
|
|
130
|
+
* A host scope's resolver: a `{ get, set? }` the game provides so the story can read (and, if you allow
|
|
131
|
+
* it, write) values the game owns, `@world.*` among them, at runtime. Property metadata (types,
|
|
132
|
+
* read-only) comes from the compiled bundle's declarations; the values themselves live in the host and
|
|
133
133
|
* are never stored or saved by this engine.
|
|
134
134
|
*/
|
|
135
|
-
type
|
|
135
|
+
type HostScope = ScopeResolver;
|
|
136
|
+
/** @deprecated Use {@link HostScope}, the name every Patterplay runtime uses. */
|
|
137
|
+
type WorldResolver = HostScope;
|
|
136
138
|
/** What the engine DECIDED, as opposed to what it produced. A step tells you the line that
|
|
137
139
|
* played; a trace event tells you why that line and not its siblings - which children were
|
|
138
140
|
* eligible, where the selector's cursor stood, which choice options were live.
|
|
@@ -222,6 +224,8 @@ interface PlayError {
|
|
|
222
224
|
source?: string;
|
|
223
225
|
message: string;
|
|
224
226
|
}
|
|
227
|
+
/** @deprecated Nothing takes one: a flow has no tap of its own, and `onTrace` takes an
|
|
228
|
+
* {@link EngineTraceHandler}. Goes in a later release. */
|
|
225
229
|
type TraceHandler = (event: TraceEvent) => void;
|
|
226
230
|
/** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
|
|
227
231
|
type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
|
|
@@ -249,14 +253,18 @@ interface EngineOptions {
|
|
|
249
253
|
/** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.
|
|
250
254
|
* Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
|
|
251
255
|
locale?: string;
|
|
252
|
-
/** The host
|
|
253
|
-
* reads
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
|
|
256
|
+
/** The host scopes the game binds, by token: for each, a resolver over values the game owns and the
|
|
257
|
+
* story reads, such as `{ world: { get, set } }` for **World Properties** (`@world.*`). A declared
|
|
258
|
+
* scope left unbound is self-backed from its defaults, as a property the registry stores and saves.
|
|
259
|
+
* Shared by all flows. A game running several engines registers its scopes in its registry itself
|
|
260
|
+
* instead, and binds none here. The same option on every runtime. */
|
|
261
|
+
hostScopes?: Record<string, HostScope>;
|
|
262
|
+
/** @deprecated Use `hostScopes: { world }`, the option every Patterplay runtime takes. Goes in a later
|
|
263
|
+
* release. */
|
|
264
|
+
world?: HostScope;
|
|
257
265
|
/** The game's registry: ONE per game, holding every engine's properties except those the game keeps
|
|
258
266
|
* itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,
|
|
259
|
-
* its per-flow and per-scene bags under keys starting `patter/`, and
|
|
267
|
+
* its per-flow and per-scene bags under keys starting `patter/`, and each scope `hostScopes` binds),
|
|
260
268
|
* reads every other scope from it, and `saveGame()` leaves the property values to the game. Host
|
|
261
269
|
* scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should
|
|
262
270
|
* store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as
|
|
@@ -324,8 +332,13 @@ declare class Checkpoint {
|
|
|
324
332
|
}
|
|
325
333
|
/** Shared, read-mostly context the engine hands to every flow it owns. */
|
|
326
334
|
interface FlowHost {
|
|
327
|
-
/** True when the run asked for a log
|
|
335
|
+
/** True when the run asked for a log. */
|
|
328
336
|
logEnabled: boolean;
|
|
337
|
+
/** Each declaration set's quality ladders, built on first use (see `ladders`). */
|
|
338
|
+
qualityLadders: Map<string, Map<string, readonly string[]>>;
|
|
339
|
+
/** True when anything takes the decisions: the log, or an {@link Engine.onTrace} handler. Flows skip
|
|
340
|
+
* the work only a decision needs (a `run` walk's entry, a write's previous value) otherwise. */
|
|
341
|
+
tracing: boolean;
|
|
329
342
|
/** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */
|
|
330
343
|
emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;
|
|
331
344
|
bundle: Bundle;
|
|
@@ -419,7 +432,7 @@ declare class Engine {
|
|
|
419
432
|
private readonly sceneGameIdToId;
|
|
420
433
|
private readonly blockGameIdToId;
|
|
421
434
|
/** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
|
|
422
|
-
* engine keeps the same
|
|
435
|
+
* engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
|
|
423
436
|
private readonly creationOptions;
|
|
424
437
|
/** The run's ordered stream: every flow's events, each naming its flow. Empty and
|
|
425
438
|
* unwritten unless `options.log` asked for it. */
|
|
@@ -433,6 +446,9 @@ declare class Engine {
|
|
|
433
446
|
/** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),
|
|
434
447
|
* not a shippable localised build. An IDs-only ship build is `false`. */
|
|
435
448
|
get isSourceDebug(): boolean;
|
|
449
|
+
/** The bundle's build identity (its content hash), the one a debug link handshakes with, or an empty
|
|
450
|
+
* string for a bundle compiled without one. */
|
|
451
|
+
get buildId(): string;
|
|
436
452
|
/**
|
|
437
453
|
* Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
|
|
438
454
|
* string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
|
|
@@ -677,6 +693,9 @@ declare class Engine {
|
|
|
677
693
|
* retained. Returns its own unsubscribe. */
|
|
678
694
|
onTrace(handler: EngineTraceHandler): () => void;
|
|
679
695
|
private emitEngine;
|
|
696
|
+
/** How many times each node has been entered across every flow, by node id: the shared count. With a
|
|
697
|
+
* flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
|
|
698
|
+
getVisitCounts(): Record<string, number>;
|
|
680
699
|
listProperties(): PropertyRow[];
|
|
681
700
|
private splitShared;
|
|
682
701
|
/** The shared `@patter` values alone, as `{ patter: {...} }`.
|
|
@@ -744,15 +763,20 @@ declare class Flow {
|
|
|
744
763
|
/** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
|
|
745
764
|
* lowercase, as the compiler emits references (the selfBackedResolver lesson). */
|
|
746
765
|
private stagesFor;
|
|
747
|
-
/** Begin this flow at a scene (and optionally a specific block within it).
|
|
748
|
-
|
|
766
|
+
/** Begin this flow at a scene (and optionally a specific block within it). The engine's own entry point:
|
|
767
|
+
* openFlow and a goto on an unstarted flow begin a flow here. A game calls {@link reset}.
|
|
768
|
+
* @internal */
|
|
769
|
+
begin(sceneId?: string, blockId?: string): void;
|
|
749
770
|
/**
|
|
750
771
|
* Forget everything in this flow and begin again - its per-flow state (not-shared
|
|
751
772
|
* `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
|
|
752
773
|
* visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
|
|
753
|
-
* untouched.
|
|
774
|
+
* untouched. The one public way to begin a flow again (openFlow begins a new one).
|
|
754
775
|
*/
|
|
755
776
|
reset(sceneId?: string, blockId?: string): void;
|
|
777
|
+
/** @deprecated Use {@link reset}, the one public name for beginning a flow again on every runtime. `start`
|
|
778
|
+
* was the same call under a second name, and goes in a later release. */
|
|
779
|
+
start(sceneId?: string, blockId?: string): void;
|
|
756
780
|
/**
|
|
757
781
|
* Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
|
|
758
782
|
* `onEntry` effects run, entering counts as a visit, and the callstack is REPLACED - any pending
|
|
@@ -782,6 +806,13 @@ declare class Flow {
|
|
|
782
806
|
* cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.
|
|
783
807
|
*/
|
|
784
808
|
close(): void;
|
|
809
|
+
/**
|
|
810
|
+
* Drop everything waiting to be delivered: an open choice, and a chosen option's prompt still to be
|
|
811
|
+
* replayed. Every move that abandons the flow's place (start and reset, goto, close) does this, so none
|
|
812
|
+
* leaves a stale prompt behind. start() cleared only the choice, so a reset between choose() and the
|
|
813
|
+
* next advance() replayed the abandoned run's prompt as the new run's first beat.
|
|
814
|
+
*/
|
|
815
|
+
private clearPending;
|
|
785
816
|
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
786
817
|
* put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
|
|
787
818
|
* Its visits, selector cursors, and property values are recorded change by change as they happen. */
|
|
@@ -820,9 +851,11 @@ declare class Flow {
|
|
|
820
851
|
log(): readonly LogEntry[];
|
|
821
852
|
/** Drop the retained entries. `seq` keeps counting, so order survives a clear. */
|
|
822
853
|
clearLog(): void;
|
|
823
|
-
/** Record one decision, on this flow's log and the engine's
|
|
824
|
-
*
|
|
854
|
+
/** Record one decision, on this flow's log and the engine's, and hand it to any `onTrace`. Each call
|
|
855
|
+
* site checks `host.tracing` first, so with nothing logging or tracing the entry is never built. */
|
|
825
856
|
private emit;
|
|
857
|
+
/** How many times this flow has entered each node, by node id. */
|
|
858
|
+
getVisitCounts(): Record<string, number>;
|
|
826
859
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
827
860
|
getChoices(): ChoiceOption[];
|
|
828
861
|
/** Pick an eligible option by id; the next `advance()` runs it. */
|
|
@@ -1139,4 +1172,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
|
|
|
1139
1172
|
*/
|
|
1140
1173
|
declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
|
|
1141
1174
|
|
|
1142
|
-
export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, Checkpoint, type ChoiceOption, Engine, type EngineLogEntry, type EngineOptions, type EngineTraceHandler, type FlatBeat, Flow, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type LogEntry, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PlayError, type PropertySummary, type StepResult, type TraceEvent, type TraceHandler, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
|
|
1175
|
+
export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, Checkpoint, type ChoiceOption, Engine, type EngineLogEntry, type EngineOptions, type EngineTraceHandler, type FlatBeat, Flow, type GameDataFieldSummary, type GameDataSummary, type HostScope, type HostScopeSummary, type LogEntry, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PlayError, type PropertySummary, type StepResult, type TraceEvent, type TraceHandler, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,12 +127,14 @@ interface ChoiceOption {
|
|
|
127
127
|
gameData?: GameData;
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* comes from the compiled bundle's
|
|
130
|
+
* A host scope's resolver: a `{ get, set? }` the game provides so the story can read (and, if you allow
|
|
131
|
+
* it, write) values the game owns, `@world.*` among them, at runtime. Property metadata (types,
|
|
132
|
+
* read-only) comes from the compiled bundle's declarations; the values themselves live in the host and
|
|
133
133
|
* are never stored or saved by this engine.
|
|
134
134
|
*/
|
|
135
|
-
type
|
|
135
|
+
type HostScope = ScopeResolver;
|
|
136
|
+
/** @deprecated Use {@link HostScope}, the name every Patterplay runtime uses. */
|
|
137
|
+
type WorldResolver = HostScope;
|
|
136
138
|
/** What the engine DECIDED, as opposed to what it produced. A step tells you the line that
|
|
137
139
|
* played; a trace event tells you why that line and not its siblings - which children were
|
|
138
140
|
* eligible, where the selector's cursor stood, which choice options were live.
|
|
@@ -222,6 +224,8 @@ interface PlayError {
|
|
|
222
224
|
source?: string;
|
|
223
225
|
message: string;
|
|
224
226
|
}
|
|
227
|
+
/** @deprecated Nothing takes one: a flow has no tap of its own, and `onTrace` takes an
|
|
228
|
+
* {@link EngineTraceHandler}. Goes in a later release. */
|
|
225
229
|
type TraceHandler = (event: TraceEvent) => void;
|
|
226
230
|
/** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
|
|
227
231
|
type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
|
|
@@ -249,14 +253,18 @@ interface EngineOptions {
|
|
|
249
253
|
/** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.
|
|
250
254
|
* Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
|
|
251
255
|
locale?: string;
|
|
252
|
-
/** The host
|
|
253
|
-
* reads
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
|
|
256
|
+
/** The host scopes the game binds, by token: for each, a resolver over values the game owns and the
|
|
257
|
+
* story reads, such as `{ world: { get, set } }` for **World Properties** (`@world.*`). A declared
|
|
258
|
+
* scope left unbound is self-backed from its defaults, as a property the registry stores and saves.
|
|
259
|
+
* Shared by all flows. A game running several engines registers its scopes in its registry itself
|
|
260
|
+
* instead, and binds none here. The same option on every runtime. */
|
|
261
|
+
hostScopes?: Record<string, HostScope>;
|
|
262
|
+
/** @deprecated Use `hostScopes: { world }`, the option every Patterplay runtime takes. Goes in a later
|
|
263
|
+
* release. */
|
|
264
|
+
world?: HostScope;
|
|
257
265
|
/** The game's registry: ONE per game, holding every engine's properties except those the game keeps
|
|
258
266
|
* itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,
|
|
259
|
-
* its per-flow and per-scene bags under keys starting `patter/`, and
|
|
267
|
+
* its per-flow and per-scene bags under keys starting `patter/`, and each scope `hostScopes` binds),
|
|
260
268
|
* reads every other scope from it, and `saveGame()` leaves the property values to the game. Host
|
|
261
269
|
* scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should
|
|
262
270
|
* store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as
|
|
@@ -324,8 +332,13 @@ declare class Checkpoint {
|
|
|
324
332
|
}
|
|
325
333
|
/** Shared, read-mostly context the engine hands to every flow it owns. */
|
|
326
334
|
interface FlowHost {
|
|
327
|
-
/** True when the run asked for a log
|
|
335
|
+
/** True when the run asked for a log. */
|
|
328
336
|
logEnabled: boolean;
|
|
337
|
+
/** Each declaration set's quality ladders, built on first use (see `ladders`). */
|
|
338
|
+
qualityLadders: Map<string, Map<string, readonly string[]>>;
|
|
339
|
+
/** True when anything takes the decisions: the log, or an {@link Engine.onTrace} handler. Flows skip
|
|
340
|
+
* the work only a decision needs (a `run` walk's entry, a write's previous value) otherwise. */
|
|
341
|
+
tracing: boolean;
|
|
329
342
|
/** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */
|
|
330
343
|
emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;
|
|
331
344
|
bundle: Bundle;
|
|
@@ -419,7 +432,7 @@ declare class Engine {
|
|
|
419
432
|
private readonly sceneGameIdToId;
|
|
420
433
|
private readonly blockGameIdToId;
|
|
421
434
|
/** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
|
|
422
|
-
* engine keeps the same
|
|
435
|
+
* engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
|
|
423
436
|
private readonly creationOptions;
|
|
424
437
|
/** The run's ordered stream: every flow's events, each naming its flow. Empty and
|
|
425
438
|
* unwritten unless `options.log` asked for it. */
|
|
@@ -433,6 +446,9 @@ declare class Engine {
|
|
|
433
446
|
/** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),
|
|
434
447
|
* not a shippable localised build. An IDs-only ship build is `false`. */
|
|
435
448
|
get isSourceDebug(): boolean;
|
|
449
|
+
/** The bundle's build identity (its content hash), the one a debug link handshakes with, or an empty
|
|
450
|
+
* string for a bundle compiled without one. */
|
|
451
|
+
get buildId(): string;
|
|
436
452
|
/**
|
|
437
453
|
* Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
|
|
438
454
|
* string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
|
|
@@ -677,6 +693,9 @@ declare class Engine {
|
|
|
677
693
|
* retained. Returns its own unsubscribe. */
|
|
678
694
|
onTrace(handler: EngineTraceHandler): () => void;
|
|
679
695
|
private emitEngine;
|
|
696
|
+
/** How many times each node has been entered across every flow, by node id: the shared count. With a
|
|
697
|
+
* flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
|
|
698
|
+
getVisitCounts(): Record<string, number>;
|
|
680
699
|
listProperties(): PropertyRow[];
|
|
681
700
|
private splitShared;
|
|
682
701
|
/** The shared `@patter` values alone, as `{ patter: {...} }`.
|
|
@@ -744,15 +763,20 @@ declare class Flow {
|
|
|
744
763
|
/** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
|
|
745
764
|
* lowercase, as the compiler emits references (the selfBackedResolver lesson). */
|
|
746
765
|
private stagesFor;
|
|
747
|
-
/** Begin this flow at a scene (and optionally a specific block within it).
|
|
748
|
-
|
|
766
|
+
/** Begin this flow at a scene (and optionally a specific block within it). The engine's own entry point:
|
|
767
|
+
* openFlow and a goto on an unstarted flow begin a flow here. A game calls {@link reset}.
|
|
768
|
+
* @internal */
|
|
769
|
+
begin(sceneId?: string, blockId?: string): void;
|
|
749
770
|
/**
|
|
750
771
|
* Forget everything in this flow and begin again - its per-flow state (not-shared
|
|
751
772
|
* `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
|
|
752
773
|
* visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
|
|
753
|
-
* untouched.
|
|
774
|
+
* untouched. The one public way to begin a flow again (openFlow begins a new one).
|
|
754
775
|
*/
|
|
755
776
|
reset(sceneId?: string, blockId?: string): void;
|
|
777
|
+
/** @deprecated Use {@link reset}, the one public name for beginning a flow again on every runtime. `start`
|
|
778
|
+
* was the same call under a second name, and goes in a later release. */
|
|
779
|
+
start(sceneId?: string, blockId?: string): void;
|
|
756
780
|
/**
|
|
757
781
|
* Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
|
|
758
782
|
* `onEntry` effects run, entering counts as a visit, and the callstack is REPLACED - any pending
|
|
@@ -782,6 +806,13 @@ declare class Flow {
|
|
|
782
806
|
* cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.
|
|
783
807
|
*/
|
|
784
808
|
close(): void;
|
|
809
|
+
/**
|
|
810
|
+
* Drop everything waiting to be delivered: an open choice, and a chosen option's prompt still to be
|
|
811
|
+
* replayed. Every move that abandons the flow's place (start and reset, goto, close) does this, so none
|
|
812
|
+
* leaves a stale prompt behind. start() cleared only the choice, so a reset between choose() and the
|
|
813
|
+
* next advance() replayed the abandoned run's prompt as the new run's first beat.
|
|
814
|
+
*/
|
|
815
|
+
private clearPending;
|
|
785
816
|
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
786
817
|
* put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
|
|
787
818
|
* Its visits, selector cursors, and property values are recorded change by change as they happen. */
|
|
@@ -820,9 +851,11 @@ declare class Flow {
|
|
|
820
851
|
log(): readonly LogEntry[];
|
|
821
852
|
/** Drop the retained entries. `seq` keeps counting, so order survives a clear. */
|
|
822
853
|
clearLog(): void;
|
|
823
|
-
/** Record one decision, on this flow's log and the engine's
|
|
824
|
-
*
|
|
854
|
+
/** Record one decision, on this flow's log and the engine's, and hand it to any `onTrace`. Each call
|
|
855
|
+
* site checks `host.tracing` first, so with nothing logging or tracing the entry is never built. */
|
|
825
856
|
private emit;
|
|
857
|
+
/** How many times this flow has entered each node, by node id. */
|
|
858
|
+
getVisitCounts(): Record<string, number>;
|
|
826
859
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
827
860
|
getChoices(): ChoiceOption[];
|
|
828
861
|
/** Pick an eligible option by id; the next `advance()` runs it. */
|
|
@@ -1139,4 +1172,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
|
|
|
1139
1172
|
*/
|
|
1140
1173
|
declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
|
|
1141
1174
|
|
|
1142
|
-
export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, Checkpoint, type ChoiceOption, Engine, type EngineLogEntry, type EngineOptions, type EngineTraceHandler, type FlatBeat, Flow, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type LogEntry, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PlayError, type PropertySummary, type StepResult, type TraceEvent, type TraceHandler, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
|
|
1175
|
+
export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, Checkpoint, type ChoiceOption, Engine, type EngineLogEntry, type EngineOptions, type EngineTraceHandler, type FlatBeat, Flow, type GameDataFieldSummary, type GameDataSummary, type HostScope, type HostScopeSummary, type LogEntry, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PlayError, type PropertySummary, type StepResult, type TraceEvent, type TraceHandler, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
|
package/dist/index.js
CHANGED
|
@@ -81,7 +81,7 @@ var Engine = class _Engine {
|
|
|
81
81
|
sceneGameIdToId = /* @__PURE__ */ new Map();
|
|
82
82
|
blockGameIdToId = /* @__PURE__ */ new Map();
|
|
83
83
|
/** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
|
|
84
|
-
* engine keeps the same
|
|
84
|
+
* engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
|
|
85
85
|
creationOptions;
|
|
86
86
|
/** The run's ordered stream: every flow's events, each naming its flow. Empty and
|
|
87
87
|
* unwritten unless `options.log` asked for it. */
|
|
@@ -133,12 +133,12 @@ var Engine = class _Engine {
|
|
|
133
133
|
registry.mountOwned("patter", patterBag, { owner: OWNER });
|
|
134
134
|
registered.push("patter");
|
|
135
135
|
const hostBound = /* @__PURE__ */ new Set();
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
const decls = (
|
|
139
|
-
registry.defineForeign(
|
|
140
|
-
registered.push(
|
|
141
|
-
hostBound.add(
|
|
136
|
+
for (const [token, resolver] of Object.entries(boundScopes(options))) {
|
|
137
|
+
const spec = bundle.scopeRegistry?.scopes.find((s) => s.token === token);
|
|
138
|
+
const decls = (spec?.declarations ?? []).map(toForeignDecl);
|
|
139
|
+
registry.defineForeign(token, resolver, decls, { writable: spec?.writable ?? true, owner: OWNER });
|
|
140
|
+
registered.push(token);
|
|
141
|
+
hostBound.add(token);
|
|
142
142
|
}
|
|
143
143
|
for (const spec of ownsRegistry ? bundle.scopeRegistry?.scopes ?? [] : []) {
|
|
144
144
|
if (hostBound.has(spec.token) || registry.has(spec.token)) continue;
|
|
@@ -158,6 +158,7 @@ var Engine = class _Engine {
|
|
|
158
158
|
}
|
|
159
159
|
this.host = {
|
|
160
160
|
logEnabled: options.log ?? false,
|
|
161
|
+
tracing: options.log ?? false,
|
|
161
162
|
emitEngine: (flow, event, scene) => this.emitEngine(flow, event, scene),
|
|
162
163
|
bundle,
|
|
163
164
|
emitIds,
|
|
@@ -180,6 +181,7 @@ var Engine = class _Engine {
|
|
|
180
181
|
patterSharedNames,
|
|
181
182
|
sceneSharedNames,
|
|
182
183
|
sharedVisits: /* @__PURE__ */ new Map(),
|
|
184
|
+
qualityLadders: /* @__PURE__ */ new Map(),
|
|
183
185
|
sharedSelectors: /* @__PURE__ */ new Map(),
|
|
184
186
|
stageBags: /* @__PURE__ */ new Map(),
|
|
185
187
|
journal: null,
|
|
@@ -206,6 +208,11 @@ var Engine = class _Engine {
|
|
|
206
208
|
get isSourceDebug() {
|
|
207
209
|
return this.sourceDebug;
|
|
208
210
|
}
|
|
211
|
+
/** The bundle's build identity (its content hash), the one a debug link handshakes with, or an empty
|
|
212
|
+
* string for a bundle compiled without one. */
|
|
213
|
+
get buildId() {
|
|
214
|
+
return this.host.bundle.content?.hash ?? "";
|
|
215
|
+
}
|
|
209
216
|
/**
|
|
210
217
|
* Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
|
|
211
218
|
* string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
|
|
@@ -326,7 +333,7 @@ var Engine = class _Engine {
|
|
|
326
333
|
});
|
|
327
334
|
}
|
|
328
335
|
this.flowsById.set(id, flow);
|
|
329
|
-
flow.
|
|
336
|
+
flow.begin(sceneId, blockId);
|
|
330
337
|
return flow;
|
|
331
338
|
}
|
|
332
339
|
/**
|
|
@@ -690,7 +697,7 @@ var Engine = class _Engine {
|
|
|
690
697
|
for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep });
|
|
691
698
|
this.host.stageBags.clear();
|
|
692
699
|
for (const t of ["patter", ...this.host.hostScopes]) if (reg.has(t)) reg.remove(t, { keep });
|
|
693
|
-
|
|
700
|
+
for (const t of Object.keys(boundScopes(this.creationOptions))) if (reg.has(t)) reg.remove(t);
|
|
694
701
|
}
|
|
695
702
|
/** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
|
|
696
703
|
getProperty(ref) {
|
|
@@ -727,13 +734,22 @@ var Engine = class _Engine {
|
|
|
727
734
|
* retained. Returns its own unsubscribe. */
|
|
728
735
|
onTrace(handler) {
|
|
729
736
|
this.engineTraceHandlers.add(handler);
|
|
730
|
-
|
|
737
|
+
this.host.tracing = true;
|
|
738
|
+
return () => {
|
|
739
|
+
this.engineTraceHandlers.delete(handler);
|
|
740
|
+
this.host.tracing = this.host.logEnabled || this.engineTraceHandlers.size > 0;
|
|
741
|
+
};
|
|
731
742
|
}
|
|
732
743
|
emitEngine(flow, event, scene) {
|
|
733
744
|
for (const h of this.engineTraceHandlers) h(flow, event);
|
|
734
745
|
if (!this.host.logEnabled) return;
|
|
735
746
|
this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...scene ? { scene } : {} });
|
|
736
747
|
}
|
|
748
|
+
/** How many times each node has been entered across every flow, by node id: the shared count. With a
|
|
749
|
+
* flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
|
|
750
|
+
getVisitCounts() {
|
|
751
|
+
return Object.fromEntries(this.host.sharedVisits);
|
|
752
|
+
}
|
|
737
753
|
listProperties() {
|
|
738
754
|
return this.host.patterSharedDecls.map((d) => ({
|
|
739
755
|
name: d.name,
|
|
@@ -938,19 +954,21 @@ var Flow = class {
|
|
|
938
954
|
* lowercase, as the compiler emits references (the selfBackedResolver lesson). */
|
|
939
955
|
stagesFor(scope, name) {
|
|
940
956
|
const key = name.toLowerCase();
|
|
941
|
-
const fromDecls = (decls) => decls?.find((d) => d.name.toLowerCase() === key && d.type === "quality")?.stages;
|
|
942
957
|
if (scope === "patter") {
|
|
943
|
-
return
|
|
958
|
+
return ladders(this.host, "patter", () => [...this.host.patterSharedDecls, ...this.host.patterLocalDecls]).get(key);
|
|
944
959
|
}
|
|
945
960
|
if (scope === "scene") {
|
|
946
|
-
const
|
|
947
|
-
|
|
961
|
+
const sceneId = this.currentSceneId;
|
|
962
|
+
if (sceneId == null) return void 0;
|
|
963
|
+
return ladders(this.host, `scene/${sceneId}`, () => this.host.bundle.scenes[sceneId]?.sceneProps).get(key);
|
|
948
964
|
}
|
|
949
|
-
return this.registryQualities?.(scope, name) ??
|
|
965
|
+
return this.registryQualities?.(scope, name) ?? ladders(this.host, `host/${scope}`, () => this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations).get(key);
|
|
950
966
|
}
|
|
951
967
|
// -- Host API -------------------------------------------------------------
|
|
952
|
-
/** Begin this flow at a scene (and optionally a specific block within it).
|
|
953
|
-
|
|
968
|
+
/** Begin this flow at a scene (and optionally a specific block within it). The engine's own entry point:
|
|
969
|
+
* openFlow and a goto on an unstarted flow begin a flow here. A game calls {@link reset}.
|
|
970
|
+
* @internal */
|
|
971
|
+
begin(sceneId, blockId) {
|
|
954
972
|
if (this.host.journal && !this.host.journal.opened.has(this)) throw new Error("a flow can't be started or reset while a checkpoint is open");
|
|
955
973
|
this.releaseBags(false);
|
|
956
974
|
this.host.registry.discardParked(keys.flow(this.id));
|
|
@@ -962,7 +980,7 @@ var Flow = class {
|
|
|
962
980
|
this.flowEnded = false;
|
|
963
981
|
this.activeSnippet = null;
|
|
964
982
|
this.beatIndex = 0;
|
|
965
|
-
this.
|
|
983
|
+
this.clearPending();
|
|
966
984
|
this.started = true;
|
|
967
985
|
if (blockId) {
|
|
968
986
|
const loc = this.host.blockIndex.get(blockId);
|
|
@@ -987,10 +1005,15 @@ var Flow = class {
|
|
|
987
1005
|
* Forget everything in this flow and begin again - its per-flow state (not-shared
|
|
988
1006
|
* `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
|
|
989
1007
|
* visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
|
|
990
|
-
* untouched.
|
|
1008
|
+
* untouched. The one public way to begin a flow again (openFlow begins a new one).
|
|
991
1009
|
*/
|
|
992
1010
|
reset(sceneId, blockId) {
|
|
993
|
-
this.
|
|
1011
|
+
this.begin(sceneId, blockId);
|
|
1012
|
+
}
|
|
1013
|
+
/** @deprecated Use {@link reset}, the one public name for beginning a flow again on every runtime. `start`
|
|
1014
|
+
* was the same call under a second name, and goes in a later release. */
|
|
1015
|
+
start(sceneId, blockId) {
|
|
1016
|
+
this.reset(sceneId, blockId);
|
|
994
1017
|
}
|
|
995
1018
|
/**
|
|
996
1019
|
* Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
|
|
@@ -1014,9 +1037,7 @@ var Flow = class {
|
|
|
1014
1037
|
this.touch();
|
|
1015
1038
|
if (scene === "END") {
|
|
1016
1039
|
this.started = true;
|
|
1017
|
-
this.
|
|
1018
|
-
this.pendingPrompt = null;
|
|
1019
|
-
this.pendingPromptOwnerId = null;
|
|
1040
|
+
this.clearPending();
|
|
1020
1041
|
this.activeSnippet = null;
|
|
1021
1042
|
this.beatIndex = 0;
|
|
1022
1043
|
this.flowEnded = true;
|
|
@@ -1031,12 +1052,10 @@ var Flow = class {
|
|
|
1031
1052
|
if (blockId === void 0) return false;
|
|
1032
1053
|
}
|
|
1033
1054
|
if (!this.started) {
|
|
1034
|
-
this.
|
|
1055
|
+
this.begin(sceneId, blockId);
|
|
1035
1056
|
return true;
|
|
1036
1057
|
}
|
|
1037
|
-
this.
|
|
1038
|
-
this.pendingPrompt = null;
|
|
1039
|
-
this.pendingPromptOwnerId = null;
|
|
1058
|
+
this.clearPending();
|
|
1040
1059
|
this.activeSnippet = null;
|
|
1041
1060
|
this.beatIndex = 0;
|
|
1042
1061
|
this.flowEnded = false;
|
|
@@ -1061,6 +1080,15 @@ var Flow = class {
|
|
|
1061
1080
|
this.stack = [];
|
|
1062
1081
|
this.activeSnippet = null;
|
|
1063
1082
|
this.beatIndex = 0;
|
|
1083
|
+
this.clearPending();
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Drop everything waiting to be delivered: an open choice, and a chosen option's prompt still to be
|
|
1087
|
+
* replayed. Every move that abandons the flow's place (start and reset, goto, close) does this, so none
|
|
1088
|
+
* leaves a stale prompt behind. start() cleared only the choice, so a reset between choose() and the
|
|
1089
|
+
* next advance() replayed the abandoned run's prompt as the new run's first beat.
|
|
1090
|
+
*/
|
|
1091
|
+
clearPending() {
|
|
1064
1092
|
this.pendingChoice = null;
|
|
1065
1093
|
this.pendingPrompt = null;
|
|
1066
1094
|
this.pendingPromptOwnerId = null;
|
|
@@ -1157,7 +1185,7 @@ var Flow = class {
|
|
|
1157
1185
|
}
|
|
1158
1186
|
const from = frame.index;
|
|
1159
1187
|
while (frame.index < children.length && !this.eligible(children[frame.index])) frame.index++;
|
|
1160
|
-
if (this.host.
|
|
1188
|
+
if (this.host.tracing && frame.index !== from) {
|
|
1161
1189
|
this.emit({
|
|
1162
1190
|
type: "select",
|
|
1163
1191
|
group: frame.containerId,
|
|
@@ -1193,14 +1221,18 @@ var Flow = class {
|
|
|
1193
1221
|
clearLog() {
|
|
1194
1222
|
this.flowLog.length = 0;
|
|
1195
1223
|
}
|
|
1196
|
-
/** Record one decision, on this flow's log and the engine's
|
|
1197
|
-
*
|
|
1224
|
+
/** Record one decision, on this flow's log and the engine's, and hand it to any `onTrace`. Each call
|
|
1225
|
+
* site checks `host.tracing` first, so with nothing logging or tracing the entry is never built. */
|
|
1198
1226
|
emit(event) {
|
|
1199
1227
|
const scene = this.currentSceneId ?? void 0;
|
|
1200
1228
|
this.host.emitEngine(this.id, event, scene);
|
|
1201
1229
|
if (!this.host.logEnabled) return;
|
|
1202
1230
|
this.flowLog.push({ ...event, seq: this.flowSeq++, ...scene ? { scene } : {} });
|
|
1203
1231
|
}
|
|
1232
|
+
/** How many times this flow has entered each node, by node id. */
|
|
1233
|
+
getVisitCounts() {
|
|
1234
|
+
return Object.fromEntries(this.visitCounts);
|
|
1235
|
+
}
|
|
1204
1236
|
/** The options of a pending choice (empty when not at a choice point). */
|
|
1205
1237
|
getChoices() {
|
|
1206
1238
|
return this.pendingChoice?.options ?? [];
|
|
@@ -1214,7 +1246,7 @@ var Flow = class {
|
|
|
1214
1246
|
if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);
|
|
1215
1247
|
this.touch();
|
|
1216
1248
|
const node = choice.byId.get(id);
|
|
1217
|
-
this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
1249
|
+
if (this.host.tracing) this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
1218
1250
|
this.pendingChoice = null;
|
|
1219
1251
|
const authored = this.host.replayPromptOnChoose ? this.authoredPromptOf(node) : void 0;
|
|
1220
1252
|
this.pendingPrompt = authored && option.prompt ? { beat: authored, shown: { ...option.prompt } } : null;
|
|
@@ -1410,7 +1442,7 @@ var Flow = class {
|
|
|
1410
1442
|
byId.set(child.id, child);
|
|
1411
1443
|
}
|
|
1412
1444
|
if (options.some((o) => o.eligible)) {
|
|
1413
|
-
this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
|
|
1445
|
+
if (this.host.tracing) this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
|
|
1414
1446
|
this.pendingChoice = { groupId: group.id, options, byId };
|
|
1415
1447
|
return;
|
|
1416
1448
|
}
|
|
@@ -1419,7 +1451,7 @@ var Flow = class {
|
|
|
1419
1451
|
this.enterChild(fallback);
|
|
1420
1452
|
return;
|
|
1421
1453
|
}
|
|
1422
|
-
this.emit({ type: "dry", group: group.id });
|
|
1454
|
+
if (this.host.tracing) this.emit({ type: "dry", group: group.id });
|
|
1423
1455
|
this.host.onDryChoice?.(group.id);
|
|
1424
1456
|
}
|
|
1425
1457
|
// -- Jumps (jump / call-return) ----------------------------------------
|
|
@@ -1434,7 +1466,7 @@ var Flow = class {
|
|
|
1434
1466
|
* hard-ends the flow regardless of the callstack.
|
|
1435
1467
|
*/
|
|
1436
1468
|
enterTarget(to, mode) {
|
|
1437
|
-
this.emit({ type: "jump", to, mode });
|
|
1469
|
+
if (this.host.tracing) this.emit({ type: "jump", to, mode });
|
|
1438
1470
|
if (to === "END") {
|
|
1439
1471
|
this.flowEnded = true;
|
|
1440
1472
|
this.stack = [];
|
|
@@ -1471,7 +1503,7 @@ var Flow = class {
|
|
|
1471
1503
|
const order = group.options?.order ?? "sequential";
|
|
1472
1504
|
const exhaust = group.options?.exhaust ?? "once";
|
|
1473
1505
|
const trace = (picked) => {
|
|
1474
|
-
this.emit({
|
|
1506
|
+
if (this.host.tracing) this.emit({
|
|
1475
1507
|
type: "select",
|
|
1476
1508
|
group: group.id,
|
|
1477
1509
|
selector: group.selector ?? "default",
|
|
@@ -1636,9 +1668,9 @@ var Flow = class {
|
|
|
1636
1668
|
let value;
|
|
1637
1669
|
try {
|
|
1638
1670
|
value = this.evalExpr(e.value);
|
|
1639
|
-
const prev = this.host.
|
|
1671
|
+
const prev = this.host.tracing ? this.getProperty(e.target) : void 0;
|
|
1640
1672
|
this.writeProperty(e.target, value, false);
|
|
1641
|
-
this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
|
|
1673
|
+
if (this.host.tracing) this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
|
|
1642
1674
|
} catch (err) {
|
|
1643
1675
|
this.reportError("effect", owner, e.value, err);
|
|
1644
1676
|
}
|
|
@@ -1659,7 +1691,7 @@ var Flow = class {
|
|
|
1659
1691
|
const message = err instanceof Error ? err.message : String(err);
|
|
1660
1692
|
const source = expr?.src;
|
|
1661
1693
|
this.host.onError({ flow: this.id, kind, node, ...source ? { source } : {}, message });
|
|
1662
|
-
this.emit({ type: "diagnostic", kind, node, ...source ? { source } : {}, message });
|
|
1694
|
+
if (this.host.tracing) this.emit({ type: "diagnostic", kind, node, ...source ? { source } : {}, message });
|
|
1663
1695
|
}
|
|
1664
1696
|
evalExpr(expr) {
|
|
1665
1697
|
return evaluate(this.conditionAst(expr), this.context(), patterDialect);
|
|
@@ -1946,6 +1978,21 @@ function mountClaims(registry, key, bag, ask) {
|
|
|
1946
1978
|
function toDecl(decl) {
|
|
1947
1979
|
return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };
|
|
1948
1980
|
}
|
|
1981
|
+
function boundScopes(options) {
|
|
1982
|
+
return { ...options.world ? { world: options.world } : {}, ...options.hostScopes };
|
|
1983
|
+
}
|
|
1984
|
+
function ladders(host, cacheKey, decls) {
|
|
1985
|
+
let found = host.qualityLadders.get(cacheKey);
|
|
1986
|
+
if (!found) {
|
|
1987
|
+
found = /* @__PURE__ */ new Map();
|
|
1988
|
+
for (const d of decls() ?? []) {
|
|
1989
|
+
const k = d.name.toLowerCase();
|
|
1990
|
+
if (d.type === "quality" && d.stages && !found.has(k)) found.set(k, d.stages);
|
|
1991
|
+
}
|
|
1992
|
+
host.qualityLadders.set(cacheKey, found);
|
|
1993
|
+
}
|
|
1994
|
+
return found;
|
|
1995
|
+
}
|
|
1949
1996
|
function toForeignDecl(decl) {
|
|
1950
1997
|
return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default, writable: decl.writable };
|
|
1951
1998
|
}
|