@patterkit/runtime 0.7.1 → 0.9.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
@@ -1,5 +1,6 @@
1
1
  import { ScalarValue } from '@wildwinter/expr';
2
- import { ScopeResolver, ScopeRegistry, ScopeDeclaration } from '@wildwinter/scoperegistry';
2
+ import { ScopeResolver, ScopeRegistry, ScopeDeclaration, PropertyBag, PropertyRow } from '@wildwinter/scoperegistry';
3
+ export { PropertyRow } from '@wildwinter/scoperegistry';
3
4
  import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
4
5
  export { Bundle } from '@patterkit/model';
5
6
 
@@ -196,6 +197,88 @@ interface ChoiceOption {
196
197
  * are never stored or saved by this engine.
197
198
  */
198
199
  type WorldResolver = ScopeResolver;
200
+ /** What the engine DECIDED, as opposed to what it produced. A step tells you the line that
201
+ * played; a trace event tells you why that line and not its siblings - which children were
202
+ * eligible, where the selector's cursor stood, which choice options were live.
203
+ *
204
+ * The reasoning is IN the entry, not implied by it. `select` carries every child it looked at
205
+ * with its own verdict, the way a deal in the Storylet Engine carries every card it considered.
206
+ * A log that recorded only the winner would answer "what happened" and not "why", and "why"
207
+ * is the question a dialogue author cannot otherwise answer: the line you expected is missing
208
+ * and nothing anywhere says which condition dropped it.
209
+ *
210
+ * Patter's vocabulary, not the Storylet Engine's: there are no cards or hands here. The
211
+ * structure is shared (an ordered retained log, `seq`, a flow-level and an engine-level tap);
212
+ * the events are this engine's own. */
213
+ type TraceEvent = {
214
+ type: "select";
215
+ /** The group whose children were being chosen among. */
216
+ group: string;
217
+ /** `branch` | `sequence` | `run` | `choice`, and for `sequence` its order/exhaust policy. */
218
+ selector: string;
219
+ order?: string;
220
+ exhaust?: string;
221
+ /** Every child considered, in authored order, each with why it was or was not takeable. */
222
+ children: {
223
+ id: string;
224
+ eligible: boolean;
225
+ }[];
226
+ /** The child selected, or null when nothing was takeable. */
227
+ picked: string | null;
228
+ } | {
229
+ type: "choice";
230
+ group: string;
231
+ /** Every option the group offered, with the ones a condition dropped marked. */
232
+ options: {
233
+ id: string;
234
+ eligible: boolean;
235
+ }[];
236
+ }
237
+ /** A choice the player answered. `option` is what they took. */
238
+ | {
239
+ type: "chose";
240
+ group: string;
241
+ option: string;
242
+ }
243
+ /** A choice with no takeable option and no eligible fallback, which falls through
244
+ * silently. Also reported through `onDryChoice`, which stays: a log is an audit read
245
+ * afterwards, that callback is live feedback a host acts on. */
246
+ | {
247
+ type: "dry";
248
+ group: string;
249
+ } | {
250
+ type: "jump";
251
+ to: string;
252
+ mode: "jump" | "call";
253
+ }
254
+ /** One landed effect; `prev` is the value it replaced, so a reader can say "0 -> 1". */
255
+ | {
256
+ type: "write";
257
+ target: string;
258
+ value: ScalarValue;
259
+ prev?: ScalarValue;
260
+ }
261
+ /** An expression that would not evaluate: never a silent pass, always visible. */
262
+ | {
263
+ type: "diagnostic";
264
+ where: string;
265
+ message: string;
266
+ };
267
+ type TraceHandler = (event: TraceEvent) => void;
268
+ /** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
269
+ type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
270
+ /** A retained entry: the event plus its place in flow time. `seq` is monotonic across the
271
+ * flow and survives clearLog, so two reads of the log agree about order. */
272
+ type LogEntry = TraceEvent & {
273
+ seq: number;
274
+ scene?: string;
275
+ };
276
+ /** One entry on the ENGINE's log: the same event plus the flow it happened in. A run is
277
+ * several flows over shared state, so "what happened in this run" is only answerable in one
278
+ * ordered stream, and only if each line says who. */
279
+ type EngineLogEntry = LogEntry & {
280
+ flow: string;
281
+ };
199
282
  interface EngineOptions {
200
283
  /**
201
284
  * Custom float-in-[0,1) source for `random()` / shuffle, shared by all flows.
@@ -227,17 +310,11 @@ interface EngineOptions {
227
310
  * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
228
311
  * that ran dry. Leave it unset in shipped games (zero cost). */
229
312
  onDryChoice?: (groupId: string) => void;
230
- }
231
- /** One shared `@patter` property, for a live state inspector: its ref, declared type, current value,
232
- * declared default (for reset), and enum options. Mirrors the Unity / Godot ports' ListProperties. */
233
- interface PropertyRow {
234
- ref: string;
235
- type: PropertyType;
236
- value: ScalarValue | undefined;
237
- default: ScalarValue;
238
- values?: string[];
239
- /** A quality's ordered stage ladder (lets an inspector offer stages instead of free text). */
240
- stages?: string[];
313
+ /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through
314
+ * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a
315
+ * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful
316
+ * with the log off - it is live feedback, not an audit read afterwards. */
317
+ log?: boolean;
241
318
  }
242
319
  /** Options for opening a flow. */
243
320
  interface OpenFlowOptions {
@@ -256,6 +333,10 @@ interface SelectorState {
256
333
  }
257
334
  /** Shared, read-mostly context the engine hands to every flow it owns. */
258
335
  interface FlowHost {
336
+ /** True when the run asked for a log; flows skip building entries otherwise. */
337
+ logEnabled: boolean;
338
+ /** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */
339
+ emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;
259
340
  bundle: Bundle;
260
341
  /** IDs-only build (`localisation.mode === "ids"`, no source-debug): the engine emits each beat's ID as
261
342
  * its text and omits character display names, leaving localisation to the game (use `flow.interpolate`
@@ -293,7 +374,11 @@ interface FlowHost {
293
374
  /** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */
294
375
  sharedSelectors: Map<string, SelectorState>;
295
376
  /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */
296
- stageBags: Map<string, Record<string, ScalarValue>>;
377
+ /** Per-scene SHARED scene props. A PropertyBag rather than a bare record since
378
+ * 2026-09-02: seeding, name normalisation and the mutable-default clone were all
379
+ * written out by hand here, and the shared bag already had them. The SAVE is
380
+ * unchanged - bag.save() is a bare record, which is what the envelope carries. */
381
+ stageBags: Map<string, PropertyBag>;
297
382
  customRng?: () => number;
298
383
  /** Play a chosen option's prompt as its first beat (spec §5); default false. */
299
384
  replayPromptOnChoose?: boolean;
@@ -337,6 +422,10 @@ declare class Engine {
337
422
  /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
338
423
  * engine keeps the same world resolver, custom RNG, and diagnostic hooks. */
339
424
  private readonly creationOptions;
425
+ /** The run's ordered stream: every flow's events, each naming its flow. Empty and
426
+ * unwritten unless `options.log` asked for it. */
427
+ private readonly engineLog;
428
+ private readonly engineTraceHandlers;
340
429
  constructor(bundle: Bundle, options?: EngineOptions);
341
430
  /** The active locale (string + character-name lookups resolve in it). */
342
431
  get locale(): string;
@@ -496,6 +585,17 @@ declare class Engine {
496
585
  setProperty(ref: string, value: ScalarValue): void;
497
586
  /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
498
587
  * value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
588
+ /** The run's decisions, in order, each naming the flow it happened in. Empty unless the
589
+ * run was opened with `log: true`. A flow's own log stays flow-local; this is the only
590
+ * place a story spanning several flows reads as one sequence. */
591
+ log(): readonly EngineLogEntry[];
592
+ /** Drop the retained entries. `seq` does NOT restart: two reads of a log either side of a
593
+ * clear still agree about what came first. */
594
+ clearLog(): void;
595
+ /** Live tap on the run's decisions, for tooling that wants them as they happen rather than
596
+ * retained. Returns its own unsubscribe. */
597
+ onTrace(handler: EngineTraceHandler): () => void;
598
+ private emitEngine;
499
599
  listProperties(): PropertyRow[];
500
600
  private splitShared;
501
601
  /** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */
@@ -530,10 +630,14 @@ declare class Flow {
530
630
  private selectors;
531
631
  /** Per-node entry counts for this flow (node id -> times entered). */
532
632
  private visitCounts;
633
+ /** This flow's per-scene LOCAL scene props; see FlowHost.stageBags. */
533
634
  private sceneBags;
534
635
  private readonly patterResolver;
535
636
  private readonly sceneResolver;
536
637
  private readonly evalCtx;
638
+ private readonly flowLog;
639
+ /** Monotonic across the flow's life; survives clearLog so two reads agree on order. */
640
+ private flowSeq;
537
641
  constructor(id: string, host: FlowHost, seed: number);
538
642
  /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
539
643
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
@@ -599,6 +703,15 @@ declare class Flow {
599
703
  */
600
704
  private settle;
601
705
  /** The options of a pending choice (empty when not at a choice point). */
706
+ /** This flow's decisions, in order. Empty unless the run was opened with `log: true`.
707
+ * The engine's log carries the same events tagged with the flow; this one is what a
708
+ * single conversation reads as. */
709
+ log(): readonly LogEntry[];
710
+ /** Drop the retained entries. `seq` keeps counting, so order survives a clear. */
711
+ clearLog(): void;
712
+ /** Record one decision, on this flow's log and the engine's. Cheap to call with logging
713
+ * off: the entry is never built. */
714
+ private emit;
602
715
  getChoices(): ChoiceOption[];
603
716
  /** Pick an eligible option by id; the next `advance()` runs it. */
604
717
  choose(id: string): void;
@@ -678,7 +791,13 @@ declare class Flow {
678
791
  private conditionAst;
679
792
  /** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
680
793
  private enter;
681
- /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32. */
794
+ /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32.
795
+ *
796
+ * The mixing is @wildwinter/expr's makePrng, not a copy inline here. It is a
797
+ * fixed published algorithm that both product families need and neither
798
+ * owns, and it existed thirteen times across the two of them. `rngState` is
799
+ * still the serialisable position, so saves are unaffected: a generator is
800
+ * made from it, drawn once, and the new state written back. */
682
801
  private readonly rng;
683
802
  private beatResult;
684
803
  /**
@@ -881,4 +1000,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
881
1000
  */
882
1001
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
883
1002
 
884
- export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, type ChoiceOption, Engine, type EngineOptions, type EngineSave, type FlatBeat, Flow, type FlowSnapshot, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PropertyRow, type PropertySummary, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
1003
+ export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, type ChoiceOption, Engine, type EngineLogEntry, type EngineOptions, type EngineSave, type EngineTraceHandler, type FlatBeat, Flow, type FlowSnapshot, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type LogEntry, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PropertySummary, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type TraceEvent, type TraceHandler, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ScalarValue } from '@wildwinter/expr';
2
- import { ScopeResolver, ScopeRegistry, ScopeDeclaration } from '@wildwinter/scoperegistry';
2
+ import { ScopeResolver, ScopeRegistry, ScopeDeclaration, PropertyBag, PropertyRow } from '@wildwinter/scoperegistry';
3
+ export { PropertyRow } from '@wildwinter/scoperegistry';
3
4
  import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
4
5
  export { Bundle } from '@patterkit/model';
5
6
 
@@ -196,6 +197,88 @@ interface ChoiceOption {
196
197
  * are never stored or saved by this engine.
197
198
  */
198
199
  type WorldResolver = ScopeResolver;
200
+ /** What the engine DECIDED, as opposed to what it produced. A step tells you the line that
201
+ * played; a trace event tells you why that line and not its siblings - which children were
202
+ * eligible, where the selector's cursor stood, which choice options were live.
203
+ *
204
+ * The reasoning is IN the entry, not implied by it. `select` carries every child it looked at
205
+ * with its own verdict, the way a deal in the Storylet Engine carries every card it considered.
206
+ * A log that recorded only the winner would answer "what happened" and not "why", and "why"
207
+ * is the question a dialogue author cannot otherwise answer: the line you expected is missing
208
+ * and nothing anywhere says which condition dropped it.
209
+ *
210
+ * Patter's vocabulary, not the Storylet Engine's: there are no cards or hands here. The
211
+ * structure is shared (an ordered retained log, `seq`, a flow-level and an engine-level tap);
212
+ * the events are this engine's own. */
213
+ type TraceEvent = {
214
+ type: "select";
215
+ /** The group whose children were being chosen among. */
216
+ group: string;
217
+ /** `branch` | `sequence` | `run` | `choice`, and for `sequence` its order/exhaust policy. */
218
+ selector: string;
219
+ order?: string;
220
+ exhaust?: string;
221
+ /** Every child considered, in authored order, each with why it was or was not takeable. */
222
+ children: {
223
+ id: string;
224
+ eligible: boolean;
225
+ }[];
226
+ /** The child selected, or null when nothing was takeable. */
227
+ picked: string | null;
228
+ } | {
229
+ type: "choice";
230
+ group: string;
231
+ /** Every option the group offered, with the ones a condition dropped marked. */
232
+ options: {
233
+ id: string;
234
+ eligible: boolean;
235
+ }[];
236
+ }
237
+ /** A choice the player answered. `option` is what they took. */
238
+ | {
239
+ type: "chose";
240
+ group: string;
241
+ option: string;
242
+ }
243
+ /** A choice with no takeable option and no eligible fallback, which falls through
244
+ * silently. Also reported through `onDryChoice`, which stays: a log is an audit read
245
+ * afterwards, that callback is live feedback a host acts on. */
246
+ | {
247
+ type: "dry";
248
+ group: string;
249
+ } | {
250
+ type: "jump";
251
+ to: string;
252
+ mode: "jump" | "call";
253
+ }
254
+ /** One landed effect; `prev` is the value it replaced, so a reader can say "0 -> 1". */
255
+ | {
256
+ type: "write";
257
+ target: string;
258
+ value: ScalarValue;
259
+ prev?: ScalarValue;
260
+ }
261
+ /** An expression that would not evaluate: never a silent pass, always visible. */
262
+ | {
263
+ type: "diagnostic";
264
+ where: string;
265
+ message: string;
266
+ };
267
+ type TraceHandler = (event: TraceEvent) => void;
268
+ /** The engine-level tap: every flow's events, tagged with the flow id - one stream for tools. */
269
+ type EngineTraceHandler = (flow: string, event: TraceEvent) => void;
270
+ /** A retained entry: the event plus its place in flow time. `seq` is monotonic across the
271
+ * flow and survives clearLog, so two reads of the log agree about order. */
272
+ type LogEntry = TraceEvent & {
273
+ seq: number;
274
+ scene?: string;
275
+ };
276
+ /** One entry on the ENGINE's log: the same event plus the flow it happened in. A run is
277
+ * several flows over shared state, so "what happened in this run" is only answerable in one
278
+ * ordered stream, and only if each line says who. */
279
+ type EngineLogEntry = LogEntry & {
280
+ flow: string;
281
+ };
199
282
  interface EngineOptions {
200
283
  /**
201
284
  * Custom float-in-[0,1) source for `random()` / shuffle, shared by all flows.
@@ -227,17 +310,11 @@ interface EngineOptions {
227
310
  * unchanged; this only makes the fall-through observable. The coverage harness uses it to flag choices
228
311
  * that ran dry. Leave it unset in shipped games (zero cost). */
229
312
  onDryChoice?: (groupId: string) => void;
230
- }
231
- /** One shared `@patter` property, for a live state inspector: its ref, declared type, current value,
232
- * declared default (for reset), and enum options. Mirrors the Unity / Godot ports' ListProperties. */
233
- interface PropertyRow {
234
- ref: string;
235
- type: PropertyType;
236
- value: ScalarValue | undefined;
237
- default: ScalarValue;
238
- values?: string[];
239
- /** A quality's ordered stage ladder (lets an inspector offer stages instead of free text). */
240
- stages?: string[];
313
+ /** Retain a trace of the engine's DECISIONS (what it chose and why), readable through
314
+ * `engine.log()` and `flow.log()`. Off by default: a shipped game pays nothing for a
315
+ * debugging surface it never reads. `onDryChoice` above is unaffected and stays useful
316
+ * with the log off - it is live feedback, not an audit read afterwards. */
317
+ log?: boolean;
241
318
  }
242
319
  /** Options for opening a flow. */
243
320
  interface OpenFlowOptions {
@@ -256,6 +333,10 @@ interface SelectorState {
256
333
  }
257
334
  /** Shared, read-mostly context the engine hands to every flow it owns. */
258
335
  interface FlowHost {
336
+ /** True when the run asked for a log; flows skip building entries otherwise. */
337
+ logEnabled: boolean;
338
+ /** A flow's events reach the ENGINE's stream through here, tagged with the flow id. */
339
+ emitEngine: (flow: string, event: TraceEvent, scene?: string) => void;
259
340
  bundle: Bundle;
260
341
  /** IDs-only build (`localisation.mode === "ids"`, no source-debug): the engine emits each beat's ID as
261
342
  * its text and omits character display names, leaving localisation to the game (use `flow.interpolate`
@@ -293,7 +374,11 @@ interface FlowHost {
293
374
  /** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */
294
375
  sharedSelectors: Map<string, SelectorState>;
295
376
  /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */
296
- stageBags: Map<string, Record<string, ScalarValue>>;
377
+ /** Per-scene SHARED scene props. A PropertyBag rather than a bare record since
378
+ * 2026-09-02: seeding, name normalisation and the mutable-default clone were all
379
+ * written out by hand here, and the shared bag already had them. The SAVE is
380
+ * unchanged - bag.save() is a bare record, which is what the envelope carries. */
381
+ stageBags: Map<string, PropertyBag>;
297
382
  customRng?: () => number;
298
383
  /** Play a chosen option's prompt as its first beat (spec §5); default false. */
299
384
  replayPromptOnChoose?: boolean;
@@ -337,6 +422,10 @@ declare class Engine {
337
422
  /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
338
423
  * engine keeps the same world resolver, custom RNG, and diagnostic hooks. */
339
424
  private readonly creationOptions;
425
+ /** The run's ordered stream: every flow's events, each naming its flow. Empty and
426
+ * unwritten unless `options.log` asked for it. */
427
+ private readonly engineLog;
428
+ private readonly engineTraceHandlers;
340
429
  constructor(bundle: Bundle, options?: EngineOptions);
341
430
  /** The active locale (string + character-name lookups resolve in it). */
342
431
  get locale(): string;
@@ -496,6 +585,17 @@ declare class Engine {
496
585
  setProperty(ref: string, value: ScalarValue): void;
497
586
  /** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
498
587
  * value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
588
+ /** The run's decisions, in order, each naming the flow it happened in. Empty unless the
589
+ * run was opened with `log: true`. A flow's own log stays flow-local; this is the only
590
+ * place a story spanning several flows reads as one sequence. */
591
+ log(): readonly EngineLogEntry[];
592
+ /** Drop the retained entries. `seq` does NOT restart: two reads of a log either side of a
593
+ * clear still agree about what came first. */
594
+ clearLog(): void;
595
+ /** Live tap on the run's decisions, for tooling that wants them as they happen rather than
596
+ * retained. Returns its own unsubscribe. */
597
+ onTrace(handler: EngineTraceHandler): () => void;
598
+ private emitEngine;
499
599
  listProperties(): PropertyRow[];
500
600
  private splitShared;
501
601
  /** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */
@@ -530,10 +630,14 @@ declare class Flow {
530
630
  private selectors;
531
631
  /** Per-node entry counts for this flow (node id -> times entered). */
532
632
  private visitCounts;
633
+ /** This flow's per-scene LOCAL scene props; see FlowHost.stageBags. */
533
634
  private sceneBags;
534
635
  private readonly patterResolver;
535
636
  private readonly sceneResolver;
536
637
  private readonly evalCtx;
638
+ private readonly flowLog;
639
+ /** Monotonic across the flow's life; survives clearLog so two reads agree on order. */
640
+ private flowSeq;
537
641
  constructor(id: string, host: FlowHost, seed: number);
538
642
  /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
539
643
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
@@ -599,6 +703,15 @@ declare class Flow {
599
703
  */
600
704
  private settle;
601
705
  /** The options of a pending choice (empty when not at a choice point). */
706
+ /** This flow's decisions, in order. Empty unless the run was opened with `log: true`.
707
+ * The engine's log carries the same events tagged with the flow; this one is what a
708
+ * single conversation reads as. */
709
+ log(): readonly LogEntry[];
710
+ /** Drop the retained entries. `seq` keeps counting, so order survives a clear. */
711
+ clearLog(): void;
712
+ /** Record one decision, on this flow's log and the engine's. Cheap to call with logging
713
+ * off: the entry is never built. */
714
+ private emit;
602
715
  getChoices(): ChoiceOption[];
603
716
  /** Pick an eligible option by id; the next `advance()` runs it. */
604
717
  choose(id: string): void;
@@ -678,7 +791,13 @@ declare class Flow {
678
791
  private conditionAst;
679
792
  /** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
680
793
  private enter;
681
- /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32. */
794
+ /** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32.
795
+ *
796
+ * The mixing is @wildwinter/expr's makePrng, not a copy inline here. It is a
797
+ * fixed published algorithm that both product families need and neither
798
+ * owns, and it existed thirteen times across the two of them. `rngState` is
799
+ * still the serialisable position, so saves are unaffected: a generator is
800
+ * made from it, drawn once, and the new state written back. */
682
801
  private readonly rng;
683
802
  private beatResult;
684
803
  /**
@@ -881,4 +1000,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
881
1000
  */
882
1001
  declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
883
1002
 
884
- export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, type ChoiceOption, Engine, type EngineOptions, type EngineSave, type FlatBeat, Flow, type FlowSnapshot, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PropertyRow, type PropertySummary, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };
1003
+ export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, type ChoiceOption, Engine, type EngineLogEntry, type EngineOptions, type EngineSave, type EngineTraceHandler, type FlatBeat, Flow, type FlowSnapshot, type GameDataFieldSummary, type GameDataSummary, type HostScopeSummary, type LogEntry, type OpenFlowOptions, type OutlineBlock, type OutlineNode, type OutlineScene, type OwnedProperties, type PropertySummary, type SaveGame, type SavedChoice, type SelectorSnapshot, type StackFrame, type StepResult, type TraceEvent, type TraceHandler, type WorldResolver, buildTagIndex, describeBundle, effectiveGameData, gameDataFields, gameDataValue };