@patterkit/runtime 0.13.0 → 0.14.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,7 +1,7 @@
1
1
  import { ScalarValue } from '@wildwinter/expr';
2
- import { ScopeResolver, ScopeRegistry, ScopeDeclaration, PropertyBag, LogMount, PropertyRow } from '@wildwinter/scoperegistry';
2
+ import { ScopeResolver, ScopeRegistry, PropertyBag, ScopeDeclaration, LogMount, PropertyRow } from '@wildwinter/scoperegistry';
3
3
  export { PropertyRow } from '@wildwinter/scoperegistry';
4
- import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, EngineSave, SaveGame, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
4
+ import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, EngineSave, SaveGame, SaveGameV2, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
5
5
  export { Bundle, EngineSave, FlowCursor, FlowSnapshot, SaveEnvelope, SaveGame, SavedChoice, SavedChoiceOption, SavedChoicePrompt, SelectorSnapshot, StackFrame } from '@patterkit/model';
6
6
 
7
7
  type SelectableNode = CompiledGroup | CompiledSnippet;
@@ -224,8 +224,18 @@ interface EngineOptions {
224
224
  * Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
225
225
  locale?: string;
226
226
  /** The host's resolver for **World Properties** (`@world.*`): the values the game owns and the story
227
- * reads. Omit it and the runtime self-backs `@world` from the declared defaults. Shared by all flows. */
227
+ * reads. Omit it and the runtime self-backs `@world` from the declared defaults, as a property the
228
+ * registry stores and saves. Shared by all flows. A game running several engines registers `@world`
229
+ * in its registry itself instead, and no engine is given a resolver. */
228
230
  world?: WorldResolver;
231
+ /** The game's registry: ONE per game, holding every engine's properties except those the game keeps
232
+ * itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,
233
+ * its per-flow and per-scene bags under keys starting `patter/`, and `@world` if `world` is passed),
234
+ * reads every other scope from it, and `saveGame()` leaves the property values to the game. Host
235
+ * scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should
236
+ * store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as
237
+ * its own game: it self-backs `@world`, and `saveGame()` carries the registry's values too. */
238
+ registry?: ScopeRegistry;
229
239
  /**
230
240
  * Replay a chosen option's `prompt` as its first played beat (spec §5). Default `false`:
231
241
  * the prompt is a label only and `choose()` plays just the option's content. `true`: the
@@ -291,8 +301,14 @@ interface FlowHost {
291
301
  blockGameIdToId: Map<string, Map<string, string>>;
292
302
  /** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */
293
303
  tagIndex: Map<string, string[]>;
294
- /** The SHARED `@patter` globals (owned scope "patter") + world properties (`@world`). */
295
- shared: ScopeRegistry;
304
+ /** The game's one registry: `@patter` (the SHARED globals), host scopes, every instance bag. */
305
+ registry: ScopeRegistry;
306
+ /** True when the engine made the registry (a standalone game): `saveGame()` then carries its values. */
307
+ readonly ownsRegistry: boolean;
308
+ /** The SHARED `@patter` globals' bag, registered under `patter`. */
309
+ patterBag: PropertyBag;
310
+ /** Host scopes this engine self-backed and registered (the game bound none, nobody else had). */
311
+ hostScopes: string[];
296
312
  /** Decls for the shared `@patter` globals - (re)seed on `engine.reset()`. */
297
313
  patterSharedDecls: ScopeDeclaration[];
298
314
  /** Decls for the per-flow `@patter` globals - seed each flow's local registry. */
@@ -305,11 +321,8 @@ interface FlowHost {
305
321
  sharedVisits: Map<string, number>;
306
322
  /** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */
307
323
  sharedSelectors: Map<string, SelectorState>;
308
- /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */
309
- /** Per-scene SHARED scene props. A PropertyBag rather than a bare record since
310
- * 2026-09-02: seeding, name normalisation and the mutable-default clone were all
311
- * written out by hand here, and the shared bag already had them. The SAVE is
312
- * unchanged - bag.save() is a bare record, which is what the envelope carries. */
324
+ /** Per-scene SHARED scene props, each registered under `keys.stage(sceneId)`. Made the first time any
325
+ * flow needs the scene, so a bag loaded before then waits in the registry and is claimed here. */
313
326
  stageBags: Map<string, PropertyBag>;
314
327
  customRng?: () => number;
315
328
  /** Play a chosen option's prompt as its first beat (spec §5); default false. */
@@ -327,12 +340,13 @@ interface FlowHost {
327
340
  * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
328
341
  * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
329
342
  onDryChoice?: (groupId: string) => void;
330
- /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on `shared`'s scope
331
- * set, which is fixed for the engine's life, so every effect target / `{@ref}` slot parses once. */
343
+ /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's
344
+ * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */
332
345
  refSplitCache: Map<string, {
333
346
  scope: string;
334
347
  name: string;
335
348
  }>;
349
+ refSplitRevision: number;
336
350
  }
337
351
  declare class Engine {
338
352
  private readonly host;
@@ -390,10 +404,12 @@ declare class Engine {
390
404
  * content-drift policy (§9.8) resolves edits under the cursor: stack frames re-find their next
391
405
  * child by id, drifted options drop, a vanished snippet is skipped.
392
406
  *
393
- * Returns the REPLACEMENT engine; this one is left untouched and should be discarded. Hosts
394
- * re-bind their flow handles via `next.getFlow(id)`. If the restore throws (defensive - §9.8
395
- * makes this unreachable for ordinary edits), the swap falls back to a cold engine with each
396
- * saved flow restarted from the top of the scene it was in.
407
+ * Returns the REPLACEMENT engine, on the same registry. This one hands its bags over (each is
408
+ * removed from the registry with its values kept, and the replacement claims them as it
409
+ * registers), its flows are closed, and it should be discarded. Hosts re-bind their flow handles
410
+ * via `next.getFlow(id)`. If the restore throws (defensive - §9.8 makes this unreachable for
411
+ * ordinary edits), the swap falls back to a fresh engine with each saved flow restarted from the
412
+ * top of the scene it was in; the shared properties carry over.
397
413
  */
398
414
  hotSwap(bundle: Bundle): Engine;
399
415
  /** Whether closed captions are currently shown (full dialogue text). */
@@ -444,6 +460,11 @@ declare class Engine {
444
460
  * fetch it with `engine.getFlow(name)?.getChoices()`.
445
461
  */
446
462
  runFlow(flow: string, scene: string, block?: string): AdvanceToStopResult["played"];
463
+ /** Content that names another engine's scope (`@story.act`) runs only where that engine is on this
464
+ * registry: without it every read would answer false and every write fail, so the flow is refused as
465
+ * it opens (or the save as it loads), before anything changes. By then a game has built all of its
466
+ * engines, whatever order it built them in. The same message on every runtime. */
467
+ private assertExternalScopes;
447
468
  /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
448
469
  private resolveSceneRef;
449
470
  /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
@@ -523,6 +544,9 @@ declare class Engine {
523
544
  * After reset, open fresh flows with `openFlow`.
524
545
  */
525
546
  reset(): void;
547
+ /** Remove every bag this engine registered, keeping the values parked when `keep` (a live reload
548
+ * handing its state to a replacement), and close its flows. The engine is inert afterwards. */
549
+ private release;
526
550
  /** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
527
551
  getProperty(ref: string): ScalarValue | undefined;
528
552
  /** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level).
@@ -548,19 +572,33 @@ declare class Engine {
548
572
  private emitEngine;
549
573
  listProperties(): PropertyRow[];
550
574
  private splitShared;
551
- /** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */
575
+ /** The shared `@patter` values alone, as `{ patter: {...} }`.
576
+ * @deprecated The registry holds every property now: a game saves `registry.save()` once, and a
577
+ * standalone engine's `saveGame()` carries its registry's values. Removed at the next breaking release. */
552
578
  save(): EngineSave;
553
- /** Restore shared `@patter` values (world properties untouched). */
579
+ /** Lay a `{ patter: {...} }` section over the shared `@patter` values; anything else is ignored.
580
+ * @deprecated See `save`. Removed at the next breaking release. */
554
581
  load(blob: EngineSave): void;
555
- /** Snapshot the whole game: shared `@patter` + visit counts + every live flow. */
582
+ /** Snapshot the whole game's NON-property state: visit counts, shared selector cursors, and every
583
+ * live flow's cursor and PRNG. The property values are the registry's: a standalone engine (one that
584
+ * made its own registry) carries them here under `registry`; a game that passed a registry saves it
585
+ * once itself, beside each engine's `saveGame()`. */
556
586
  saveGame(): SaveGame;
557
- /** Restore a `saveGame()`: shared globals + visit counts + shared scene bags + reconstruct every flow. */
558
- loadGame(save: SaveGame): void;
587
+ /**
588
+ * Restore a `saveGame()`: visit counts, shared selector cursors, and every flow. Property values come
589
+ * from the registry. A save that carries them (a standalone engine's, or a version 2 save from before
590
+ * the registry held them) has them moved into the registry here; otherwise the game loads its
591
+ * registry itself, before or after this call. Either order works: this engine's bags are handed back
592
+ * to the registry (values kept) and the restored flows claim them as they register.
593
+ */
594
+ loadGame(save: SaveGame | SaveGameV2): void;
559
595
  }
560
596
  declare class Flow {
561
597
  readonly id: string;
562
598
  private readonly host;
563
599
  private local;
600
+ /** The registry keys this flow has registered (its globals and each scene bag). */
601
+ private readonly registered;
564
602
  private rngState;
565
603
  private started;
566
604
  private flowEnded;
@@ -585,10 +623,14 @@ declare class Flow {
585
623
  private readonly patterResolver;
586
624
  private readonly sceneResolver;
587
625
  private readonly evalCtx;
626
+ private ctxRevision;
627
+ private registryQualities;
588
628
  private readonly flowLog;
589
629
  /** Monotonic across the flow's life; survives clearLog so two reads agree on order. */
590
630
  private flowSeq;
591
631
  constructor(id: string, host: FlowHost, seed: number);
632
+ /** The eval context, its scopes refreshed if the registry's set of scopes has moved since. */
633
+ private context;
592
634
  /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
593
635
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
594
636
  private stagesFor;
@@ -681,7 +723,7 @@ declare class Flow {
681
723
  /** The write itself. `host` says WHO is writing, which is the only thing `writable: false` cares
682
724
  * about: the story is refused, the game is not. */
683
725
  private writeProperty;
684
- /** @internal Snapshot this flow's cursor + per-flow scopes (not-shared `@patter`/`@scene`) + PRNG. */
726
+ /** @internal Snapshot this flow's cursor + PRNG + visits. Its properties are the registry's. */
685
727
  snapshot(): FlowSnapshot;
686
728
  /** @internal Restore this flow from a snapshot. */
687
729
  restore(snap: FlowSnapshot): void;
@@ -793,10 +835,20 @@ declare class Flow {
793
835
  * locale, else the authoring `displayName`. Undefined when the character has no display name at all
794
836
  * (the host falls back to the `character` token itself). */
795
837
  private resolveCharacterName;
796
- /** Split a ref into scope + name. Tokens: `@scene`, foreign tokens, else `@patter` (incl. bare `@name`). */
838
+ /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */
797
839
  private splitRef;
798
- /** The per-flow registry: the NOT-shared `@patter` globals (the shared ones live on the host). */
799
- private freshLocal;
840
+ /** A fresh, unregistered bag for the NOT-shared `@patter` globals (the shared ones live on the host). */
841
+ private newLocal;
842
+ /** Register a fresh globals bag under this flow's key; it claims any values waiting there. */
843
+ private mountLocal;
844
+ /** @internal Remove every bag this flow registered; with `keep`, their values wait in the registry for
845
+ * the flow that replaces this one. Engine-driven (close, loadGame, hotSwap). */
846
+ releaseBags(keep: boolean): void;
847
+ /** The bag a `@scene` property of scene `s` lives in (stage or this flow's), made if missing. */
848
+ private sceneBagFor;
849
+ /** Make (and register) scene `s`'s stage bag and this flow's bag for it, if not made yet. A bag made
850
+ * here claims whatever values the registry holds for its key: that is how a loaded save reaches it. */
851
+ private ensureSceneBags;
800
852
  /**
801
853
  * Seed a scene's `@scene` props (spec §7). The not-shared props seed THIS flow's
802
854
  * bag the first time it enters (persist across re-entries thereafter); the shared
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ScalarValue } from '@wildwinter/expr';
2
- import { ScopeResolver, ScopeRegistry, ScopeDeclaration, PropertyBag, LogMount, PropertyRow } from '@wildwinter/scoperegistry';
2
+ import { ScopeResolver, ScopeRegistry, PropertyBag, ScopeDeclaration, LogMount, PropertyRow } from '@wildwinter/scoperegistry';
3
3
  export { PropertyRow } from '@wildwinter/scoperegistry';
4
- import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, EngineSave, SaveGame, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
4
+ import { GameData, Bundle, CompiledGroup, CompiledSnippet, CompiledBlock, FlowSnapshot, EngineSave, SaveGame, SaveGameV2, PropertyType, ScalarValue as ScalarValue$1, GameDataNodeKind, GameDataField } from '@patterkit/model';
5
5
  export { Bundle, EngineSave, FlowCursor, FlowSnapshot, SaveEnvelope, SaveGame, SavedChoice, SavedChoiceOption, SavedChoicePrompt, SelectorSnapshot, StackFrame } from '@patterkit/model';
6
6
 
7
7
  type SelectableNode = CompiledGroup | CompiledSnippet;
@@ -224,8 +224,18 @@ interface EngineOptions {
224
224
  * Ignored by an "ids" bundle, which emits beat IDs for the game to localise itself. */
225
225
  locale?: string;
226
226
  /** The host's resolver for **World Properties** (`@world.*`): the values the game owns and the story
227
- * reads. Omit it and the runtime self-backs `@world` from the declared defaults. Shared by all flows. */
227
+ * reads. Omit it and the runtime self-backs `@world` from the declared defaults, as a property the
228
+ * registry stores and saves. Shared by all flows. A game running several engines registers `@world`
229
+ * in its registry itself instead, and no engine is given a resolver. */
228
230
  world?: WorldResolver;
231
+ /** The game's registry: ONE per game, holding every engine's properties except those the game keeps
232
+ * itself, saved once. Given one, the engine registers its own scopes in it (`@patter` under `patter`,
233
+ * its per-flow and per-scene bags under keys starting `patter/`, and `@world` if `world` is passed),
234
+ * reads every other scope from it, and `saveGame()` leaves the property values to the game. Host
235
+ * scopes the bundle declares (`@world`) are then the game's to register: owned if the registry should
236
+ * store them, foreign if the game keeps them. Omit it and the engine makes its own registry and acts as
237
+ * its own game: it self-backs `@world`, and `saveGame()` carries the registry's values too. */
238
+ registry?: ScopeRegistry;
229
239
  /**
230
240
  * Replay a chosen option's `prompt` as its first played beat (spec §5). Default `false`:
231
241
  * the prompt is a label only and `choose()` plays just the option's content. `true`: the
@@ -291,8 +301,14 @@ interface FlowHost {
291
301
  blockGameIdToId: Map<string, Map<string, string>>;
292
302
  /** Author tags (#215): node id -> accumulated tags (own + every ancestor's, deduped). Built once. */
293
303
  tagIndex: Map<string, string[]>;
294
- /** The SHARED `@patter` globals (owned scope "patter") + world properties (`@world`). */
295
- shared: ScopeRegistry;
304
+ /** The game's one registry: `@patter` (the SHARED globals), host scopes, every instance bag. */
305
+ registry: ScopeRegistry;
306
+ /** True when the engine made the registry (a standalone game): `saveGame()` then carries its values. */
307
+ readonly ownsRegistry: boolean;
308
+ /** The SHARED `@patter` globals' bag, registered under `patter`. */
309
+ patterBag: PropertyBag;
310
+ /** Host scopes this engine self-backed and registered (the game bound none, nobody else had). */
311
+ hostScopes: string[];
296
312
  /** Decls for the shared `@patter` globals - (re)seed on `engine.reset()`. */
297
313
  patterSharedDecls: ScopeDeclaration[];
298
314
  /** Decls for the per-flow `@patter` globals - seed each flow's local registry. */
@@ -305,11 +321,8 @@ interface FlowHost {
305
321
  sharedVisits: Map<string, number>;
306
322
  /** Shared selector cursors (node id -> SelectorState) for `shared` memoried selectors. */
307
323
  sharedSelectors: Map<string, SelectorState>;
308
- /** Shared, scene-namespaced `@scene` bags (scene id -> name -> value) for shared scene props. */
309
- /** Per-scene SHARED scene props. A PropertyBag rather than a bare record since
310
- * 2026-09-02: seeding, name normalisation and the mutable-default clone were all
311
- * written out by hand here, and the shared bag already had them. The SAVE is
312
- * unchanged - bag.save() is a bare record, which is what the envelope carries. */
324
+ /** Per-scene SHARED scene props, each registered under `keys.stage(sceneId)`. Made the first time any
325
+ * flow needs the scene, so a bag loaded before then waits in the registry and is claimed here. */
313
326
  stageBags: Map<string, PropertyBag>;
314
327
  customRng?: () => number;
315
328
  /** Play a chosen option's prompt as its first beat (spec §5); default false. */
@@ -327,12 +340,13 @@ interface FlowHost {
327
340
  * fallback - so it falls through and the flow continues past it. Zero cost when unset; the coverage
328
341
  * harness passes it to surface silent fall-throughs. Not a gameplay signal (the behaviour is unchanged). */
329
342
  onDryChoice?: (groupId: string) => void;
330
- /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on `shared`'s scope
331
- * set, which is fixed for the engine's life, so every effect target / `{@ref}` slot parses once. */
343
+ /** Memoised `splitRef` results (ref string -> {scope,name}). The split depends only on the registry's
344
+ * scope set, so the cache is dropped whenever that moves (`refSplitRevision`). */
332
345
  refSplitCache: Map<string, {
333
346
  scope: string;
334
347
  name: string;
335
348
  }>;
349
+ refSplitRevision: number;
336
350
  }
337
351
  declare class Engine {
338
352
  private readonly host;
@@ -390,10 +404,12 @@ declare class Engine {
390
404
  * content-drift policy (§9.8) resolves edits under the cursor: stack frames re-find their next
391
405
  * child by id, drifted options drop, a vanished snippet is skipped.
392
406
  *
393
- * Returns the REPLACEMENT engine; this one is left untouched and should be discarded. Hosts
394
- * re-bind their flow handles via `next.getFlow(id)`. If the restore throws (defensive - §9.8
395
- * makes this unreachable for ordinary edits), the swap falls back to a cold engine with each
396
- * saved flow restarted from the top of the scene it was in.
407
+ * Returns the REPLACEMENT engine, on the same registry. This one hands its bags over (each is
408
+ * removed from the registry with its values kept, and the replacement claims them as it
409
+ * registers), its flows are closed, and it should be discarded. Hosts re-bind their flow handles
410
+ * via `next.getFlow(id)`. If the restore throws (defensive - §9.8 makes this unreachable for
411
+ * ordinary edits), the swap falls back to a fresh engine with each saved flow restarted from the
412
+ * top of the scene it was in; the shared properties carry over.
397
413
  */
398
414
  hotSwap(bundle: Bundle): Engine;
399
415
  /** Whether closed captions are currently shown (full dialogue text). */
@@ -444,6 +460,11 @@ declare class Engine {
444
460
  * fetch it with `engine.getFlow(name)?.getChoices()`.
445
461
  */
446
462
  runFlow(flow: string, scene: string, block?: string): AdvanceToStopResult["played"];
463
+ /** Content that names another engine's scope (`@story.act`) runs only where that engine is on this
464
+ * registry: without it every read would answer false and every write fail, so the flow is refused as
465
+ * it opens (or the save as it loads), before anything changes. By then a game has built all of its
466
+ * engines, whatever order it built them in. The same message on every runtime. */
467
+ private assertExternalScopes;
447
468
  /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
448
469
  private resolveSceneRef;
449
470
  /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
@@ -523,6 +544,9 @@ declare class Engine {
523
544
  * After reset, open fresh flows with `openFlow`.
524
545
  */
525
546
  reset(): void;
547
+ /** Remove every bag this engine registered, keeping the values parked when `keep` (a live reload
548
+ * handing its state to a replacement), and close its flows. The engine is inert afterwards. */
549
+ private release;
526
550
  /** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
527
551
  getProperty(ref: string): ScalarValue | undefined;
528
552
  /** Write a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level).
@@ -548,19 +572,33 @@ declare class Engine {
548
572
  private emitEngine;
549
573
  listProperties(): PropertyRow[];
550
574
  private splitShared;
551
- /** Snapshot shared `@patter` state only (for a unified cross-engine save blob, Phase D). */
575
+ /** The shared `@patter` values alone, as `{ patter: {...} }`.
576
+ * @deprecated The registry holds every property now: a game saves `registry.save()` once, and a
577
+ * standalone engine's `saveGame()` carries its registry's values. Removed at the next breaking release. */
552
578
  save(): EngineSave;
553
- /** Restore shared `@patter` values (world properties untouched). */
579
+ /** Lay a `{ patter: {...} }` section over the shared `@patter` values; anything else is ignored.
580
+ * @deprecated See `save`. Removed at the next breaking release. */
554
581
  load(blob: EngineSave): void;
555
- /** Snapshot the whole game: shared `@patter` + visit counts + every live flow. */
582
+ /** Snapshot the whole game's NON-property state: visit counts, shared selector cursors, and every
583
+ * live flow's cursor and PRNG. The property values are the registry's: a standalone engine (one that
584
+ * made its own registry) carries them here under `registry`; a game that passed a registry saves it
585
+ * once itself, beside each engine's `saveGame()`. */
556
586
  saveGame(): SaveGame;
557
- /** Restore a `saveGame()`: shared globals + visit counts + shared scene bags + reconstruct every flow. */
558
- loadGame(save: SaveGame): void;
587
+ /**
588
+ * Restore a `saveGame()`: visit counts, shared selector cursors, and every flow. Property values come
589
+ * from the registry. A save that carries them (a standalone engine's, or a version 2 save from before
590
+ * the registry held them) has them moved into the registry here; otherwise the game loads its
591
+ * registry itself, before or after this call. Either order works: this engine's bags are handed back
592
+ * to the registry (values kept) and the restored flows claim them as they register.
593
+ */
594
+ loadGame(save: SaveGame | SaveGameV2): void;
559
595
  }
560
596
  declare class Flow {
561
597
  readonly id: string;
562
598
  private readonly host;
563
599
  private local;
600
+ /** The registry keys this flow has registered (its globals and each scene bag). */
601
+ private readonly registered;
564
602
  private rngState;
565
603
  private started;
566
604
  private flowEnded;
@@ -585,10 +623,14 @@ declare class Flow {
585
623
  private readonly patterResolver;
586
624
  private readonly sceneResolver;
587
625
  private readonly evalCtx;
626
+ private ctxRevision;
627
+ private registryQualities;
588
628
  private readonly flowLog;
589
629
  /** Monotonic across the flow's life; survives clearLog so two reads agree on order. */
590
630
  private flowSeq;
591
631
  constructor(id: string, host: FlowHost, seed: number);
632
+ /** The eval context, its scopes refreshed if the registry's set of scopes has moved since. */
633
+ private context;
592
634
  /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
593
635
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
594
636
  private stagesFor;
@@ -681,7 +723,7 @@ declare class Flow {
681
723
  /** The write itself. `host` says WHO is writing, which is the only thing `writable: false` cares
682
724
  * about: the story is refused, the game is not. */
683
725
  private writeProperty;
684
- /** @internal Snapshot this flow's cursor + per-flow scopes (not-shared `@patter`/`@scene`) + PRNG. */
726
+ /** @internal Snapshot this flow's cursor + PRNG + visits. Its properties are the registry's. */
685
727
  snapshot(): FlowSnapshot;
686
728
  /** @internal Restore this flow from a snapshot. */
687
729
  restore(snap: FlowSnapshot): void;
@@ -793,10 +835,20 @@ declare class Flow {
793
835
  * locale, else the authoring `displayName`. Undefined when the character has no display name at all
794
836
  * (the host falls back to the `character` token itself). */
795
837
  private resolveCharacterName;
796
- /** Split a ref into scope + name. Tokens: `@scene`, foreign tokens, else `@patter` (incl. bare `@name`). */
838
+ /** Split a ref into scope + name. Tokens: `@scene`, registered tokens, else `@patter` (incl. bare `@name`). */
797
839
  private splitRef;
798
- /** The per-flow registry: the NOT-shared `@patter` globals (the shared ones live on the host). */
799
- private freshLocal;
840
+ /** A fresh, unregistered bag for the NOT-shared `@patter` globals (the shared ones live on the host). */
841
+ private newLocal;
842
+ /** Register a fresh globals bag under this flow's key; it claims any values waiting there. */
843
+ private mountLocal;
844
+ /** @internal Remove every bag this flow registered; with `keep`, their values wait in the registry for
845
+ * the flow that replaces this one. Engine-driven (close, loadGame, hotSwap). */
846
+ releaseBags(keep: boolean): void;
847
+ /** The bag a `@scene` property of scene `s` lives in (stage or this flow's), made if missing. */
848
+ private sceneBagFor;
849
+ /** Make (and register) scene `s`'s stage bag and this flow's bag for it, if not made yet. A bag made
850
+ * here claims whatever values the registry holds for its key: that is how a loaded save reaches it. */
851
+ private ensureSceneBags;
800
852
  /**
801
853
  * Seed a scene's `@scene` props (spec §7). The not-shared props seed THIS flow's
802
854
  * bag the first time it enters (persist across re-entries thereafter); the shared