@patterkit/runtime 0.15.0 → 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 +22 -0
- package/dist/index.cjs +172 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -3
- package/dist/index.d.ts +59 -3
- package/dist/index.js +171 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -33,6 +33,28 @@ version number always means the same runtime behaviour. This package is versione
|
|
|
33
33
|
|
|
34
34
|
## [Unreleased]
|
|
35
35
|
|
|
36
|
+
## [0.16.0] - 2026-10-04
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- **Checkpoints: try something, then undo it.** `engine.checkpoint()` opens a checkpoint; `engine.rollback(checkpoint)` puts the game back exactly
|
|
41
|
+
as it was then, and `engine.commit(checkpoint)` keeps everything. A rollback undoes every change the story made in between:
|
|
42
|
+
property values in every scope (a game's own `@world` store is written back through the game), visit
|
|
43
|
+
counts, shuffle and sequence positions, `@scene` bags made since, and every flow's position and random
|
|
44
|
+
state; a flow opened since is closed, and its name is free again. `engine.inCheckpoint` says whether one is open.
|
|
45
|
+
The use is asking "would this say anything?" without consequences: move a flow to an address, step
|
|
46
|
+
it, and roll back if it had nothing to give, so the scene's on-entry effects, the visits, and the
|
|
47
|
+
shuffle draws never happened. Each change records how to undo itself as it happens, so a checkpoint
|
|
48
|
+
costs what the steps inside it do, not what the game has built up, and the record is let go when it
|
|
49
|
+
closes. One is open at a time; while it is, the calls a rollback couldn't undo are refused
|
|
50
|
+
(`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` over an open flow, and a flow's own `start`, `reset`, and `restore`). Held across all four runtimes by the conformance corpus.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **`Flow.restore()` on a running flow keeps that flow's own property values.** It used to drop them back to
|
|
55
|
+
their defaults, since a snapshot holds a flow's position and memory, not its property values. Loading
|
|
56
|
+
a save is unchanged.
|
|
57
|
+
|
|
36
58
|
## [0.15.0] - 2026-10-03
|
|
37
59
|
|
|
38
60
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
Checkpoint: () => Checkpoint,
|
|
23
24
|
Engine: () => Engine,
|
|
24
25
|
Flow: () => Flow,
|
|
25
26
|
buildTagIndex: () => buildTagIndex,
|
|
@@ -87,6 +88,13 @@ var keys = {
|
|
|
87
88
|
/** A flow's NOT-shared `@scene` props for one scene. */
|
|
88
89
|
flowScene: (flowId, sceneId) => `patter/flow/${esc(flowId)}/scene/${esc(sceneId)}`
|
|
89
90
|
};
|
|
91
|
+
var Checkpoint = class {
|
|
92
|
+
/** @internal */
|
|
93
|
+
constructor(journal) {
|
|
94
|
+
this.journal = journal;
|
|
95
|
+
}
|
|
96
|
+
journal;
|
|
97
|
+
};
|
|
90
98
|
var Engine = class _Engine {
|
|
91
99
|
host;
|
|
92
100
|
defaultSeed;
|
|
@@ -204,6 +212,7 @@ var Engine = class _Engine {
|
|
|
204
212
|
sharedVisits: /* @__PURE__ */ new Map(),
|
|
205
213
|
sharedSelectors: /* @__PURE__ */ new Map(),
|
|
206
214
|
stageBags: /* @__PURE__ */ new Map(),
|
|
215
|
+
journal: null,
|
|
207
216
|
customRng: options.rng,
|
|
208
217
|
onDryChoice: options.onDryChoice,
|
|
209
218
|
replayPromptOnChoose: options.replayPromptOnChoose ?? false,
|
|
@@ -267,6 +276,7 @@ var Engine = class _Engine {
|
|
|
267
276
|
* top of the scene it was in; the shared properties carry over.
|
|
268
277
|
*/
|
|
269
278
|
hotSwap(bundle) {
|
|
279
|
+
this.refuseInCheckpoint("hotSwap");
|
|
270
280
|
const snapshot = this.saveGame();
|
|
271
281
|
const carryOver = (next2) => {
|
|
272
282
|
next2.setLocale(this.currentLocale);
|
|
@@ -327,8 +337,20 @@ var Engine = class _Engine {
|
|
|
327
337
|
this.assertExternalScopes();
|
|
328
338
|
const sceneId = this.resolveSceneRef(opts.scene);
|
|
329
339
|
const blockId = this.resolveBlockRef(sceneId, opts.block);
|
|
330
|
-
this.flowsById.get(id)
|
|
340
|
+
const prior = this.flowsById.get(id);
|
|
341
|
+
const journal = this.host.journal;
|
|
342
|
+
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`);
|
|
343
|
+
prior?.close();
|
|
331
344
|
const flow = new Flow(id, this.host, opts.seed ?? this.defaultSeed);
|
|
345
|
+
if (journal) {
|
|
346
|
+
journal.flows.add(flow);
|
|
347
|
+
journal.opened.add(flow);
|
|
348
|
+
journal.undo.push(() => {
|
|
349
|
+
flow.close();
|
|
350
|
+
if (prior) this.flowsById.set(id, prior);
|
|
351
|
+
else this.flowsById.delete(id);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
332
354
|
this.flowsById.set(id, flow);
|
|
333
355
|
flow.start(sceneId, blockId);
|
|
334
356
|
return flow;
|
|
@@ -584,9 +606,64 @@ var Engine = class _Engine {
|
|
|
584
606
|
for (const [sceneId, bag] of this.host.stageBags) mounts.push({ bag, pathPrefix: `@scene:${sceneId}.` });
|
|
585
607
|
return mounts;
|
|
586
608
|
}
|
|
609
|
+
/**
|
|
610
|
+
* Open a checkpoint: from here until `rollback` or `commit`, the engine keeps what it needs to undo
|
|
611
|
+
* every change the story makes. `rollback` puts the game back exactly as it was at the checkpoint:
|
|
612
|
+
* property values (every scope, including a host's `@world`, which is written back through the
|
|
613
|
+
* game), visit counts, shuffle and sequence positions, `@scene` bags made since, every flow's cursor
|
|
614
|
+
* and PRNG, and any flow opened since (closed and forgotten). `commit` keeps everything and stops
|
|
615
|
+
* recording.
|
|
616
|
+
*
|
|
617
|
+
* The use is asking "would this say anything?" without consequences: move a flow to an address,
|
|
618
|
+
* advance it, and roll back if it had nothing to give, so its scene's `onEntry` effects, its visits,
|
|
619
|
+
* and its shuffle draws never happened.
|
|
620
|
+
*
|
|
621
|
+
* One checkpoint at a time. Inside one, the calls that replace or drop flows or their bags wholesale
|
|
622
|
+
* (`reset`, `loadGame`, `hotSwap`, `closeFlow`, `openFlow` on the name of an open flow, and a flow's own
|
|
623
|
+
* `start`, `reset`, and `restore`) throw. Not undone:
|
|
624
|
+
* trace and log events already emitted, and draws from a custom `rng` the game supplied.
|
|
625
|
+
*/
|
|
626
|
+
checkpoint() {
|
|
627
|
+
if (this.host.journal) throw new Error("a checkpoint is already open: roll it back or commit it first");
|
|
628
|
+
const journal = { undo: [], flows: /* @__PURE__ */ new Set(), opened: /* @__PURE__ */ new Set(), selectors: /* @__PURE__ */ new Set(), flowSelectors: /* @__PURE__ */ new Map() };
|
|
629
|
+
this.host.journal = journal;
|
|
630
|
+
return new Checkpoint(journal);
|
|
631
|
+
}
|
|
632
|
+
/** Undo everything since `checkpoint` (see there), and close it. */
|
|
633
|
+
rollback(checkpoint) {
|
|
634
|
+
this.endCheckpoint(checkpoint);
|
|
635
|
+
const undo = checkpoint.journal.undo;
|
|
636
|
+
for (let i = undo.length - 1; i >= 0; i--) undo[i]();
|
|
637
|
+
_Engine.forget(checkpoint.journal);
|
|
638
|
+
}
|
|
639
|
+
/** Keep everything since `checkpoint`, and close it. */
|
|
640
|
+
commit(checkpoint) {
|
|
641
|
+
this.endCheckpoint(checkpoint);
|
|
642
|
+
_Engine.forget(checkpoint.journal);
|
|
643
|
+
}
|
|
644
|
+
/** True while a checkpoint is open. */
|
|
645
|
+
get inCheckpoint() {
|
|
646
|
+
return this.host.journal !== null;
|
|
647
|
+
}
|
|
648
|
+
endCheckpoint(checkpoint) {
|
|
649
|
+
if (this.host.journal !== checkpoint.journal) throw new Error("that checkpoint is not the open one");
|
|
650
|
+
this.host.journal = null;
|
|
651
|
+
}
|
|
652
|
+
/** Let go of what a closed checkpoint recorded, so a handle the game keeps holds nothing. */
|
|
653
|
+
static forget(journal) {
|
|
654
|
+
journal.undo = [];
|
|
655
|
+
journal.flows.clear();
|
|
656
|
+
journal.opened.clear();
|
|
657
|
+
journal.selectors.clear();
|
|
658
|
+
journal.flowSelectors.clear();
|
|
659
|
+
}
|
|
660
|
+
refuseInCheckpoint(what) {
|
|
661
|
+
if (this.host.journal) throw new Error(`${what} can't be undone, so it isn't allowed while a checkpoint is open`);
|
|
662
|
+
}
|
|
587
663
|
/** Close (remove) a flow. The flow object is FINISHED, not merely unregistered, so a host still
|
|
588
664
|
* holding it cannot keep advancing it into the shared world (see {@link Flow.close}). */
|
|
589
665
|
closeFlow(id) {
|
|
666
|
+
this.refuseInCheckpoint("closeFlow");
|
|
590
667
|
this.flowsById.get(id)?.close();
|
|
591
668
|
this.flowsById.delete(id);
|
|
592
669
|
}
|
|
@@ -597,6 +674,7 @@ var Engine = class _Engine {
|
|
|
597
674
|
* After reset, open fresh flows with `openFlow`.
|
|
598
675
|
*/
|
|
599
676
|
reset() {
|
|
677
|
+
this.refuseInCheckpoint("reset");
|
|
600
678
|
for (const flow of this.flowsById.values()) flow.close();
|
|
601
679
|
this.flowsById.clear();
|
|
602
680
|
this.host.patterBag.reseed(this.host.patterSharedDecls);
|
|
@@ -634,7 +712,9 @@ var Engine = class _Engine {
|
|
|
634
712
|
* were each refused the one property they existed to move.) */
|
|
635
713
|
setProperty(ref, value) {
|
|
636
714
|
const { scope, name } = this.splitShared(ref);
|
|
715
|
+
const prev = this.host.journal ? this.host.registry.get(scope, name) : void 0;
|
|
637
716
|
this.host.registry.set(scope, name, value, { host: true });
|
|
717
|
+
if (this.host.journal && prev !== void 0) this.host.journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));
|
|
638
718
|
}
|
|
639
719
|
/** The shared `@patter` properties, for a live state inspector: each with its ref, type, current
|
|
640
720
|
* value, declared default (for reset), and enum options. Mirrors the Unity / Godot ports. */
|
|
@@ -721,6 +801,7 @@ var Engine = class _Engine {
|
|
|
721
801
|
* to the registry (values kept) and the restored flows claim them as they register.
|
|
722
802
|
*/
|
|
723
803
|
loadGame(save) {
|
|
804
|
+
this.refuseInCheckpoint("loadGame");
|
|
724
805
|
const version = save.version;
|
|
725
806
|
if (version !== 2 && version !== import_model2.SAVE_VERSION) throw new Error(`unsupported save version: ${String(version)}`);
|
|
726
807
|
this.assertExternalScopes();
|
|
@@ -871,6 +952,7 @@ var Flow = class {
|
|
|
871
952
|
// -- Host API -------------------------------------------------------------
|
|
872
953
|
/** Begin this flow at a scene (and optionally a specific block within it). */
|
|
873
954
|
start(sceneId, blockId) {
|
|
955
|
+
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");
|
|
874
956
|
this.releaseBags(false);
|
|
875
957
|
this.host.registry.discardParked(keys.flow(this.id));
|
|
876
958
|
this.mountLocal();
|
|
@@ -930,6 +1012,7 @@ var Flow = class {
|
|
|
930
1012
|
*/
|
|
931
1013
|
goto(scene, block) {
|
|
932
1014
|
if (this.closed) return false;
|
|
1015
|
+
this.touch();
|
|
933
1016
|
if (scene === "END") {
|
|
934
1017
|
this.started = true;
|
|
935
1018
|
this.pendingChoice = null;
|
|
@@ -983,6 +1066,19 @@ var Flow = class {
|
|
|
983
1066
|
this.pendingPromptBeat = null;
|
|
984
1067
|
this.pendingPromptOwnerId = null;
|
|
985
1068
|
}
|
|
1069
|
+
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
1070
|
+
* put them back: a handful of fields and a copy of the call stack (a few frames), never its history.
|
|
1071
|
+
* Its visits, selector cursors, and property values are recorded change by change as they happen. */
|
|
1072
|
+
touch() {
|
|
1073
|
+
const journal = this.host.journal;
|
|
1074
|
+
if (!journal || journal.flows.has(this)) return;
|
|
1075
|
+
journal.flows.add(this);
|
|
1076
|
+
const { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPromptBeat, pendingPromptOwnerId } = this;
|
|
1077
|
+
const stack = this.stack.map((f) => ({ ...f }));
|
|
1078
|
+
journal.undo.push(() => {
|
|
1079
|
+
Object.assign(this, { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPromptBeat, pendingPromptOwnerId, stack });
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
986
1082
|
/** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */
|
|
987
1083
|
get isClosed() {
|
|
988
1084
|
return this.closed;
|
|
@@ -997,6 +1093,7 @@ var Flow = class {
|
|
|
997
1093
|
advance() {
|
|
998
1094
|
if (this.closed) return { type: "end" };
|
|
999
1095
|
if (!this.started) throw new Error("flow has not been started");
|
|
1096
|
+
this.touch();
|
|
1000
1097
|
if (this.pendingPromptBeat) {
|
|
1001
1098
|
const b = this.pendingPromptBeat;
|
|
1002
1099
|
this.pendingPromptBeat = null;
|
|
@@ -1116,6 +1213,7 @@ var Flow = class {
|
|
|
1116
1213
|
const option = choice.options.find((o) => o.id === id);
|
|
1117
1214
|
if (!option) throw new Error(`unknown choice option: ${id}`);
|
|
1118
1215
|
if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);
|
|
1216
|
+
this.touch();
|
|
1119
1217
|
const node = choice.byId.get(id);
|
|
1120
1218
|
this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
1121
1219
|
this.pendingChoice = null;
|
|
@@ -1143,13 +1241,27 @@ var Flow = class {
|
|
|
1143
1241
|
* about: the story is refused, the game is not. */
|
|
1144
1242
|
writeProperty(ref, value, host) {
|
|
1145
1243
|
const { scope, name } = this.splitRef(ref);
|
|
1244
|
+
const journal = this.host.journal;
|
|
1146
1245
|
if (scope === "patter") {
|
|
1246
|
+
if (journal) {
|
|
1247
|
+
const shared = this.host.patterSharedNames.has(name);
|
|
1248
|
+
const prev = shared ? this.host.patterBag.get(name) : this.local.get(name);
|
|
1249
|
+
const bag = this.local;
|
|
1250
|
+
if (prev !== void 0) journal.undo.push(shared ? () => this.host.registry.set("patter", name, prev) : () => bag.set(name, prev));
|
|
1251
|
+
}
|
|
1147
1252
|
this.patterResolver.set(name, value);
|
|
1148
1253
|
} else if (scope === "scene") {
|
|
1149
1254
|
if (this.currentSceneId === null) throw new Error(`'${ref}': the flow has not entered a scene yet`);
|
|
1255
|
+
if (journal) {
|
|
1256
|
+
const bag = this.sceneBagFor(this.currentSceneId, name);
|
|
1257
|
+
const prev = bag?.get(name);
|
|
1258
|
+
if (bag && prev !== void 0) journal.undo.push(() => bag.set(name, prev));
|
|
1259
|
+
}
|
|
1150
1260
|
this.sceneResolver.set(name, value);
|
|
1151
1261
|
} else {
|
|
1262
|
+
const prev = journal ? this.host.registry.get(scope, name) : void 0;
|
|
1152
1263
|
this.host.registry.set(scope, name, value, host ? { host: true } : void 0);
|
|
1264
|
+
if (journal && prev !== void 0) journal.undo.push(() => this.host.registry.set(scope, name, prev, { host: true }));
|
|
1153
1265
|
}
|
|
1154
1266
|
}
|
|
1155
1267
|
// -- Save / restore (engine-driven) --------------------------------------
|
|
@@ -1176,8 +1288,19 @@ var Flow = class {
|
|
|
1176
1288
|
}
|
|
1177
1289
|
};
|
|
1178
1290
|
}
|
|
1179
|
-
/** @internal Restore this flow from a snapshot.
|
|
1291
|
+
/** @internal Restore this flow from a snapshot: its cursor, PRNG, visits, and selector cursors. Its
|
|
1292
|
+
* property values are the registry's and stay as they are (a load puts the saved ones there first). */
|
|
1180
1293
|
restore(snap) {
|
|
1294
|
+
if (this.host.journal) throw new Error("a flow can't be restored while a checkpoint is open");
|
|
1295
|
+
this.restoreCursor(snap);
|
|
1296
|
+
this.releaseBags(true);
|
|
1297
|
+
this.mountLocal();
|
|
1298
|
+
for (const s of /* @__PURE__ */ new Set([this.currentSceneId, ...this.stack.map((f) => f.sceneId)])) {
|
|
1299
|
+
if (s !== null && this.host.bundle.scenes[s]) this.ensureSceneBags(s);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
/** The cursor half of `restore`, without touching the bags (a rollback has already put those back). */
|
|
1303
|
+
restoreCursor(snap) {
|
|
1181
1304
|
this.rngState = (0, import_expr.toUint32)(snap.rngState);
|
|
1182
1305
|
this.visitCounts = new Map(Object.entries(snap.visits ?? {}));
|
|
1183
1306
|
const c = snap.cursor;
|
|
@@ -1193,11 +1316,6 @@ var Flow = class {
|
|
|
1193
1316
|
}
|
|
1194
1317
|
return { ...frame };
|
|
1195
1318
|
});
|
|
1196
|
-
this.releaseBags(false);
|
|
1197
|
-
this.mountLocal();
|
|
1198
|
-
for (const s of /* @__PURE__ */ new Set([c.currentSceneId, ...this.stack.map((f) => f.sceneId)])) {
|
|
1199
|
-
if (s !== null && this.host.bundle.scenes[s]) this.ensureSceneBags(s);
|
|
1200
|
-
}
|
|
1201
1319
|
this.activeSnippet = null;
|
|
1202
1320
|
if (c.activeSnippetId !== null) {
|
|
1203
1321
|
const node = this.host.nodeIndex.get(c.activeSnippetId);
|
|
@@ -1474,6 +1592,24 @@ var Flow = class {
|
|
|
1474
1592
|
/** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
|
|
1475
1593
|
selectorState(group) {
|
|
1476
1594
|
const map = group.shared ? this.host.sharedSelectors : this.selectors;
|
|
1595
|
+
const journal = this.host.journal;
|
|
1596
|
+
let seen;
|
|
1597
|
+
if (journal) {
|
|
1598
|
+
seen = group.shared ? journal.selectors : journal.flowSelectors.get(this);
|
|
1599
|
+
if (!seen) {
|
|
1600
|
+
seen = /* @__PURE__ */ new Set();
|
|
1601
|
+
journal.flowSelectors.set(this, seen);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (journal && seen && !seen.has(group.id)) {
|
|
1605
|
+
seen.add(group.id);
|
|
1606
|
+
const was = map.get(group.id);
|
|
1607
|
+
const copy = was ? { ...was, ...was.bag ? { bag: [...was.bag] } : {} } : void 0;
|
|
1608
|
+
journal.undo.push(() => {
|
|
1609
|
+
if (copy) map.set(group.id, copy);
|
|
1610
|
+
else map.delete(group.id);
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1477
1613
|
let st = map.get(group.id);
|
|
1478
1614
|
if (!st) {
|
|
1479
1615
|
st = {};
|
|
@@ -1509,8 +1645,18 @@ var Flow = class {
|
|
|
1509
1645
|
}
|
|
1510
1646
|
/** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
|
|
1511
1647
|
enter(id) {
|
|
1512
|
-
|
|
1513
|
-
|
|
1648
|
+
const own = this.visitCounts;
|
|
1649
|
+
const ownBefore = own.get(id);
|
|
1650
|
+
own.set(id, (ownBefore ?? 0) + 1);
|
|
1651
|
+
const shared = this.host.sharedVisits;
|
|
1652
|
+
const before = shared.get(id);
|
|
1653
|
+
shared.set(id, (before ?? 0) + 1);
|
|
1654
|
+
if (this.host.journal) this.host.journal.undo.push(() => {
|
|
1655
|
+
if (ownBefore === void 0) own.delete(id);
|
|
1656
|
+
else own.set(id, ownBefore);
|
|
1657
|
+
if (before === void 0) shared.delete(id);
|
|
1658
|
+
else shared.set(id, before);
|
|
1659
|
+
});
|
|
1514
1660
|
}
|
|
1515
1661
|
/** Next float in [0, 1): the shared custom PRNG, or this flow's serialisable mulberry32.
|
|
1516
1662
|
*
|
|
@@ -1653,17 +1799,29 @@ var Flow = class {
|
|
|
1653
1799
|
ensureSceneBags(s) {
|
|
1654
1800
|
const shared = this.host.sceneSharedNames.get(s) ?? /* @__PURE__ */ new Set();
|
|
1655
1801
|
const props = this.host.bundle.scenes[s]?.sceneProps ?? [];
|
|
1802
|
+
const journal = this.host.journal;
|
|
1656
1803
|
if (!this.sceneBags.has(s)) {
|
|
1657
1804
|
const bag = new import_scoperegistry2.PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
1658
1805
|
const key = keys.flowScene(this.id, s);
|
|
1659
1806
|
this.host.registry.mountOwned(key, bag, { owner: OWNER });
|
|
1660
1807
|
this.registered.add(key);
|
|
1661
1808
|
this.sceneBags.set(s, bag);
|
|
1809
|
+
if (journal) journal.undo.push(() => {
|
|
1810
|
+
if (this.sceneBags.get(s) !== bag) return;
|
|
1811
|
+
this.host.registry.remove(key);
|
|
1812
|
+
this.registered.delete(key);
|
|
1813
|
+
this.sceneBags.delete(s);
|
|
1814
|
+
});
|
|
1662
1815
|
}
|
|
1663
1816
|
if (!this.host.stageBags.has(s)) {
|
|
1664
1817
|
const bag = new import_scoperegistry2.PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
1665
1818
|
this.host.registry.mountOwned(keys.stage(s), bag, { owner: OWNER });
|
|
1666
1819
|
this.host.stageBags.set(s, bag);
|
|
1820
|
+
if (journal) journal.undo.push(() => {
|
|
1821
|
+
if (this.host.stageBags.get(s) !== bag) return;
|
|
1822
|
+
this.host.registry.remove(keys.stage(s));
|
|
1823
|
+
this.host.stageBags.delete(s);
|
|
1824
|
+
});
|
|
1667
1825
|
}
|
|
1668
1826
|
}
|
|
1669
1827
|
/**
|
|
@@ -1680,7 +1838,10 @@ var Flow = class {
|
|
|
1680
1838
|
if (!decl.temporary) continue;
|
|
1681
1839
|
const name = decl.name.toLowerCase();
|
|
1682
1840
|
const bag = shared.has(name) ? this.host.stageBags.get(scene.id) : this.sceneBags.get(scene.id);
|
|
1683
|
-
if (bag)
|
|
1841
|
+
if (!bag) continue;
|
|
1842
|
+
const prev = this.host.journal ? bag.get(name) : void 0;
|
|
1843
|
+
if (prev !== void 0) this.host.journal.undo.push(() => bag.set(name, prev));
|
|
1844
|
+
bag.set(name, (0, import_scoperegistry2.defaultFor)(decl));
|
|
1684
1845
|
}
|
|
1685
1846
|
}
|
|
1686
1847
|
};
|
|
@@ -1880,6 +2041,7 @@ function effectiveGameData(fields, node) {
|
|
|
1880
2041
|
}
|
|
1881
2042
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1882
2043
|
0 && (module.exports = {
|
|
2044
|
+
Checkpoint,
|
|
1883
2045
|
Engine,
|
|
1884
2046
|
Flow,
|
|
1885
2047
|
buildTagIndex,
|