@patterkit/runtime 0.17.0 → 0.19.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
@@ -194,12 +194,34 @@ type TraceEvent = {
194
194
  value: ScalarValue;
195
195
  prev?: ScalarValue;
196
196
  }
197
- /** An expression that would not evaluate: never a silent pass, always visible. */
197
+ /** Content the engine could not evaluate and played through (see {@link PlayError}): a condition
198
+ * counted as false, an effect skipped, or a Best-match part scored as false. */
198
199
  | {
199
200
  type: "diagnostic";
200
- where: string;
201
+ kind: PlayError["kind"];
202
+ node: string;
203
+ source?: string;
201
204
  message: string;
202
205
  };
206
+ /**
207
+ * A content error the engine played through. Content can fail at run time in ways the compiler cannot
208
+ * see: a division by zero, a host value of the wrong type, a story write to a read-only `@world` value.
209
+ * The story never stops for one. A condition that fails counts as false; an effect that fails is skipped
210
+ * and the rest of its list still runs; a part of a Best-match condition that fails scores as false. Each
211
+ * is reported, through `EngineOptions.onError` and as a `diagnostic` entry in the decision log. The same
212
+ * rule holds on all four runtimes, including the two whose languages cannot throw.
213
+ */
214
+ interface PlayError {
215
+ /** The flow it happened in. */
216
+ flow: string;
217
+ /** What failed: a condition, an effect, or a part of a condition being scored for Best match. */
218
+ kind: "condition" | "effect" | "best-match";
219
+ /** The snippet, group, or option whose condition failed, or that owns the effect (a scene, for its onEntry). */
220
+ node: string;
221
+ /** The expression's source text, when the bundle carries it. */
222
+ source?: string;
223
+ message: string;
224
+ }
203
225
  type TraceHandler = (event: TraceEvent) => void;
204
226
  /** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
205
227
  type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
@@ -256,6 +278,10 @@ interface EngineOptions {
256
278
  * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
257
279
  * that ran dry. Leave it unset in shipped games (zero cost). */
258
280
  onDryChoice?: (groupId: string) => void;
281
+ /** Called with each content error the engine played through (see {@link PlayError}): a condition
282
+ * that failed and counted as false, an effect that failed and was skipped. Unset, each is written
283
+ * to `console.warn`, so a content bug is never silent. */
284
+ onError?: (error: PlayError) => void;
259
285
  /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through
260
286
  * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a
261
287
  * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful
@@ -365,6 +391,8 @@ interface FlowHost {
365
391
  * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
366
392
  * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
367
393
  onDryChoice?: (groupId: string) => void;
394
+ /** Where a content error the engine played through is reported (see PlayError). */
395
+ onError: (error: PlayError) => void;
368
396
  /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's
369
397
  * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */
370
398
  refSplitCache: Map<string, {
@@ -396,6 +424,8 @@ declare class Engine {
396
424
  /** The run's ordered stream: every flow's events, each naming its flow. Empty and
397
425
  * unwritten unless `options.log` asked for it. */
398
426
  private readonly engineLog;
427
+ /** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */
428
+ private engineSeq;
399
429
  private readonly engineTraceHandlers;
400
430
  constructor(bundle: Bundle, options?: EngineOptions);
401
431
  /** The active locale (string + character-name lookups resolve in it). */
@@ -501,9 +531,12 @@ declare class Engine {
501
531
  * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.
502
532
  */
503
533
  private resolveOpenAddress;
504
- /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
534
+ /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
535
+ * unknown one passes through, for the caller to find nothing. */
505
536
  private resolveSceneRef;
506
- /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
537
+ /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
538
+ * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
539
+ * since a block's gameId is only unique within its scene. */
507
540
  private resolveBlockRef;
508
541
  /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
509
542
  * The inverse of the resolve helpers - for a host that wants to display / log the address. */
@@ -684,10 +717,13 @@ declare class Flow {
684
717
  private activeSnippet;
685
718
  private beatIndex;
686
719
  private pendingChoice;
687
- /** When `replayPromptOnChoose`, the chosen option's prompt beat to deliver before its content. */
688
- private pendingPromptBeat;
689
- /** The chosen option that owns `pendingPromptBeat`, so a save taken between choose() and the next
690
- * advance() can re-derive the prompt on load (the beat isn't otherwise reachable by id). */
720
+ /** When `replayPromptOnChoose`, the chosen option's AUTHORED prompt to deliver before its content: its
721
+ * beat (id, gameData, tags) and the prompt exactly as the choice showed it (text and speaker fields).
722
+ * `shown` is missing only after loading a save written before saves carried it: the beat is then
723
+ * resolved when it is delivered, as it always was. */
724
+ private pendingPrompt;
725
+ /** The chosen option that owns `pendingPrompt`, so a save taken between choose() and the next
726
+ * advance() can find the prompt beat again on load (it isn't otherwise reachable by id). */
691
727
  private pendingPromptOwnerId;
692
728
  private selectors;
693
729
  /** Per-node entry counts for this flow (node id -> times entered). */
@@ -867,8 +903,12 @@ declare class Flow {
867
903
  private matchedSpec;
868
904
  /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
869
905
  private selectorState;
906
+ /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
870
907
  private runEffects;
908
+ /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
871
909
  private eligible;
910
+ /** Report a content error the engine is playing through: to the host's onError, and to the log. */
911
+ private reportError;
872
912
  private evalExpr;
873
913
  /** The deserialised (in-memory) AST for an expression, cached per Expression. Shared by the
874
914
  * evaluator and the Best-match specificity walker so both work off one parse. */
@@ -907,6 +947,11 @@ declare class Flow {
907
947
  * option's first content line. NO look-ahead. Undefined only when even that is absent.
908
948
  */
909
949
  private promptFor;
950
+ /** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */
951
+ private authoredPromptOf;
952
+ /** A replayed prompt as a step: the beat's id, gameData, and tags, and the text and speaker fields the
953
+ * choice showed. */
954
+ private promptResult;
910
955
  /** The prompt BEAT of an option: the Option group's `prompt`, else (tolerance) its first content line. */
911
956
  private promptBeatOf;
912
957
  /** The first snippet with a line/text beat within a child list, depth-first in authored order. */
@@ -1087,11 +1132,11 @@ declare function gameDataValue(fields: GameDataField[], node: GameData | undefin
1087
1132
  declare function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData;
1088
1133
 
1089
1134
  /**
1090
- * Map every node id (scene / block / group / snippet / beat) to its accumulated
1135
+ * Map every node id (scene / block / group / snippet / beat, prompt beats included) to its accumulated
1091
1136
  * tags. Node ids are globally unique within a project (the validator enforces
1092
1137
  * it), so one flat map suffices. Nodes with no tags anywhere up the chain map to
1093
1138
  * an empty array.
1094
1139
  */
1095
1140
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
1096
1141
 
1097
- 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -194,12 +194,34 @@ type TraceEvent = {
194
194
  value: ScalarValue;
195
195
  prev?: ScalarValue;
196
196
  }
197
- /** An expression that would not evaluate: never a silent pass, always visible. */
197
+ /** Content the engine could not evaluate and played through (see {@link PlayError}): a condition
198
+ * counted as false, an effect skipped, or a Best-match part scored as false. */
198
199
  | {
199
200
  type: "diagnostic";
200
- where: string;
201
+ kind: PlayError["kind"];
202
+ node: string;
203
+ source?: string;
201
204
  message: string;
202
205
  };
206
+ /**
207
+ * A content error the engine played through. Content can fail at run time in ways the compiler cannot
208
+ * see: a division by zero, a host value of the wrong type, a story write to a read-only `@world` value.
209
+ * The story never stops for one. A condition that fails counts as false; an effect that fails is skipped
210
+ * and the rest of its list still runs; a part of a Best-match condition that fails scores as false. Each
211
+ * is reported, through `EngineOptions.onError` and as a `diagnostic` entry in the decision log. The same
212
+ * rule holds on all four runtimes, including the two whose languages cannot throw.
213
+ */
214
+ interface PlayError {
215
+ /** The flow it happened in. */
216
+ flow: string;
217
+ /** What failed: a condition, an effect, or a part of a condition being scored for Best match. */
218
+ kind: "condition" | "effect" | "best-match";
219
+ /** The snippet, group, or option whose condition failed, or that owns the effect (a scene, for its onEntry). */
220
+ node: string;
221
+ /** The expression's source text, when the bundle carries it. */
222
+ source?: string;
223
+ message: string;
224
+ }
203
225
  type TraceHandler = (event: TraceEvent) => void;
204
226
  /** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
205
227
  type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
@@ -256,6 +278,10 @@ interface EngineOptions {
256
278
  * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
257
279
  * that ran dry. Leave it unset in shipped games (zero cost). */
258
280
  onDryChoice?: (groupId: string) => void;
281
+ /** Called with each content error the engine played through (see {@link PlayError}): a condition
282
+ * that failed and counted as false, an effect that failed and was skipped. Unset, each is written
283
+ * to `console.warn`, so a content bug is never silent. */
284
+ onError?: (error: PlayError) => void;
259
285
  /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through
260
286
  * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a
261
287
  * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful
@@ -365,6 +391,8 @@ interface FlowHost {
365
391
  * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
366
392
  * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
367
393
  onDryChoice?: (groupId: string) => void;
394
+ /** Where a content error the engine played through is reported (see PlayError). */
395
+ onError: (error: PlayError) => void;
368
396
  /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's
369
397
  * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */
370
398
  refSplitCache: Map<string, {
@@ -396,6 +424,8 @@ declare class Engine {
396
424
  /** The run's ordered stream: every flow's events, each naming its flow. Empty and
397
425
  * unwritten unless `options.log` asked for it. */
398
426
  private readonly engineLog;
427
+ /** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */
428
+ private engineSeq;
399
429
  private readonly engineTraceHandlers;
400
430
  constructor(bundle: Bundle, options?: EngineOptions);
401
431
  /** The active locale (string + character-name lookups resolve in it). */
@@ -501,9 +531,12 @@ declare class Engine {
501
531
  * is an internal id from any scene. Neither: the bundle's first scene. The same rule on every runtime.
502
532
  */
503
533
  private resolveOpenAddress;
504
- /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
534
+ /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
535
+ * unknown one passes through, for the caller to find nothing. */
505
536
  private resolveSceneRef;
506
- /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
537
+ /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
538
+ * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
539
+ * since a block's gameId is only unique within its scene. */
507
540
  private resolveBlockRef;
508
541
  /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
509
542
  * The inverse of the resolve helpers - for a host that wants to display / log the address. */
@@ -684,10 +717,13 @@ declare class Flow {
684
717
  private activeSnippet;
685
718
  private beatIndex;
686
719
  private pendingChoice;
687
- /** When `replayPromptOnChoose`, the chosen option's prompt beat to deliver before its content. */
688
- private pendingPromptBeat;
689
- /** The chosen option that owns `pendingPromptBeat`, so a save taken between choose() and the next
690
- * advance() can re-derive the prompt on load (the beat isn't otherwise reachable by id). */
720
+ /** When `replayPromptOnChoose`, the chosen option's AUTHORED prompt to deliver before its content: its
721
+ * beat (id, gameData, tags) and the prompt exactly as the choice showed it (text and speaker fields).
722
+ * `shown` is missing only after loading a save written before saves carried it: the beat is then
723
+ * resolved when it is delivered, as it always was. */
724
+ private pendingPrompt;
725
+ /** The chosen option that owns `pendingPrompt`, so a save taken between choose() and the next
726
+ * advance() can find the prompt beat again on load (it isn't otherwise reachable by id). */
691
727
  private pendingPromptOwnerId;
692
728
  private selectors;
693
729
  /** Per-node entry counts for this flow (node id -> times entered). */
@@ -867,8 +903,12 @@ declare class Flow {
867
903
  private matchedSpec;
868
904
  /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
869
905
  private selectorState;
906
+ /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
870
907
  private runEffects;
908
+ /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
871
909
  private eligible;
910
+ /** Report a content error the engine is playing through: to the host's onError, and to the log. */
911
+ private reportError;
872
912
  private evalExpr;
873
913
  /** The deserialised (in-memory) AST for an expression, cached per Expression. Shared by the
874
914
  * evaluator and the Best-match specificity walker so both work off one parse. */
@@ -907,6 +947,11 @@ declare class Flow {
907
947
  * option's first content line. NO look-ahead. Undefined only when even that is absent.
908
948
  */
909
949
  private promptFor;
950
+ /** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */
951
+ private authoredPromptOf;
952
+ /** A replayed prompt as a step: the beat's id, gameData, and tags, and the text and speaker fields the
953
+ * choice showed. */
954
+ private promptResult;
910
955
  /** The prompt BEAT of an option: the Option group's `prompt`, else (tolerance) its first content line. */
911
956
  private promptBeatOf;
912
957
  /** The first snippet with a line/text beat within a child list, depth-first in authored order. */
@@ -1087,11 +1132,11 @@ declare function gameDataValue(fields: GameDataField[], node: GameData | undefin
1087
1132
  declare function effectiveGameData(fields: GameDataField[], node: GameData | undefined): GameData;
1088
1133
 
1089
1134
  /**
1090
- * Map every node id (scene / block / group / snippet / beat) to its accumulated
1135
+ * Map every node id (scene / block / group / snippet / beat, prompt beats included) to its accumulated
1091
1136
  * tags. Node ids are globally unique within a project (the validator enforces
1092
1137
  * it), so one flat map suffices. Nodes with no tags anywhere up the chain map to
1093
1138
  * an empty array.
1094
1139
  */
1095
1140
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
1096
1141
 
1097
- 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 };
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 };