@patterkit/runtime 0.18.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/dist/index.d.cts CHANGED
@@ -127,12 +127,14 @@ interface ChoiceOption {
127
127
  gameData?: GameData;
128
128
  }
129
129
  /**
130
- * The host's **World Properties** resolver: a `{ get, set? }` the game provides so the story can read
131
- * (and, if you allow it, write) its `@world.*` values at runtime. Property metadata (types, read-only)
132
- * comes from the compiled bundle's declared world properties; the values themselves live in the host and
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 WorldResolver = ScopeResolver;
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.
@@ -194,12 +196,36 @@ type TraceEvent = {
194
196
  value: ScalarValue;
195
197
  prev?: ScalarValue;
196
198
  }
197
- /** An expression that would not evaluate: never a silent pass, always visible. */
199
+ /** Content the engine could not evaluate and played through (see {@link PlayError}): a condition
200
+ * counted as false, an effect skipped, or a Best-match part scored as false. */
198
201
  | {
199
202
  type: "diagnostic";
200
- where: string;
203
+ kind: PlayError["kind"];
204
+ node: string;
205
+ source?: string;
201
206
  message: string;
202
207
  };
208
+ /**
209
+ * A content error the engine played through. Content can fail at run time in ways the compiler cannot
210
+ * see: a division by zero, a host value of the wrong type, a story write to a read-only `@world` value.
211
+ * The story never stops for one. A condition that fails counts as false; an effect that fails is skipped
212
+ * and the rest of its list still runs; a part of a Best-match condition that fails scores as false. Each
213
+ * is reported, through `EngineOptions.onError` and as a `diagnostic` entry in the decision log. The same
214
+ * rule holds on all four runtimes, including the two whose languages cannot throw.
215
+ */
216
+ interface PlayError {
217
+ /** The flow it happened in. */
218
+ flow: string;
219
+ /** What failed: a condition, an effect, or a part of a condition being scored for Best match. */
220
+ kind: "condition" | "effect" | "best-match";
221
+ /** The snippet, group, or option whose condition failed, or that owns the effect (a scene, for its onEntry). */
222
+ node: string;
223
+ /** The expression's source text, when the bundle carries it. */
224
+ source?: string;
225
+ message: string;
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. */
203
229
  type TraceHandler = (event: TraceEvent) => void;
204
230
  /** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
205
231
  type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
@@ -227,14 +253,18 @@ interface EngineOptions {
227
253
  /** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.
228
254
  * Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
229
255
  locale?: string;
230
- /** The host's resolver for **World Properties** (`@world.*`): the values the game owns and the story
231
- * reads. Omit it and the runtime self-backs `@world` from the declared defaults, as a property the
232
- * registry stores and saves. Shared by all flows. A game running several engines registers `@world`
233
- * in its registry itself instead, and no engine is given a resolver. */
234
- world?: WorldResolver;
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;
235
265
  /** The game's registry: ONE per game, holding every engine's properties except those the game keeps
236
266
  * itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,
237
- * its per-flow and per-scene bags under keys starting `patter/`, and `@world` if `world` is passed),
267
+ * its per-flow and per-scene bags under keys starting `patter/`, and each scope `hostScopes` binds),
238
268
  * reads every other scope from it, and `saveGame()` leaves the property values to the game. Host
239
269
  * scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should
240
270
  * store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as
@@ -256,6 +286,10 @@ interface EngineOptions {
256
286
  * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
257
287
  * that ran dry. Leave it unset in shipped games (zero cost). */
258
288
  onDryChoice?: (groupId: string) => void;
289
+ /** Called with each content error the engine played through (see {@link PlayError}): a condition
290
+ * that failed and counted as false, an effect that failed and was skipped. Unset, each is written
291
+ * to `console.warn`, so a content bug is never silent. */
292
+ onError?: (error: PlayError) => void;
259
293
  /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through
260
294
  * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a
261
295
  * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful
@@ -298,8 +332,13 @@ declare class Checkpoint {
298
332
  }
299
333
  /** Shared, read-mostly context the engine hands to every flow it owns. */
300
334
  interface FlowHost {
301
- /** True when the run asked for a log; flows skip building entries otherwise. */
335
+ /** True when the run asked for a log. */
302
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;
303
342
  /** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */
304
343
  emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;
305
344
  bundle: Bundle;
@@ -365,6 +404,8 @@ interface FlowHost {
365
404
  * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
366
405
  * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
367
406
  onDryChoice?: (groupId: string) => void;
407
+ /** Where a content error the engine played through is reported (see PlayError). */
408
+ onError: (error: PlayError) => void;
368
409
  /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's
369
410
  * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */
370
411
  refSplitCache: Map<string, {
@@ -391,11 +432,13 @@ declare class Engine {
391
432
  private readonly sceneGameIdToId;
392
433
  private readonly blockGameIdToId;
393
434
  /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
394
- * engine keeps the same world resolver, custom RNG, and diagnostic hooks. */
435
+ * engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
395
436
  private readonly creationOptions;
396
437
  /** The run's ordered stream: every flow's events, each naming its flow. Empty and
397
438
  * unwritten unless `options.log` asked for it. */
398
439
  private readonly engineLog;
440
+ /** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */
441
+ private engineSeq;
399
442
  private readonly engineTraceHandlers;
400
443
  constructor(bundle: Bundle, options?: EngineOptions);
401
444
  /** The active locale (string + character-name lookups resolve in it). */
@@ -403,6 +446,9 @@ declare class Engine {
403
446
  /** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),
404
447
  * not a shippable localised build. An IDs-only ship build is `false`. */
405
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;
406
452
  /**
407
453
  * Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
408
454
  * string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
@@ -501,9 +547,12 @@ declare class Engine {
501
547
  * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.
502
548
  */
503
549
  private resolveOpenAddress;
504
- /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
550
+ /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
551
+ * unknown one passes through, for the caller to find nothing. */
505
552
  private resolveSceneRef;
506
- /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
553
+ /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
554
+ * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
555
+ * since a block's gameId is only unique within its scene. */
507
556
  private resolveBlockRef;
508
557
  /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
509
558
  * The inverse of the resolve helpers - for a host that wants to display / log the address. */
@@ -644,6 +693,9 @@ declare class Engine {
644
693
  * retained. Returns its own unsubscribe. */
645
694
  onTrace(handler: EngineTraceHandler): () => void;
646
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>;
647
699
  listProperties(): PropertyRow[];
648
700
  private splitShared;
649
701
  /** The shared `@patter` values alone, as `{ patter: {...} }`.
@@ -711,15 +763,20 @@ declare class Flow {
711
763
  /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
712
764
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
713
765
  private stagesFor;
714
- /** Begin this flow at a scene (and optionally a specific block within it). */
715
- start(sceneId?: string, blockId?: string): void;
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;
716
770
  /**
717
771
  * Forget everything in this flow and begin again - its per-flow state (not-shared
718
772
  * `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
719
773
  * visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
720
- * untouched. A clearer-named alias of `start()`.
774
+ * untouched. The one public way to begin a flow again (openFlow begins a new one).
721
775
  */
722
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;
723
780
  /**
724
781
  * Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
725
782
  * `onEntry` effects run, entering counts as a visit, and the callstack is REPLACED - any pending
@@ -749,6 +806,13 @@ declare class Flow {
749
806
  * cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.
750
807
  */
751
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;
752
816
  /** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
753
817
  * put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
754
818
  * Its visits, selector cursors, and property values are recorded change by change as they happen. */
@@ -787,9 +851,11 @@ declare class Flow {
787
851
  log(): readonly LogEntry[];
788
852
  /** Drop the retained entries. `seq` keeps counting, so order survives a clear. */
789
853
  clearLog(): void;
790
- /** Record one decision, on this flow's log and the engine's. Cheap to call with logging
791
- * off: the entry is never built. */
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. */
792
856
  private emit;
857
+ /** How many times this flow has entered each node, by node id. */
858
+ getVisitCounts(): Record<string, number>;
793
859
  /** The options of a pending choice (empty when not at a choice point). */
794
860
  getChoices(): ChoiceOption[];
795
861
  /** Pick an eligible option by id; the next `advance()` runs it. */
@@ -870,8 +936,12 @@ declare class Flow {
870
936
  private matchedSpec;
871
937
  /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
872
938
  private selectorState;
939
+ /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
873
940
  private runEffects;
941
+ /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
874
942
  private eligible;
943
+ /** Report a content error the engine is playing through: to the host's onError, and to the log. */
944
+ private reportError;
875
945
  private evalExpr;
876
946
  /** The deserialised (in-memory) AST for an expression, cached per Expression. Shared by the
877
947
  * evaluator and the Best-match specificity walker so both work off one parse. */
@@ -1095,11 +1165,11 @@ declare function gameDataValue(fields: GameDataField[], node: GameData | undefin
1095
1165
  declare function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData;
1096
1166
 
1097
1167
  /**
1098
- * Map every node id (scene / block / group / snippet / beat) to its accumulated
1168
+ * Map every node id (scene / block / group / snippet / beat, prompt beats included) to its accumulated
1099
1169
  * tags. Node ids are globally unique within a project (the validator enforces
1100
1170
  * it), so one flat map suffices. Nodes with no tags anywhere up the chain map to
1101
1171
  * an empty array.
1102
1172
  */
1103
1173
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
1104
1174
 
1105
- 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 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
- * The host's **World Properties** resolver: a `{ get, set? }` the game provides so the story can read
131
- * (and, if you allow it, write) its `@world.*` values at runtime. Property metadata (types, read-only)
132
- * comes from the compiled bundle's declared world properties; the values themselves live in the host and
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 WorldResolver = ScopeResolver;
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.
@@ -194,12 +196,36 @@ type TraceEvent = {
194
196
  value: ScalarValue;
195
197
  prev?: ScalarValue;
196
198
  }
197
- /** An expression that would not evaluate: never a silent pass, always visible. */
199
+ /** Content the engine could not evaluate and played through (see {@link PlayError}): a condition
200
+ * counted as false, an effect skipped, or a Best-match part scored as false. */
198
201
  | {
199
202
  type: "diagnostic";
200
- where: string;
203
+ kind: PlayError["kind"];
204
+ node: string;
205
+ source?: string;
201
206
  message: string;
202
207
  };
208
+ /**
209
+ * A content error the engine played through. Content can fail at run time in ways the compiler cannot
210
+ * see: a division by zero, a host value of the wrong type, a story write to a read-only `@world` value.
211
+ * The story never stops for one. A condition that fails counts as false; an effect that fails is skipped
212
+ * and the rest of its list still runs; a part of a Best-match condition that fails scores as false. Each
213
+ * is reported, through `EngineOptions.onError` and as a `diagnostic` entry in the decision log. The same
214
+ * rule holds on all four runtimes, including the two whose languages cannot throw.
215
+ */
216
+ interface PlayError {
217
+ /** The flow it happened in. */
218
+ flow: string;
219
+ /** What failed: a condition, an effect, or a part of a condition being scored for Best match. */
220
+ kind: "condition" | "effect" | "best-match";
221
+ /** The snippet, group, or option whose condition failed, or that owns the effect (a scene, for its onEntry). */
222
+ node: string;
223
+ /** The expression's source text, when the bundle carries it. */
224
+ source?: string;
225
+ message: string;
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. */
203
229
  type TraceHandler = (event: TraceEvent) => void;
204
230
  /** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
205
231
  type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
@@ -227,14 +253,18 @@ interface EngineOptions {
227
253
  /** Active locale for string lookups (embedded localisation). Defaults to the bundle's default locale.
228
254
  * Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
229
255
  locale?: string;
230
- /** The host's resolver for **World Properties** (`@world.*`): the values the game owns and the story
231
- * reads. Omit it and the runtime self-backs `@world` from the declared defaults, as a property the
232
- * registry stores and saves. Shared by all flows. A game running several engines registers `@world`
233
- * in its registry itself instead, and no engine is given a resolver. */
234
- world?: WorldResolver;
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;
235
265
  /** The game's registry: ONE per game, holding every engine's properties except those the game keeps
236
266
  * itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,
237
- * its per-flow and per-scene bags under keys starting `patter/`, and `@world` if `world` is passed),
267
+ * its per-flow and per-scene bags under keys starting `patter/`, and each scope `hostScopes` binds),
238
268
  * reads every other scope from it, and `saveGame()` leaves the property values to the game. Host
239
269
  * scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should
240
270
  * store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as
@@ -256,6 +286,10 @@ interface EngineOptions {
256
286
  * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
257
287
  * that ran dry. Leave it unset in shipped games (zero cost). */
258
288
  onDryChoice?: (groupId: string) => void;
289
+ /** Called with each content error the engine played through (see {@link PlayError}): a condition
290
+ * that failed and counted as false, an effect that failed and was skipped. Unset, each is written
291
+ * to `console.warn`, so a content bug is never silent. */
292
+ onError?: (error: PlayError) => void;
259
293
  /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through
260
294
  * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a
261
295
  * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful
@@ -298,8 +332,13 @@ declare class Checkpoint {
298
332
  }
299
333
  /** Shared, read-mostly context the engine hands to every flow it owns. */
300
334
  interface FlowHost {
301
- /** True when the run asked for a log; flows skip building entries otherwise. */
335
+ /** True when the run asked for a log. */
302
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;
303
342
  /** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */
304
343
  emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;
305
344
  bundle: Bundle;
@@ -365,6 +404,8 @@ interface FlowHost {
365
404
  * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
366
405
  * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
367
406
  onDryChoice?: (groupId: string) => void;
407
+ /** Where a content error the engine played through is reported (see PlayError). */
408
+ onError: (error: PlayError) => void;
368
409
  /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's
369
410
  * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */
370
411
  refSplitCache: Map<string, {
@@ -391,11 +432,13 @@ declare class Engine {
391
432
  private readonly sceneGameIdToId;
392
433
  private readonly blockGameIdToId;
393
434
  /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
394
- * engine keeps the same world resolver, custom RNG, and diagnostic hooks. */
435
+ * engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
395
436
  private readonly creationOptions;
396
437
  /** The run's ordered stream: every flow's events, each naming its flow. Empty and
397
438
  * unwritten unless `options.log` asked for it. */
398
439
  private readonly engineLog;
440
+ /** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */
441
+ private engineSeq;
399
442
  private readonly engineTraceHandlers;
400
443
  constructor(bundle: Bundle, options?: EngineOptions);
401
444
  /** The active locale (string + character-name lookups resolve in it). */
@@ -403,6 +446,9 @@ declare class Engine {
403
446
  /** True for a source-only DEBUG build: the embedded strings are the source language (for debugging),
404
447
  * not a shippable localised build. An IDs-only ship build is `false`. */
405
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;
406
452
  /**
407
453
  * Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
408
454
  * string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
@@ -501,9 +547,12 @@ declare class Engine {
501
547
  * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.
502
548
  */
503
549
  private resolveOpenAddress;
504
- /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
550
+ /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
551
+ * unknown one passes through, for the caller to find nothing. */
505
552
  private resolveSceneRef;
506
- /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
553
+ /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
554
+ * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
555
+ * since a block's gameId is only unique within its scene. */
507
556
  private resolveBlockRef;
508
557
  /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
509
558
  * The inverse of the resolve helpers - for a host that wants to display / log the address. */
@@ -644,6 +693,9 @@ declare class Engine {
644
693
  * retained. Returns its own unsubscribe. */
645
694
  onTrace(handler: EngineTraceHandler): () => void;
646
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>;
647
699
  listProperties(): PropertyRow[];
648
700
  private splitShared;
649
701
  /** The shared `@patter` values alone, as `{ patter: {...} }`.
@@ -711,15 +763,20 @@ declare class Flow {
711
763
  /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
712
764
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
713
765
  private stagesFor;
714
- /** Begin this flow at a scene (and optionally a specific block within it). */
715
- start(sceneId?: string, blockId?: string): void;
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;
716
770
  /**
717
771
  * Forget everything in this flow and begin again - its per-flow state (not-shared
718
772
  * `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
719
773
  * visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
720
- * untouched. A clearer-named alias of `start()`.
774
+ * untouched. The one public way to begin a flow again (openFlow begins a new one).
721
775
  */
722
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;
723
780
  /**
724
781
  * Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
725
782
  * `onEntry` effects run, entering counts as a visit, and the callstack is REPLACED - any pending
@@ -749,6 +806,13 @@ declare class Flow {
749
806
  * cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.
750
807
  */
751
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;
752
816
  /** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
753
817
  * put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
754
818
  * Its visits, selector cursors, and property values are recorded change by change as they happen. */
@@ -787,9 +851,11 @@ declare class Flow {
787
851
  log(): readonly LogEntry[];
788
852
  /** Drop the retained entries. `seq` keeps counting, so order survives a clear. */
789
853
  clearLog(): void;
790
- /** Record one decision, on this flow's log and the engine's. Cheap to call with logging
791
- * off: the entry is never built. */
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. */
792
856
  private emit;
857
+ /** How many times this flow has entered each node, by node id. */
858
+ getVisitCounts(): Record<string, number>;
793
859
  /** The options of a pending choice (empty when not at a choice point). */
794
860
  getChoices(): ChoiceOption[];
795
861
  /** Pick an eligible option by id; the next `advance()` runs it. */
@@ -870,8 +936,12 @@ declare class Flow {
870
936
  private matchedSpec;
871
937
  /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
872
938
  private selectorState;
939
+ /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
873
940
  private runEffects;
941
+ /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
874
942
  private eligible;
943
+ /** Report a content error the engine is playing through: to the host's onError, and to the log. */
944
+ private reportError;
875
945
  private evalExpr;
876
946
  /** The deserialised (in-memory) AST for an expression, cached per Expression. Shared by the
877
947
  * evaluator and the Best-match specificity walker so both work off one parse. */
@@ -1095,11 +1165,11 @@ declare function gameDataValue(fields: GameDataField[], node: GameData | undefin
1095
1165
  declare function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData;
1096
1166
 
1097
1167
  /**
1098
- * Map every node id (scene / block / group / snippet / beat) to its accumulated
1168
+ * Map every node id (scene / block / group / snippet / beat, prompt beats included) to its accumulated
1099
1169
  * tags. Node ids are globally unique within a project (the validator enforces
1100
1170
  * it), so one flat map suffices. Nodes with no tags anywhere up the chain map to
1101
1171
  * an empty array.
1102
1172
  */
1103
1173
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
1104
1174
 
1105
- 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 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 };