@patterkit/runtime 0.14.3 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/index.cjs +195 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -3
- package/dist/index.d.ts +73 -3
- package/dist/index.js +194 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ScalarValue } from '@wildwinter/expr';
|
|
2
|
-
import {
|
|
2
|
+
import { ScopeRegistry, PropertyBag, ScopeDeclaration, LogMount, ScopeResolver, PropertyRow } from '@wildwinter/scoperegistry';
|
|
3
3
|
export { PropertyRow } from '@wildwinter/scoperegistry';
|
|
4
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';
|
|
@@ -69,6 +69,8 @@ interface OutlineBlock {
|
|
|
69
69
|
id: string;
|
|
70
70
|
gameId?: string;
|
|
71
71
|
name: string;
|
|
72
|
+
/** The block's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */
|
|
73
|
+
gameData?: GameData;
|
|
72
74
|
tags?: string[];
|
|
73
75
|
children: OutlineNode[];
|
|
74
76
|
}
|
|
@@ -77,6 +79,8 @@ interface OutlineScene {
|
|
|
77
79
|
id: string;
|
|
78
80
|
gameId?: string;
|
|
79
81
|
name: string;
|
|
82
|
+
/** The scene's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */
|
|
83
|
+
gameData?: GameData;
|
|
80
84
|
tags?: string[];
|
|
81
85
|
blocks: OutlineBlock[];
|
|
82
86
|
}
|
|
@@ -273,6 +277,25 @@ interface SelectorState {
|
|
|
273
277
|
bag?: string[];
|
|
274
278
|
last?: string;
|
|
275
279
|
}
|
|
280
|
+
/** What an open checkpoint records: how to undo each change, newest last. Each piece of state records
|
|
281
|
+
* itself when it changes (a value, a count, a cursor), never a copy of everything, so a checkpoint costs
|
|
282
|
+
* what the steps inside it do, however much history the game has built up. */
|
|
283
|
+
interface Journal {
|
|
284
|
+
undo: Array<() => void>;
|
|
285
|
+
/** Flows already snapshotted (or opened inside the checkpoint, so closed on rollback). */
|
|
286
|
+
flows: Set<Flow>;
|
|
287
|
+
/** Flows opened inside the checkpoint. */
|
|
288
|
+
opened: Set<Flow>;
|
|
289
|
+
/** Selector cursors already copied: shared ones by group id, a flow's own in `flowSelectors`. */
|
|
290
|
+
selectors: Set<string>;
|
|
291
|
+
flowSelectors: Map<Flow, Set<string>>;
|
|
292
|
+
}
|
|
293
|
+
/** An open checkpoint, from {@link Engine.checkpoint}. Opaque: hand it back to `rollback` or `commit`. */
|
|
294
|
+
declare class Checkpoint {
|
|
295
|
+
readonly journal: Journal;
|
|
296
|
+
/** @internal */
|
|
297
|
+
constructor(journal: Journal);
|
|
298
|
+
}
|
|
276
299
|
/** Shared, read-mostly context the engine hands to every flow it owns. */
|
|
277
300
|
interface FlowHost {
|
|
278
301
|
/** True when the run asked for a log; flows skip building entries otherwise. */
|
|
@@ -324,6 +347,8 @@ interface FlowHost {
|
|
|
324
347
|
/** Per-scene SHARED scene props, each registered under `keys.stage(sceneId)`. Made the first time any
|
|
325
348
|
* flow needs the scene, so a bag loaded before then waits in the registry and is claimed here. */
|
|
326
349
|
stageBags: Map<string, PropertyBag>;
|
|
350
|
+
/** The open checkpoint's undo journal, or null when none is open (see {@link Engine.checkpoint}). */
|
|
351
|
+
journal: Journal | null;
|
|
327
352
|
customRng?: () => number;
|
|
328
353
|
/** Play a chosen option's prompt as its first beat (spec §5); default false. */
|
|
329
354
|
replayPromptOnChoose?: boolean;
|
|
@@ -483,6 +508,16 @@ declare class Engine {
|
|
|
483
508
|
tagsForScene(sceneRef: string): string[];
|
|
484
509
|
/** A block's accumulated tags (scene + block), by scene + block ref (id or gameId). Empty when none / unknown. */
|
|
485
510
|
tagsForBlock(sceneRef: string, blockRef: string): string[];
|
|
511
|
+
/**
|
|
512
|
+
* A scene's own author gameData (by internal id or gameId address): the RAW sparse overrides, exactly
|
|
513
|
+
* as a beat's step carries its own, not merged with the project's declared field defaults (resolve
|
|
514
|
+
* those with `effectiveGameData(gameDataFields(bundle, "scene"), ...)`). Not inherited by its blocks.
|
|
515
|
+
* A fresh object each call; empty when the scene sets none or the ref is unknown.
|
|
516
|
+
*/
|
|
517
|
+
gameDataForScene(sceneRef: string): GameData;
|
|
518
|
+
/** A block's own author gameData, by scene + block ref (id or gameId). Raw overrides, like
|
|
519
|
+
* {@link gameDataForScene}; the scene's gameData is not folded in. Empty when none / unknown. */
|
|
520
|
+
gameDataForBlock(sceneRef: string, blockRef: string): GameData;
|
|
486
521
|
/**
|
|
487
522
|
* Every cast member the PROJECT declares, in authored order - the same list `describeBundle` counts.
|
|
488
523
|
* A superset of any scene's cast: the validator holds a beat's `character` to a declared member, so
|
|
@@ -534,6 +569,34 @@ declare class Engine {
|
|
|
534
569
|
* loadGame() replaces every bag, so re-enumerate after a load.
|
|
535
570
|
*/
|
|
536
571
|
listBags(): LogMount[];
|
|
572
|
+
/**
|
|
573
|
+
* Open a checkpoint: from here until `rollback` or `commit`, the engine keeps what it needs to undo
|
|
574
|
+
* every change the story makes. `rollback` puts the game back exactly as it was at the checkpoint:
|
|
575
|
+
* property values (every scope, including a host's `@world`, which is written back through the
|
|
576
|
+
* game), visit counts, shuffle and sequence positions, `@scene` bags made since, every flow's cursor
|
|
577
|
+
* and PRNG, and any flow opened since (closed and forgotten). `commit` keeps everything and stops
|
|
578
|
+
* recording.
|
|
579
|
+
*
|
|
580
|
+
* The use is asking "would this say anything?" without consequences: move a flow to an address,
|
|
581
|
+
* advance it, and roll back if it had nothing to give, so its scene's `onEntry` effects, its visits,
|
|
582
|
+
* and its shuffle draws never happened.
|
|
583
|
+
*
|
|
584
|
+
* One checkpoint at a time. Inside one, the calls that replace or drop flows or their bags wholesale
|
|
585
|
+
* (`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` on the name of an open flow, and a flow's own
|
|
586
|
+
* `start`, `reset`, and `restore`) throw. Not undone:
|
|
587
|
+
* trace and log events already emitted, and draws from a custom `rng` the game supplied.
|
|
588
|
+
*/
|
|
589
|
+
checkpoint(): Checkpoint;
|
|
590
|
+
/** Undo everything since `checkpoint` (see there), and close it. */
|
|
591
|
+
rollback(checkpoint: Checkpoint): void;
|
|
592
|
+
/** Keep everything since `checkpoint`, and close it. */
|
|
593
|
+
commit(checkpoint: Checkpoint): void;
|
|
594
|
+
/** True while a checkpoint is open. */
|
|
595
|
+
get inCheckpoint(): boolean;
|
|
596
|
+
private endCheckpoint;
|
|
597
|
+
/** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */
|
|
598
|
+
private static forget;
|
|
599
|
+
private refuseInCheckpoint;
|
|
537
600
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
538
601
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
539
602
|
closeFlow(id: string): void;
|
|
@@ -672,6 +735,10 @@ declare class Flow {
|
|
|
672
735
|
* cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.
|
|
673
736
|
*/
|
|
674
737
|
close(): void;
|
|
738
|
+
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
739
|
+
* put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
|
|
740
|
+
* Its visits, selector cursors, and property values are recorded change by change as they happen. */
|
|
741
|
+
private touch;
|
|
675
742
|
/** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */
|
|
676
743
|
get isClosed(): boolean;
|
|
677
744
|
/** The scene the cursor is currently in - set on entry and whenever a jump crosses scenes. Read
|
|
@@ -725,8 +792,11 @@ declare class Flow {
|
|
|
725
792
|
private writeProperty;
|
|
726
793
|
/** @internal Snapshot this flow's cursor + PRNG + visits. Its properties are the registry's. */
|
|
727
794
|
snapshot(): FlowSnapshot;
|
|
728
|
-
/** @internal Restore this flow from a snapshot.
|
|
795
|
+
/** @internal Restore this flow from a snapshot: its cursor, PRNG, visits, and selector cursors. Its
|
|
796
|
+
* property values are the registry's and stay as they are (a load puts the saved ones there first). */
|
|
729
797
|
restore(snap: FlowSnapshot): void;
|
|
798
|
+
/** The cursor half of `restore`, without touching the bags (a rollback has already put those back). */
|
|
799
|
+
private restoreCursor;
|
|
730
800
|
/** Set the current scene, reset its scene-local props, run onEntry. */
|
|
731
801
|
private enterSceneSetup;
|
|
732
802
|
/**
|
|
@@ -1013,4 +1083,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
|
|
|
1013
1083
|
*/
|
|
1014
1084
|
declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
|
|
1015
1085
|
|
|
1016
|
-
export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, 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 };
|
|
1086
|
+
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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ScalarValue } from '@wildwinter/expr';
|
|
2
|
-
import {
|
|
2
|
+
import { ScopeRegistry, PropertyBag, ScopeDeclaration, LogMount, ScopeResolver, PropertyRow } from '@wildwinter/scoperegistry';
|
|
3
3
|
export { PropertyRow } from '@wildwinter/scoperegistry';
|
|
4
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';
|
|
@@ -69,6 +69,8 @@ interface OutlineBlock {
|
|
|
69
69
|
id: string;
|
|
70
70
|
gameId?: string;
|
|
71
71
|
name: string;
|
|
72
|
+
/** The block's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */
|
|
73
|
+
gameData?: GameData;
|
|
72
74
|
tags?: string[];
|
|
73
75
|
children: OutlineNode[];
|
|
74
76
|
}
|
|
@@ -77,6 +79,8 @@ interface OutlineScene {
|
|
|
77
79
|
id: string;
|
|
78
80
|
gameId?: string;
|
|
79
81
|
name: string;
|
|
82
|
+
/** The scene's own author gameData overrides (raw, not merged with defaults). Omitted when empty. */
|
|
83
|
+
gameData?: GameData;
|
|
80
84
|
tags?: string[];
|
|
81
85
|
blocks: OutlineBlock[];
|
|
82
86
|
}
|
|
@@ -273,6 +277,25 @@ interface SelectorState {
|
|
|
273
277
|
bag?: string[];
|
|
274
278
|
last?: string;
|
|
275
279
|
}
|
|
280
|
+
/** What an open checkpoint records: how to undo each change, newest last. Each piece of state records
|
|
281
|
+
* itself when it changes (a value, a count, a cursor), never a copy of everything, so a checkpoint costs
|
|
282
|
+
* what the steps inside it do, however much history the game has built up. */
|
|
283
|
+
interface Journal {
|
|
284
|
+
undo: Array<() => void>;
|
|
285
|
+
/** Flows already snapshotted (or opened inside the checkpoint, so closed on rollback). */
|
|
286
|
+
flows: Set<Flow>;
|
|
287
|
+
/** Flows opened inside the checkpoint. */
|
|
288
|
+
opened: Set<Flow>;
|
|
289
|
+
/** Selector cursors already copied: shared ones by group id, a flow's own in `flowSelectors`. */
|
|
290
|
+
selectors: Set<string>;
|
|
291
|
+
flowSelectors: Map<Flow, Set<string>>;
|
|
292
|
+
}
|
|
293
|
+
/** An open checkpoint, from {@link Engine.checkpoint}. Opaque: hand it back to `rollback` or `commit`. */
|
|
294
|
+
declare class Checkpoint {
|
|
295
|
+
readonly journal: Journal;
|
|
296
|
+
/** @internal */
|
|
297
|
+
constructor(journal: Journal);
|
|
298
|
+
}
|
|
276
299
|
/** Shared, read-mostly context the engine hands to every flow it owns. */
|
|
277
300
|
interface FlowHost {
|
|
278
301
|
/** True when the run asked for a log; flows skip building entries otherwise. */
|
|
@@ -324,6 +347,8 @@ interface FlowHost {
|
|
|
324
347
|
/** Per-scene SHARED scene props, each registered under `keys.stage(sceneId)`. Made the first time any
|
|
325
348
|
* flow needs the scene, so a bag loaded before then waits in the registry and is claimed here. */
|
|
326
349
|
stageBags: Map<string, PropertyBag>;
|
|
350
|
+
/** The open checkpoint's undo journal, or null when none is open (see {@link Engine.checkpoint}). */
|
|
351
|
+
journal: Journal | null;
|
|
327
352
|
customRng?: () => number;
|
|
328
353
|
/** Play a chosen option's prompt as its first beat (spec §5); default false. */
|
|
329
354
|
replayPromptOnChoose?: boolean;
|
|
@@ -483,6 +508,16 @@ declare class Engine {
|
|
|
483
508
|
tagsForScene(sceneRef: string): string[];
|
|
484
509
|
/** A block's accumulated tags (scene + block), by scene + block ref (id or gameId). Empty when none / unknown. */
|
|
485
510
|
tagsForBlock(sceneRef: string, blockRef: string): string[];
|
|
511
|
+
/**
|
|
512
|
+
* A scene's own author gameData (by internal id or gameId address): the RAW sparse overrides, exactly
|
|
513
|
+
* as a beat's step carries its own, not merged with the project's declared field defaults (resolve
|
|
514
|
+
* those with `effectiveGameData(gameDataFields(bundle, "scene"), ...)`). Not inherited by its blocks.
|
|
515
|
+
* A fresh object each call; empty when the scene sets none or the ref is unknown.
|
|
516
|
+
*/
|
|
517
|
+
gameDataForScene(sceneRef: string): GameData;
|
|
518
|
+
/** A block's own author gameData, by scene + block ref (id or gameId). Raw overrides, like
|
|
519
|
+
* {@link gameDataForScene}; the scene's gameData is not folded in. Empty when none / unknown. */
|
|
520
|
+
gameDataForBlock(sceneRef: string, blockRef: string): GameData;
|
|
486
521
|
/**
|
|
487
522
|
* Every cast member the PROJECT declares, in authored order - the same list `describeBundle` counts.
|
|
488
523
|
* A superset of any scene's cast: the validator holds a beat's `character` to a declared member, so
|
|
@@ -534,6 +569,34 @@ declare class Engine {
|
|
|
534
569
|
* loadGame() replaces every bag, so re-enumerate after a load.
|
|
535
570
|
*/
|
|
536
571
|
listBags(): LogMount[];
|
|
572
|
+
/**
|
|
573
|
+
* Open a checkpoint: from here until `rollback` or `commit`, the engine keeps what it needs to undo
|
|
574
|
+
* every change the story makes. `rollback` puts the game back exactly as it was at the checkpoint:
|
|
575
|
+
* property values (every scope, including a host's `@world`, which is written back through the
|
|
576
|
+
* game), visit counts, shuffle and sequence positions, `@scene` bags made since, every flow's cursor
|
|
577
|
+
* and PRNG, and any flow opened since (closed and forgotten). `commit` keeps everything and stops
|
|
578
|
+
* recording.
|
|
579
|
+
*
|
|
580
|
+
* The use is asking "would this say anything?" without consequences: move a flow to an address,
|
|
581
|
+
* advance it, and roll back if it had nothing to give, so its scene's `onEntry` effects, its visits,
|
|
582
|
+
* and its shuffle draws never happened.
|
|
583
|
+
*
|
|
584
|
+
* One checkpoint at a time. Inside one, the calls that replace or drop flows or their bags wholesale
|
|
585
|
+
* (`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` on the name of an open flow, and a flow's own
|
|
586
|
+
* `start`, `reset`, and `restore`) throw. Not undone:
|
|
587
|
+
* trace and log events already emitted, and draws from a custom `rng` the game supplied.
|
|
588
|
+
*/
|
|
589
|
+
checkpoint(): Checkpoint;
|
|
590
|
+
/** Undo everything since `checkpoint` (see there), and close it. */
|
|
591
|
+
rollback(checkpoint: Checkpoint): void;
|
|
592
|
+
/** Keep everything since `checkpoint`, and close it. */
|
|
593
|
+
commit(checkpoint: Checkpoint): void;
|
|
594
|
+
/** True while a checkpoint is open. */
|
|
595
|
+
get inCheckpoint(): boolean;
|
|
596
|
+
private endCheckpoint;
|
|
597
|
+
/** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */
|
|
598
|
+
private static forget;
|
|
599
|
+
private refuseInCheckpoint;
|
|
537
600
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
538
601
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
539
602
|
closeFlow(id: string): void;
|
|
@@ -672,6 +735,10 @@ declare class Flow {
|
|
|
672
735
|
* cannot quietly mutate the world. Terminal: unlike ending, a close is never revived.
|
|
673
736
|
*/
|
|
674
737
|
close(): void;
|
|
738
|
+
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
739
|
+
* put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
|
|
740
|
+
* Its visits, selector cursors, and property values are recorded change by change as they happen. */
|
|
741
|
+
private touch;
|
|
675
742
|
/** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */
|
|
676
743
|
get isClosed(): boolean;
|
|
677
744
|
/** The scene the cursor is currently in - set on entry and whenever a jump crosses scenes. Read
|
|
@@ -725,8 +792,11 @@ declare class Flow {
|
|
|
725
792
|
private writeProperty;
|
|
726
793
|
/** @internal Snapshot this flow's cursor + PRNG + visits. Its properties are the registry's. */
|
|
727
794
|
snapshot(): FlowSnapshot;
|
|
728
|
-
/** @internal Restore this flow from a snapshot.
|
|
795
|
+
/** @internal Restore this flow from a snapshot: its cursor, PRNG, visits, and selector cursors. Its
|
|
796
|
+
* property values are the registry's and stay as they are (a load puts the saved ones there first). */
|
|
729
797
|
restore(snap: FlowSnapshot): void;
|
|
798
|
+
/** The cursor half of `restore`, without touching the bags (a rollback has already put those back). */
|
|
799
|
+
private restoreCursor;
|
|
730
800
|
/** Set the current scene, reset its scene-local props, run onEntry. */
|
|
731
801
|
private enterSceneSetup;
|
|
732
802
|
/**
|
|
@@ -1013,4 +1083,4 @@ declare function effectiveGameData(fields: GameDataField[], node: GameData | und
|
|
|
1013
1083
|
*/
|
|
1014
1084
|
declare function buildTagIndex(bundle: Bundle): Map<string, string[]>;
|
|
1015
1085
|
|
|
1016
|
-
export { type AddressSummary, type AdvanceToStopResult, type BeatInfo, type BundleCounts, type BundleDescription, type BundleIdentity, 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 };
|
|
1086
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -55,6 +55,13 @@ var keys = {
|
|
|
55
55
|
/** A flow's NOT-shared `@scene` props for one scene. */
|
|
56
56
|
flowScene: (flowId, sceneId) => `patter/flow/${esc(flowId)}/scene/${esc(sceneId)}`
|
|
57
57
|
};
|
|
58
|
+
var Checkpoint = class {
|
|
59
|
+
/** @internal */
|
|
60
|
+
constructor(journal) {
|
|
61
|
+
this.journal = journal;
|
|
62
|
+
}
|
|
63
|
+
journal;
|
|
64
|
+
};
|
|
58
65
|
var Engine = class _Engine {
|
|
59
66
|
host;
|
|
60
67
|
defaultSeed;
|
|
@@ -172,6 +179,7 @@ var Engine = class _Engine {
|
|
|
172
179
|
sharedVisits: /* @__PURE__ */ new Map(),
|
|
173
180
|
sharedSelectors: /* @__PURE__ */ new Map(),
|
|
174
181
|
stageBags: /* @__PURE__ */ new Map(),
|
|
182
|
+
journal: null,
|
|
175
183
|
customRng: options.rng,
|
|
176
184
|
onDryChoice: options.onDryChoice,
|
|
177
185
|
replayPromptOnChoose: options.replayPromptOnChoose ?? false,
|
|
@@ -235,6 +243,7 @@ var Engine = class _Engine {
|
|
|
235
243
|
* top of the scene it was in; the shared properties carry over.
|
|
236
244
|
*/
|
|
237
245
|
hotSwap(bundle) {
|
|
246
|
+
this.refuseInCheckpoint("hotSwap");
|
|
238
247
|
const snapshot = this.saveGame();
|
|
239
248
|
const carryOver = (next2) => {
|
|
240
249
|
next2.setLocale(this.currentLocale);
|
|
@@ -295,8 +304,20 @@ var Engine = class _Engine {
|
|
|
295
304
|
this.assertExternalScopes();
|
|
296
305
|
const sceneId = this.resolveSceneRef(opts.scene);
|
|
297
306
|
const blockId = this.resolveBlockRef(sceneId, opts.block);
|
|
298
|
-
this.flowsById.get(id)
|
|
307
|
+
const prior = this.flowsById.get(id);
|
|
308
|
+
const journal = this.host.journal;
|
|
309
|
+
if (journal && prior && !prior.isClosed) throw new Error(`openFlow would replace the open flow '${id}', which can't be undone while a checkpoint is open`);
|
|
310
|
+
prior?.close();
|
|
299
311
|
const flow = new Flow(id, this.host, opts.seed ?? this.defaultSeed);
|
|
312
|
+
if (journal) {
|
|
313
|
+
journal.flows.add(flow);
|
|
314
|
+
journal.opened.add(flow);
|
|
315
|
+
journal.undo.push(() => {
|
|
316
|
+
flow.close();
|
|
317
|
+
if (prior) this.flowsById.set(id, prior);
|
|
318
|
+
else this.flowsById.delete(id);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
300
321
|
this.flowsById.set(id, flow);
|
|
301
322
|
flow.start(sceneId, blockId);
|
|
302
323
|
return flow;
|
|
@@ -388,6 +409,24 @@ var Engine = class _Engine {
|
|
|
388
409
|
const id = this.resolveBlockRef(sceneId, blockRef);
|
|
389
410
|
return (id != null ? this.host.tagIndex.get(id) : void 0) ?? [];
|
|
390
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* A scene's own author gameData (by internal id or gameId address): the RAW sparse overrides, exactly
|
|
414
|
+
* as a beat's step carries its own, not merged with the project's declared field defaults (resolve
|
|
415
|
+
* those with `effectiveGameData(gameDataFields(bundle, "scene"), ...)`). Not inherited by its blocks.
|
|
416
|
+
* A fresh object each call; empty when the scene sets none or the ref is unknown.
|
|
417
|
+
*/
|
|
418
|
+
gameDataForScene(sceneRef) {
|
|
419
|
+
const id = this.resolveSceneRef(sceneRef);
|
|
420
|
+
const scene = id != null ? this.host.bundle.scenes[id] : void 0;
|
|
421
|
+
return { ...scene?.gameData };
|
|
422
|
+
}
|
|
423
|
+
/** A block's own author gameData, by scene + block ref (id or gameId). Raw overrides, like
|
|
424
|
+
* {@link gameDataForScene}; the scene's gameData is not folded in. Empty when none / unknown. */
|
|
425
|
+
gameDataForBlock(sceneRef, blockRef) {
|
|
426
|
+
const id = this.resolveBlockRef(this.resolveSceneRef(sceneRef), blockRef);
|
|
427
|
+
const block = id != null ? this.host.blockById.get(id) : void 0;
|
|
428
|
+
return { ...block?.gameData };
|
|
429
|
+
}
|
|
391
430
|
/**
|
|
392
431
|
* Every cast member the PROJECT declares, in authored order - the same list `describeBundle` counts.
|
|
393
432
|
* A superset of any scene's cast: the validator holds a beat's `character` to a declared member, so
|
|
@@ -434,11 +473,13 @@ var Engine = class _Engine {
|
|
|
434
473
|
id: scene.id,
|
|
435
474
|
...effectiveGameId(scene) ? { gameId: effectiveGameId(scene) } : {},
|
|
436
475
|
name: scene.name,
|
|
476
|
+
...gameDataField(scene.gameData),
|
|
437
477
|
...this.tagsField(scene.id),
|
|
438
478
|
blocks: scene.blocks.map((block) => ({
|
|
439
479
|
id: block.id,
|
|
440
480
|
...effectiveGameId(block) ? { gameId: effectiveGameId(block) } : {},
|
|
441
481
|
name: block.name,
|
|
482
|
+
...gameDataField(block.gameData),
|
|
442
483
|
...this.tagsField(block.id),
|
|
443
484
|
children: block.children.map((n) => this.outlineNode(n))
|
|
444
485
|
}))
|
|
@@ -532,9 +573,64 @@ var Engine = class _Engine {
|
|
|
532
573
|
for (const [sceneId, bag] of this.host.stageBags) mounts.push({ bag, pathPrefix: `@scene:${sceneId}.` });
|
|
533
574
|
return mounts;
|
|
534
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Open a checkpoint: from here until `rollback` or `commit`, the engine keeps what it needs to undo
|
|
578
|
+
* every change the story makes. `rollback` puts the game back exactly as it was at the checkpoint:
|
|
579
|
+
* property values (every scope, including a host's `@world`, which is written back through the
|
|
580
|
+
* game), visit counts, shuffle and sequence positions, `@scene` bags made since, every flow's cursor
|
|
581
|
+
* and PRNG, and any flow opened since (closed and forgotten). `commit` keeps everything and stops
|
|
582
|
+
* recording.
|
|
583
|
+
*
|
|
584
|
+
* The use is asking "would this say anything?" without consequences: move a flow to an address,
|
|
585
|
+
* advance it, and roll back if it had nothing to give, so its scene's `onEntry` effects, its visits,
|
|
586
|
+
* and its shuffle draws never happened.
|
|
587
|
+
*
|
|
588
|
+
* One checkpoint at a time. Inside one, the calls that replace or drop flows or their bags wholesale
|
|
589
|
+
* (`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` on the name of an open flow, and a flow's own
|
|
590
|
+
* `start`, `reset`, and `restore`) throw. Not undone:
|
|
591
|
+
* trace and log events already emitted, and draws from a custom `rng` the game supplied.
|
|
592
|
+
*/
|
|
593
|
+
checkpoint() {
|
|
594
|
+
if (this.host.journal) throw new Error("a checkpoint is already open: roll it back or commit it first");
|
|
595
|
+
const journal = { undo: [], flows: /* @__PURE__ */ new Set(), opened: /* @__PURE__ */ new Set(), selectors: /* @__PURE__ */ new Set(), flowSelectors: /* @__PURE__ */ new Map() };
|
|
596
|
+
this.host.journal = journal;
|
|
597
|
+
return new Checkpoint(journal);
|
|
598
|
+
}
|
|
599
|
+
/** Undo everything since `checkpoint` (see there), and close it. */
|
|
600
|
+
rollback(checkpoint) {
|
|
601
|
+
this.endCheckpoint(checkpoint);
|
|
602
|
+
const undo = checkpoint.journal.undo;
|
|
603
|
+
for (let i = undo.length - 1; i >= 0; i--) undo[i]();
|
|
604
|
+
_Engine.forget(checkpoint.journal);
|
|
605
|
+
}
|
|
606
|
+
/** Keep everything since `checkpoint`, and close it. */
|
|
607
|
+
commit(checkpoint) {
|
|
608
|
+
this.endCheckpoint(checkpoint);
|
|
609
|
+
_Engine.forget(checkpoint.journal);
|
|
610
|
+
}
|
|
611
|
+
/** True while a checkpoint is open. */
|
|
612
|
+
get inCheckpoint() {
|
|
613
|
+
return this.host.journal !== null;
|
|
614
|
+
}
|
|
615
|
+
endCheckpoint(checkpoint) {
|
|
616
|
+
if (this.host.journal !== checkpoint.journal) throw new Error("that checkpoint is not the open one");
|
|
617
|
+
this.host.journal = null;
|
|
618
|
+
}
|
|
619
|
+
/** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */
|
|
620
|
+
static forget(journal) {
|
|
621
|
+
journal.undo = [];
|
|
622
|
+
journal.flows.clear();
|
|
623
|
+
journal.opened.clear();
|
|
624
|
+
journal.selectors.clear();
|
|
625
|
+
journal.flowSelectors.clear();
|
|
626
|
+
}
|
|
627
|
+
refuseInCheckpoint(what) {
|
|
628
|
+
if (this.host.journal) throw new Error(`${what} can't be undone, so it isn't allowed while a checkpoint is open`);
|
|
629
|
+
}
|
|
535
630
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
536
631
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
537
632
|
closeFlow(id) {
|
|
633
|
+
this.refuseInCheckpoint("closeFlow");
|
|
538
634
|
this.flowsById.get(id)?.close();
|
|
539
635
|
this.flowsById.delete(id);
|
|
540
636
|
}
|
|
@@ -545,6 +641,7 @@ var Engine = class _Engine {
|
|
|
545
641
|
* After reset, open fresh flows with `openFlow`.
|
|
546
642
|
*/
|
|
547
643
|
reset() {
|
|
644
|
+
this.refuseInCheckpoint("reset");
|
|
548
645
|
for (const flow of this.flowsById.values()) flow.close();
|
|
549
646
|
this.flowsById.clear();
|
|
550
647
|
this.host.patterBag.reseed(this.host.patterSharedDecls);
|
|
@@ -582,7 +679,9 @@ var Engine = class _Engine {
|
|
|
582
679
|
* were each refused the one property they existed to move.) */
|
|
583
680
|
setProperty(ref, value) {
|
|
584
681
|
const { scope, name } = this.splitShared(ref);
|
|
682
|
+
const prev = this.host.journal ? this.host.registry.get(scope, name) : void 0;
|
|
585
683
|
this.host.registry.set(scope, name, value, { host: true });
|
|
684
|
+
if (this.host.journal && prev !== void 0) this.host.journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));
|
|
586
685
|
}
|
|
587
686
|
/** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
|
|
588
687
|
* value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
|
|
@@ -669,6 +768,7 @@ var Engine = class _Engine {
|
|
|
669
768
|
* to the registry (values kept) and the restored flows claim them as they register.
|
|
670
769
|
*/
|
|
671
770
|
loadGame(save) {
|
|
771
|
+
this.refuseInCheckpoint("loadGame");
|
|
672
772
|
const version = save.version;
|
|
673
773
|
if (version !== 2 && version !== SAVE_VERSION) throw new Error(`unsupported save version: ${String(version)}`);
|
|
674
774
|
this.assertExternalScopes();
|
|
@@ -819,6 +919,7 @@ var Flow = class {
|
|
|
819
919
|
// -- Host API -------------------------------------------------------------
|
|
820
920
|
/** Begin this flow at a scene (and optionally a specific block within it). */
|
|
821
921
|
start(sceneId, blockId) {
|
|
922
|
+
if (this.host.journal && !this.host.journal.opened.has(this)) throw new Error("a flow can't be started or reset while a checkpoint is open");
|
|
822
923
|
this.releaseBags(false);
|
|
823
924
|
this.host.registry.discardParked(keys.flow(this.id));
|
|
824
925
|
this.mountLocal();
|
|
@@ -878,6 +979,7 @@ var Flow = class {
|
|
|
878
979
|
*/
|
|
879
980
|
goto(scene, block) {
|
|
880
981
|
if (this.closed) return false;
|
|
982
|
+
this.touch();
|
|
881
983
|
if (scene === "END") {
|
|
882
984
|
this.started = true;
|
|
883
985
|
this.pendingChoice = null;
|
|
@@ -931,6 +1033,19 @@ var Flow = class {
|
|
|
931
1033
|
this.pendingPromptBeat = null;
|
|
932
1034
|
this.pendingPromptOwnerId = null;
|
|
933
1035
|
}
|
|
1036
|
+
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
1037
|
+
* put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
|
|
1038
|
+
* Its visits, selector cursors, and property values are recorded change by change as they happen. */
|
|
1039
|
+
touch() {
|
|
1040
|
+
const journal = this.host.journal;
|
|
1041
|
+
if (!journal || journal.flows.has(this)) return;
|
|
1042
|
+
journal.flows.add(this);
|
|
1043
|
+
const { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPromptBeat, pendingPromptOwnerId } = this;
|
|
1044
|
+
const stack = this.stack.map((f) => ({ ...f }));
|
|
1045
|
+
journal.undo.push(() => {
|
|
1046
|
+
Object.assign(this, { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPromptBeat, pendingPromptOwnerId, stack });
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
934
1049
|
/** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */
|
|
935
1050
|
get isClosed() {
|
|
936
1051
|
return this.closed;
|
|
@@ -945,6 +1060,7 @@ var Flow = class {
|
|
|
945
1060
|
advance() {
|
|
946
1061
|
if (this.closed) return { type: "end" };
|
|
947
1062
|
if (!this.started) throw new Error("flow has not been started");
|
|
1063
|
+
this.touch();
|
|
948
1064
|
if (this.pendingPromptBeat) {
|
|
949
1065
|
const b = this.pendingPromptBeat;
|
|
950
1066
|
this.pendingPromptBeat = null;
|
|
@@ -1064,6 +1180,7 @@ var Flow = class {
|
|
|
1064
1180
|
const option = choice.options.find((o) => o.id === id);
|
|
1065
1181
|
if (!option) throw new Error(`unknown choice option: ${id}`);
|
|
1066
1182
|
if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);
|
|
1183
|
+
this.touch();
|
|
1067
1184
|
const node = choice.byId.get(id);
|
|
1068
1185
|
this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
1069
1186
|
this.pendingChoice = null;
|
|
@@ -1091,13 +1208,27 @@ var Flow = class {
|
|
|
1091
1208
|
* about: the story is refused, the game is not. */
|
|
1092
1209
|
writeProperty(ref, value, host) {
|
|
1093
1210
|
const { scope, name } = this.splitRef(ref);
|
|
1211
|
+
const journal = this.host.journal;
|
|
1094
1212
|
if (scope === "patter") {
|
|
1213
|
+
if (journal) {
|
|
1214
|
+
const shared = this.host.patterSharedNames.has(name);
|
|
1215
|
+
const prev = shared ? this.host.patterBag.get(name) : this.local.get(name);
|
|
1216
|
+
const bag = this.local;
|
|
1217
|
+
if (prev !== void 0) journal.undo.push(shared ? () => this.host.registry.set("patter", name, prev) : () => bag.set(name, prev));
|
|
1218
|
+
}
|
|
1095
1219
|
this.patterResolver.set(name, value);
|
|
1096
1220
|
} else if (scope === "scene") {
|
|
1097
1221
|
if (this.currentSceneId === null) throw new Error(`'${ref}': the flow has not entered a scene yet`);
|
|
1222
|
+
if (journal) {
|
|
1223
|
+
const bag = this.sceneBagFor(this.currentSceneId, name);
|
|
1224
|
+
const prev = bag?.get(name);
|
|
1225
|
+
if (bag && prev !== void 0) journal.undo.push(() => bag.set(name, prev));
|
|
1226
|
+
}
|
|
1098
1227
|
this.sceneResolver.set(name, value);
|
|
1099
1228
|
} else {
|
|
1229
|
+
const prev = journal ? this.host.registry.get(scope, name) : void 0;
|
|
1100
1230
|
this.host.registry.set(scope, name, value, host ? { host: true } : void 0);
|
|
1231
|
+
if (journal && prev !== void 0) journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));
|
|
1101
1232
|
}
|
|
1102
1233
|
}
|
|
1103
1234
|
// -- Save / restore (engine-driven) --------------------------------------
|
|
@@ -1124,8 +1255,19 @@ var Flow = class {
|
|
|
1124
1255
|
}
|
|
1125
1256
|
};
|
|
1126
1257
|
}
|
|
1127
|
-
/** @internal Restore this flow from a snapshot.
|
|
1258
|
+
/** @internal Restore this flow from a snapshot: its cursor, PRNG, visits, and selector cursors. Its
|
|
1259
|
+
* property values are the registry's and stay as they are (a load puts the saved ones there first). */
|
|
1128
1260
|
restore(snap) {
|
|
1261
|
+
if (this.host.journal) throw new Error("a flow can't be restored while a checkpoint is open");
|
|
1262
|
+
this.restoreCursor(snap);
|
|
1263
|
+
this.releaseBags(true);
|
|
1264
|
+
this.mountLocal();
|
|
1265
|
+
for (const s of /* @__PURE__ */ new Set([this.currentSceneId, ...this.stack.map((f) => f.sceneId)])) {
|
|
1266
|
+
if (s !== null && this.host.bundle.scenes[s]) this.ensureSceneBags(s);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
/** The cursor half of `restore`, without touching the bags (a rollback has already put those back). */
|
|
1270
|
+
restoreCursor(snap) {
|
|
1129
1271
|
this.rngState = toUint32(snap.rngState);
|
|
1130
1272
|
this.visitCounts = new Map(Object.entries(snap.visits ?? {}));
|
|
1131
1273
|
const c = snap.cursor;
|
|
@@ -1141,11 +1283,6 @@ var Flow = class {
|
|
|
1141
1283
|
}
|
|
1142
1284
|
return { ...frame };
|
|
1143
1285
|
});
|
|
1144
|
-
this.releaseBags(false);
|
|
1145
|
-
this.mountLocal();
|
|
1146
|
-
for (const s of /* @__PURE__ */ new Set([c.currentSceneId, ...this.stack.map((f) => f.sceneId)])) {
|
|
1147
|
-
if (s !== null && this.host.bundle.scenes[s]) this.ensureSceneBags(s);
|
|
1148
|
-
}
|
|
1149
1286
|
this.activeSnippet = null;
|
|
1150
1287
|
if (c.activeSnippetId !== null) {
|
|
1151
1288
|
const node = this.host.nodeIndex.get(c.activeSnippetId);
|
|
@@ -1422,6 +1559,24 @@ var Flow = class {
|
|
|
1422
1559
|
/** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
|
|
1423
1560
|
selectorState(group) {
|
|
1424
1561
|
const map = group.shared ? this.host.sharedSelectors : this.selectors;
|
|
1562
|
+
const journal = this.host.journal;
|
|
1563
|
+
let seen;
|
|
1564
|
+
if (journal) {
|
|
1565
|
+
seen = group.shared ? journal.selectors : journal.flowSelectors.get(this);
|
|
1566
|
+
if (!seen) {
|
|
1567
|
+
seen = /* @__PURE__ */ new Set();
|
|
1568
|
+
journal.flowSelectors.set(this, seen);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
if (journal && seen && !seen.has(group.id)) {
|
|
1572
|
+
seen.add(group.id);
|
|
1573
|
+
const was = map.get(group.id);
|
|
1574
|
+
const copy = was ? { ...was, ...was.bag ? { bag: [...was.bag] } : {} } : void 0;
|
|
1575
|
+
journal.undo.push(() => {
|
|
1576
|
+
if (copy) map.set(group.id, copy);
|
|
1577
|
+
else map.delete(group.id);
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1425
1580
|
let st = map.get(group.id);
|
|
1426
1581
|
if (!st) {
|
|
1427
1582
|
st = {};
|
|
@@ -1457,8 +1612,18 @@ var Flow = class {
|
|
|
1457
1612
|
}
|
|
1458
1613
|
/** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
|
|
1459
1614
|
enter(id) {
|
|
1460
|
-
|
|
1461
|
-
|
|
1615
|
+
const own = this.visitCounts;
|
|
1616
|
+
const ownBefore = own.get(id);
|
|
1617
|
+
own.set(id, (ownBefore ?? 0) + 1);
|
|
1618
|
+
const shared = this.host.sharedVisits;
|
|
1619
|
+
const before = shared.get(id);
|
|
1620
|
+
shared.set(id, (before ?? 0) + 1);
|
|
1621
|
+
if (this.host.journal) this.host.journal.undo.push(() => {
|
|
1622
|
+
if (ownBefore === void 0) own.delete(id);
|
|
1623
|
+
else own.set(id, ownBefore);
|
|
1624
|
+
if (before === void 0) shared.delete(id);
|
|
1625
|
+
else shared.set(id, before);
|
|
1626
|
+
});
|
|
1462
1627
|
}
|
|
1463
1628
|
/** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32.
|
|
1464
1629
|
*
|
|
@@ -1601,17 +1766,29 @@ var Flow = class {
|
|
|
1601
1766
|
ensureSceneBags(s) {
|
|
1602
1767
|
const shared = this.host.sceneSharedNames.get(s) ?? /* @__PURE__ */ new Set();
|
|
1603
1768
|
const props = this.host.bundle.scenes[s]?.sceneProps ?? [];
|
|
1769
|
+
const journal = this.host.journal;
|
|
1604
1770
|
if (!this.sceneBags.has(s)) {
|
|
1605
1771
|
const bag = new PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
1606
1772
|
const key = keys.flowScene(this.id, s);
|
|
1607
1773
|
this.host.registry.mountOwned(key, bag, { owner: OWNER });
|
|
1608
1774
|
this.registered.add(key);
|
|
1609
1775
|
this.sceneBags.set(s, bag);
|
|
1776
|
+
if (journal) journal.undo.push(() => {
|
|
1777
|
+
if (this.sceneBags.get(s) !== bag) return;
|
|
1778
|
+
this.host.registry.remove(key);
|
|
1779
|
+
this.registered.delete(key);
|
|
1780
|
+
this.sceneBags.delete(s);
|
|
1781
|
+
});
|
|
1610
1782
|
}
|
|
1611
1783
|
if (!this.host.stageBags.has(s)) {
|
|
1612
1784
|
const bag = new PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
1613
1785
|
this.host.registry.mountOwned(keys.stage(s), bag, { owner: OWNER });
|
|
1614
1786
|
this.host.stageBags.set(s, bag);
|
|
1787
|
+
if (journal) journal.undo.push(() => {
|
|
1788
|
+
if (this.host.stageBags.get(s) !== bag) return;
|
|
1789
|
+
this.host.registry.remove(keys.stage(s));
|
|
1790
|
+
this.host.stageBags.delete(s);
|
|
1791
|
+
});
|
|
1615
1792
|
}
|
|
1616
1793
|
}
|
|
1617
1794
|
/**
|
|
@@ -1628,7 +1805,10 @@ var Flow = class {
|
|
|
1628
1805
|
if (!decl.temporary) continue;
|
|
1629
1806
|
const name = decl.name.toLowerCase();
|
|
1630
1807
|
const bag = shared.has(name) ? this.host.stageBags.get(scene.id) : this.sceneBags.get(scene.id);
|
|
1631
|
-
if (bag)
|
|
1808
|
+
if (!bag) continue;
|
|
1809
|
+
const prev = this.host.journal ? bag.get(name) : void 0;
|
|
1810
|
+
if (prev !== void 0) this.host.journal.undo.push(() => bag.set(name, prev));
|
|
1811
|
+
bag.set(name, defaultFor(decl));
|
|
1632
1812
|
}
|
|
1633
1813
|
}
|
|
1634
1814
|
};
|
|
@@ -1641,6 +1821,9 @@ function collectCast(nodes, out) {
|
|
|
1641
1821
|
for (const beat of n.beats ?? []) if (beat.kind === "line" && beat.character) out.add(beat.character);
|
|
1642
1822
|
});
|
|
1643
1823
|
}
|
|
1824
|
+
function gameDataField(gameData) {
|
|
1825
|
+
return gameData && Object.keys(gameData).length ? { gameData } : {};
|
|
1826
|
+
}
|
|
1644
1827
|
function serialiseSelectors(map) {
|
|
1645
1828
|
const out = {};
|
|
1646
1829
|
for (const [id, st] of map) {
|
|
@@ -1824,6 +2007,7 @@ function effectiveGameData(fields, node) {
|
|
|
1824
2007
|
return out;
|
|
1825
2008
|
}
|
|
1826
2009
|
export {
|
|
2010
|
+
Checkpoint,
|
|
1827
2011
|
Engine,
|
|
1828
2012
|
Flow,
|
|
1829
2013
|
buildTagIndex,
|