@patterkit/ops 0.18.0 → 0.20.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.ts +165 -18
- package/dist/index.js +665 -214
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync, existsSync as existsSync2 } from "fs";
|
|
3
3
|
import { join as join2, dirname as dirname2, resolve as resolve2, basename } from "path";
|
|
4
4
|
import { parseSource } from "@patterkit/core";
|
|
5
|
-
import { isCaseOnlyPropertyName, projectLayout } from "@patterkit/model";
|
|
5
|
+
import { EXTENSION_SHARD, EXTENSIONS_DIR, MANIFEST_EXTENSION, isCaseOnlyPropertyName, projectLayout } from "@patterkit/model";
|
|
6
6
|
|
|
7
7
|
// src/game-scopes.ts
|
|
8
8
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
@@ -251,6 +251,46 @@ function planShareScopes(root, project, dir) {
|
|
|
251
251
|
}
|
|
252
252
|
|
|
253
253
|
// src/load.ts
|
|
254
|
+
var NO_EXTENSIONS = () => ({ manifests: [], extensionShards: [], extensionShardFiles: [], extensionIssues: [] });
|
|
255
|
+
function loadExtensions(root, layout) {
|
|
256
|
+
const out = NO_EXTENSIONS();
|
|
257
|
+
const manifestDir = join2(root, EXTENSIONS_DIR);
|
|
258
|
+
const manifestFiles = readDirSafe(manifestDir).filter((e) => e.isFile() && !e.name.startsWith(".") && e.name.endsWith(MANIFEST_EXTENSION)).map((e) => join2(manifestDir, e.name)).sort();
|
|
259
|
+
for (const path of manifestFiles) {
|
|
260
|
+
try {
|
|
261
|
+
out.manifests.push({ path, id: basename(path).slice(0, -MANIFEST_EXTENSION.length), manifest: parseSource(readFileSync2(path, "utf8")) });
|
|
262
|
+
} catch (e) {
|
|
263
|
+
out.extensionIssues.push({ file: path, message: `the extension's manifest will not parse: ${e instanceof Error ? e.message : String(e)}` });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (const file of walkFiles(join2(root, layout.extensions), EXTENSION_SHARD)) {
|
|
267
|
+
let parsed;
|
|
268
|
+
try {
|
|
269
|
+
parsed = parseSource(readFileSync2(file, "utf8"));
|
|
270
|
+
} catch (e) {
|
|
271
|
+
out.extensionIssues.push({ file, message: `the extension data will not parse, so it is left out: ${e instanceof Error ? e.message : String(e)}` });
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const problem = extensionShardProblem(parsed);
|
|
275
|
+
if (problem) {
|
|
276
|
+
out.extensionIssues.push({ file, message: `${problem}, so it is left out` });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
out.extensionShards.push(parsed);
|
|
280
|
+
out.extensionShardFiles.push(file);
|
|
281
|
+
}
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
function extensionShardProblem(v) {
|
|
285
|
+
if (typeof v !== "object" || v === null || Array.isArray(v)) return "not an extension data file";
|
|
286
|
+
const o = v;
|
|
287
|
+
if (typeof o["schema"] !== "string" || !o["schema"].startsWith("patter/extension@")) return `schema is '${String(o["schema"])}', expected 'patter/extension@...'`;
|
|
288
|
+
if (typeof o["extension"] !== "string" || o["extension"] === "") return "it names no extension";
|
|
289
|
+
if (typeof o["scene"] !== "string" || o["scene"] === "") return "it names no scene";
|
|
290
|
+
const entries = o["entries"];
|
|
291
|
+
if (typeof entries !== "object" || entries === null || Array.isArray(entries)) return "its entries are not keyed by node kind";
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
254
294
|
function withGameScopes(root, project) {
|
|
255
295
|
const { gameScopes, missing } = discoverGameScopes(root, project);
|
|
256
296
|
return { ...gameScopes ? { gameScopes } : {}, ...missing ? { gameScopesMissing: missing } : {} };
|
|
@@ -436,7 +476,7 @@ function loadProjectLanding(startPath, opts) {
|
|
|
436
476
|
}
|
|
437
477
|
}
|
|
438
478
|
}
|
|
439
|
-
return { root, projectFile, project, scenes, locales, sceneFiles, localeFiles, authoring: [], authoringFiles: [], ...withGameScopes(root, project) };
|
|
479
|
+
return { root, projectFile, project, scenes, locales, sceneFiles, localeFiles, authoring: [], authoringFiles: [], ...withGameScopes(root, project), ...NO_EXTENSIONS() };
|
|
440
480
|
}
|
|
441
481
|
function applySceneOrder(scenes, order) {
|
|
442
482
|
if (!order?.length) return;
|
|
@@ -479,7 +519,19 @@ function loadProject(startPath) {
|
|
|
479
519
|
authoring.push(parseFile(f, "patter/authoring", "schema"));
|
|
480
520
|
authoringFiles.push(f);
|
|
481
521
|
}
|
|
482
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
root,
|
|
524
|
+
projectFile,
|
|
525
|
+
project,
|
|
526
|
+
scenes,
|
|
527
|
+
locales,
|
|
528
|
+
sceneFiles,
|
|
529
|
+
localeFiles,
|
|
530
|
+
authoring,
|
|
531
|
+
authoringFiles,
|
|
532
|
+
...withGameScopes(root, project),
|
|
533
|
+
...loadExtensions(root, layout)
|
|
534
|
+
};
|
|
483
535
|
}
|
|
484
536
|
|
|
485
537
|
// src/write.ts
|
|
@@ -494,11 +546,12 @@ function applyWrites(writes) {
|
|
|
494
546
|
|
|
495
547
|
// src/validate.ts
|
|
496
548
|
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
497
|
-
import { basename as basename4, isAbsolute as isAbsolute2, join as join5, relative as relative3 } from "path";
|
|
549
|
+
import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute2, join as join5, relative as relative3 } from "path";
|
|
498
550
|
|
|
499
551
|
// src/merge.ts
|
|
500
|
-
import { canonicalStringify as canonicalStringify2 } from "@patterkit/core";
|
|
552
|
+
import { canonicalStringify as canonicalStringify2, parseSource as parseSource2 } from "@patterkit/core";
|
|
501
553
|
import { DEFAULT_QUALIFIERS } from "@patterkit/model";
|
|
554
|
+
import { markUnresolved, mergeExtensionData, unresolvedProblems, withoutMark } from "@wildwinter/toolkit/extensions";
|
|
502
555
|
var UnsupportedMergeError = class extends Error {
|
|
503
556
|
};
|
|
504
557
|
var eq = (a, b) => canonicalStringify2(a) === canonicalStringify2(b);
|
|
@@ -510,7 +563,13 @@ function sidecarIssues(sidecarPaths) {
|
|
|
510
563
|
message: "unresolved merge conflict - resolve it and delete the .patterconflict sidecar before committing"
|
|
511
564
|
}));
|
|
512
565
|
}
|
|
513
|
-
var MERGE_TYPES = {
|
|
566
|
+
var MERGE_TYPES = {
|
|
567
|
+
flow: "flow",
|
|
568
|
+
strings: "loc",
|
|
569
|
+
authoring: "authoring",
|
|
570
|
+
project: "project",
|
|
571
|
+
extension: "extension"
|
|
572
|
+
};
|
|
514
573
|
function detectMergeType(file) {
|
|
515
574
|
const s = typeof file.schema === "string" ? file.schema : "";
|
|
516
575
|
const name = /^patter\/([a-z]+)(?:@|$)/.exec(s)?.[1];
|
|
@@ -520,16 +579,32 @@ function detectMergeType(file) {
|
|
|
520
579
|
}
|
|
521
580
|
function runMerge(base, ours, theirs, opts) {
|
|
522
581
|
const type = opts?.type ?? detectMergeType(ours);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
582
|
+
if (type === "extension") {
|
|
583
|
+
const r2 = mergeExtensionData(base, ours, theirs, { host: "patter", ...opts?.manifest !== void 0 ? { manifest: opts.manifest } : {} });
|
|
584
|
+
return { type, merged: r2.merged, conflicts: [...r2.conflicts], warnings: [...r2.warnings] };
|
|
585
|
+
}
|
|
586
|
+
const b = withoutMark(base), o = withoutMark(ours), t = withoutMark(theirs);
|
|
587
|
+
const r = type === "loc" ? mergeLoc(b, o, t) : type === "authoring" ? mergeAuthoring(b, o, t) : type === "flow" ? mergeFlow(b, o, t) : mergeProject(b, o, t);
|
|
588
|
+
return { ...r, merged: markUnresolved(r.merged, r.conflicts, { base, ours, theirs }) };
|
|
589
|
+
}
|
|
590
|
+
function unresolvedMarkIssues(files) {
|
|
591
|
+
const issues = [];
|
|
592
|
+
for (const { file, text } of files) {
|
|
593
|
+
if (!text.includes("unresolved")) continue;
|
|
594
|
+
let shard;
|
|
595
|
+
try {
|
|
596
|
+
shard = parseSource2(text);
|
|
597
|
+
} catch {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
for (const p of unresolvedProblems(shard)) {
|
|
601
|
+
issues.push({
|
|
602
|
+
file,
|
|
603
|
+
message: `unresolved merge at ${p.path}: the shard holds our side of a clash - resolve it, then remove the entry from "unresolved"`
|
|
604
|
+
});
|
|
605
|
+
}
|
|
532
606
|
}
|
|
607
|
+
return issues;
|
|
533
608
|
}
|
|
534
609
|
function deletedKind(base, ours, theirs) {
|
|
535
610
|
const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
|
|
@@ -542,7 +617,7 @@ function merge3(base, ours, theirs, id, path, conflicts) {
|
|
|
542
617
|
conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
|
|
543
618
|
return ours;
|
|
544
619
|
}
|
|
545
|
-
function mergeMap(base, ours, theirs, prefix, conflicts,
|
|
620
|
+
function mergeMap(base, ours, theirs, prefix, conflicts, resolve8) {
|
|
546
621
|
const out = {};
|
|
547
622
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
548
623
|
const b = base[k], o = ours[k], t = theirs[k];
|
|
@@ -551,7 +626,7 @@ function mergeMap(base, ours, theirs, prefix, conflicts, resolve7) {
|
|
|
551
626
|
else if (eq(b, o)) v = t;
|
|
552
627
|
else if (eq(b, t)) v = o;
|
|
553
628
|
else {
|
|
554
|
-
const r =
|
|
629
|
+
const r = resolve8?.(k, b, o, t);
|
|
555
630
|
if (r && r.resolved) v = r.value;
|
|
556
631
|
else {
|
|
557
632
|
conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
|
|
@@ -697,13 +772,13 @@ function mergeEditRecord(o, t) {
|
|
|
697
772
|
if (Object.keys(loc).length > 0) merged.localisedAt = loc;
|
|
698
773
|
return merged;
|
|
699
774
|
}
|
|
700
|
-
function mergeKeyedByName(base, ours, theirs, path, conflicts,
|
|
775
|
+
function mergeKeyedByName(base, ours, theirs, path, conflicts, resolve8) {
|
|
701
776
|
const byName = (arr) => {
|
|
702
777
|
const m = {};
|
|
703
778
|
for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
|
|
704
779
|
return m;
|
|
705
780
|
};
|
|
706
|
-
const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts,
|
|
781
|
+
const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts, resolve8?.(path, conflicts));
|
|
707
782
|
return Object.keys(merged).sort().map((name) => merged[name]);
|
|
708
783
|
}
|
|
709
784
|
function mergeCastMember(path, conflicts) {
|
|
@@ -718,6 +793,7 @@ function mergeCastMember(path, conflicts) {
|
|
|
718
793
|
return { resolved: true, value: out };
|
|
719
794
|
};
|
|
720
795
|
}
|
|
796
|
+
var PROJECT_HANDLED = /* @__PURE__ */ new Set(["properties", "cast", "gameDataFields", "qualifiers", "locales", "extensions"]);
|
|
721
797
|
function mergeProject(base, ours, theirs) {
|
|
722
798
|
const conflicts = [];
|
|
723
799
|
const merged = {};
|
|
@@ -736,6 +812,9 @@ function mergeProject(base, ours, theirs) {
|
|
|
736
812
|
if (v !== void 0) merged[k] = v;
|
|
737
813
|
} else if (k === "locales") {
|
|
738
814
|
merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
|
|
815
|
+
} else if (k === "extensions" && [base[k], ours[k], theirs[k]].every((x) => x === void 0 || isObj(x))) {
|
|
816
|
+
const v = mergeProjectExtensions(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
|
|
817
|
+
if (Object.keys(v).length > 0) merged[k] = v;
|
|
739
818
|
} else {
|
|
740
819
|
const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
|
|
741
820
|
if (v !== void 0) merged[k] = v;
|
|
@@ -743,6 +822,31 @@ function mergeProject(base, ours, theirs) {
|
|
|
743
822
|
}
|
|
744
823
|
return { type: "project", merged, conflicts, warnings: [] };
|
|
745
824
|
}
|
|
825
|
+
function mergeProjectExtensions(b, o, t, conflicts) {
|
|
826
|
+
const out = {};
|
|
827
|
+
for (const id of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
828
|
+
const bv = b[id], ov = o[id], tv = t[id];
|
|
829
|
+
const path = `extensions.${id}`;
|
|
830
|
+
if (!isObj(ov) || !isObj(tv) || bv !== void 0 && !isObj(bv)) {
|
|
831
|
+
const v = merge3(bv, ov, tv, id, path, conflicts);
|
|
832
|
+
if (v !== void 0) out[id] = v;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
const bo = asMap(bv);
|
|
836
|
+
const entry = {};
|
|
837
|
+
for (const f of /* @__PURE__ */ new Set([...Object.keys(bo), ...Object.keys(ov), ...Object.keys(tv)])) {
|
|
838
|
+
if (f === "settings" && [bo[f], ov[f], tv[f]].every((x) => x === void 0 || isObj(x))) {
|
|
839
|
+
const settings = mergeMap(asMap(bo[f]), asMap(ov[f]), asMap(tv[f]), `${path}.settings`, conflicts);
|
|
840
|
+
if (Object.keys(settings).length > 0) entry[f] = settings;
|
|
841
|
+
} else {
|
|
842
|
+
const v = merge3(bo[f], ov[f], tv[f], id, `${path}.${f}`, conflicts);
|
|
843
|
+
if (v !== void 0) entry[f] = v;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
out[id] = entry;
|
|
847
|
+
}
|
|
848
|
+
return out;
|
|
849
|
+
}
|
|
746
850
|
function mergeGameDataFields(b, o, t, conflicts) {
|
|
747
851
|
const out = {};
|
|
748
852
|
for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
@@ -814,11 +918,19 @@ function mergeNode(b, o, t, path, conflicts, warnings) {
|
|
|
814
918
|
function mergeFields(b, o, t, id, path, conflicts) {
|
|
815
919
|
const out = {};
|
|
816
920
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
817
|
-
const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
921
|
+
const v = k === "gameData" && [b[k], o[k], t[k]].every((x) => x === void 0 || isObj(x)) ? mergeGameData(asMap(b[k]), asMap(o[k]), asMap(t[k]), id, `${path}.${k}`, conflicts) : merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
818
922
|
if (v !== void 0) out[k] = v;
|
|
819
923
|
}
|
|
820
924
|
return out;
|
|
821
925
|
}
|
|
926
|
+
function mergeGameData(b, o, t, id, path, conflicts) {
|
|
927
|
+
const out = {};
|
|
928
|
+
for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
929
|
+
const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
930
|
+
if (v !== void 0) out[k] = v;
|
|
931
|
+
}
|
|
932
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
933
|
+
}
|
|
822
934
|
function mergeChildren(B, O, T, path, conflicts, warnings) {
|
|
823
935
|
const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
|
|
824
936
|
const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
|
|
@@ -908,14 +1020,14 @@ function checkDuplicateIds(root, conflicts) {
|
|
|
908
1020
|
n.children.forEach(walk);
|
|
909
1021
|
};
|
|
910
1022
|
walk(root);
|
|
911
|
-
for (const id of dup) conflicts.push({ id, path:
|
|
1023
|
+
for (const id of dup) conflicts.push({ id, path: `scene[${id}]`, base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
|
|
912
1024
|
}
|
|
913
1025
|
var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
|
|
914
1026
|
var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
915
1027
|
|
|
916
1028
|
// src/validate.ts
|
|
917
1029
|
import { validateProject } from "@patterkit/core";
|
|
918
|
-
import { validateConditions, validateInterpolation, hostScopesToSpec, projectScopes } from "@patterkit/compiler";
|
|
1030
|
+
import { validateConditions, validateInterpolation, hostScopesToSpec, projectScopes, extensionPass } from "@patterkit/compiler";
|
|
919
1031
|
|
|
920
1032
|
// src/compile.ts
|
|
921
1033
|
import { basename as basename2, extname, isAbsolute, resolve as resolve3 } from "path";
|
|
@@ -941,7 +1053,19 @@ function compileScenes(loaded) {
|
|
|
941
1053
|
}));
|
|
942
1054
|
}
|
|
943
1055
|
function compileLoaded(loaded) {
|
|
944
|
-
return exportBundle({
|
|
1056
|
+
return exportBundle({
|
|
1057
|
+
project: loaded.project,
|
|
1058
|
+
scenes: compileScenes(loaded),
|
|
1059
|
+
locales: loaded.locales,
|
|
1060
|
+
gameScopes: loaded.gameScopes?.merged,
|
|
1061
|
+
extensions: loadedExtensions(loaded)
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
function loadedExtensions(loaded) {
|
|
1065
|
+
return {
|
|
1066
|
+
manifests: loaded.manifests,
|
|
1067
|
+
shards: loaded.extensionShards.map((shard, i) => ({ path: loaded.extensionShardFiles[i], shard }))
|
|
1068
|
+
};
|
|
945
1069
|
}
|
|
946
1070
|
function defaultBundlePath(loaded) {
|
|
947
1071
|
return `../patter-dist/${basename2(loaded.projectFile).replace(/\.patterproj$/, "")}.patterc`;
|
|
@@ -959,7 +1083,7 @@ function besideBundlePath(loaded, ext) {
|
|
|
959
1083
|
}
|
|
960
1084
|
|
|
961
1085
|
// src/validate.ts
|
|
962
|
-
import { PROJECT_LOCALE_SCENE, projectLayout as projectLayout2 } from "@patterkit/model";
|
|
1086
|
+
import { EXTENSION_SHARD as EXTENSION_SHARD3, PROJECT_LOCALE_SCENE, projectLayout as projectLayout2 } from "@patterkit/model";
|
|
963
1087
|
|
|
964
1088
|
// src/reachability.ts
|
|
965
1089
|
import { disjuncts, latchOf, scopedRef, terms } from "@wildwinter/expr";
|
|
@@ -1122,9 +1246,10 @@ function reachabilityIssues(loaded, compiled) {
|
|
|
1122
1246
|
|
|
1123
1247
|
// src/pack.ts
|
|
1124
1248
|
import JSZip from "jszip";
|
|
1125
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
1249
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
1126
1250
|
import { dirname as dirname4, join as join4, relative as relative2, sep } from "path";
|
|
1127
|
-
import { parseSource as
|
|
1251
|
+
import { parseSource as parseSource3 } from "@patterkit/core";
|
|
1252
|
+
import { EXTENSION_SHARD as EXTENSION_SHARD2, EXTENSIONS_DIR as EXTENSIONS_DIR2, MANIFEST_EXTENSION as MANIFEST_EXTENSION2 } from "@patterkit/model";
|
|
1128
1253
|
import { ARCHIVE_ENTRY_OPTS } from "@wildwinter/toolkit/archive";
|
|
1129
1254
|
|
|
1130
1255
|
// src/handoff.ts
|
|
@@ -1211,12 +1336,12 @@ function readMarker(text) {
|
|
|
1211
1336
|
}
|
|
1212
1337
|
|
|
1213
1338
|
// src/pack.ts
|
|
1214
|
-
var SHARD_EXTENSIONS = [".patterflow", ".patterloc", ".patterx", ".patterproj"];
|
|
1339
|
+
var SHARD_EXTENSIONS = [".patterflow", ".patterloc", ".patterx", ".patterproj", EXTENSION_SHARD2];
|
|
1215
1340
|
var ENTRY_OPTS = ARCHIVE_ENTRY_OPTS;
|
|
1216
1341
|
async function runPack(startPath) {
|
|
1217
1342
|
const projectFile = findProjectFile(startPath);
|
|
1218
1343
|
const root = dirname4(projectFile);
|
|
1219
|
-
const project =
|
|
1344
|
+
const project = parseSource3(readFileSync4(projectFile, "utf8"));
|
|
1220
1345
|
const unresolved = sidecarIssues(walkFiles(root, CONFLICT_SIDECAR));
|
|
1221
1346
|
if (unresolved.length) {
|
|
1222
1347
|
throw new Error(
|
|
@@ -1224,7 +1349,15 @@ async function runPack(startPath) {
|
|
|
1224
1349
|
` + unresolved.map((i) => ` ${i.file}`).join("\n")
|
|
1225
1350
|
);
|
|
1226
1351
|
}
|
|
1227
|
-
const
|
|
1352
|
+
const shardFiles = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).map((abs) => ({ abs, rel: relative2(root, abs).split(sep).join("/") }));
|
|
1353
|
+
const marked = [...new Set(unresolvedMarkIssues(shardFiles.map((f) => ({ file: f.rel, text: readFileSync4(f.abs, "utf8") }))).map((i) => i.file))];
|
|
1354
|
+
if (marked.length) {
|
|
1355
|
+
throw new Error(
|
|
1356
|
+
`${marked.length} shard(s) still marked with an unresolved merge - resolve the clashes each names under "unresolved", then remove the entries, before packing:
|
|
1357
|
+
` + marked.map((f) => ` ${f}`).join("\n")
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
const files = [...shardFiles, ...extensionManifests(root)].sort((a, b) => a.rel.localeCompare(b.rel));
|
|
1228
1361
|
const scopes = gameScopesSnapshot(root, project);
|
|
1229
1362
|
const handoffs = walkFiles(join4(root, HANDOFF_DIR), ".json").map((abs) => ({ rel: relative2(root, abs).split(sep).join("/"), text: readFileSync4(abs, "utf8") })).filter((f) => {
|
|
1230
1363
|
try {
|
|
@@ -1247,6 +1380,19 @@ async function runPack(startPath) {
|
|
|
1247
1380
|
for (const f of handoffs) zip.file(f.rel, f.text, ENTRY_OPTS);
|
|
1248
1381
|
return zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE", streamFiles: false });
|
|
1249
1382
|
}
|
|
1383
|
+
function extensionManifests(root) {
|
|
1384
|
+
let names;
|
|
1385
|
+
try {
|
|
1386
|
+
names = readdirSync3(join4(root, EXTENSIONS_DIR2), { withFileTypes: true }).filter((e) => e.isFile() && !e.name.startsWith(".") && e.name.endsWith(MANIFEST_EXTENSION2)).map((e) => e.name);
|
|
1387
|
+
} catch {
|
|
1388
|
+
return [];
|
|
1389
|
+
}
|
|
1390
|
+
return names.map((name) => ({ abs: join4(root, EXTENSIONS_DIR2, name), rel: `${EXTENSIONS_DIR2}/${name}` }));
|
|
1391
|
+
}
|
|
1392
|
+
var isManifestEntry = (name) => {
|
|
1393
|
+
const rest = name.startsWith(`${EXTENSIONS_DIR2}/`) ? name.slice(EXTENSIONS_DIR2.length + 1) : void 0;
|
|
1394
|
+
return rest !== void 0 && !rest.includes("/") && rest.endsWith(MANIFEST_EXTENSION2) && rest.length > MANIFEST_EXTENSION2.length;
|
|
1395
|
+
};
|
|
1250
1396
|
|
|
1251
1397
|
// src/validate.ts
|
|
1252
1398
|
function runValidate(loaded) {
|
|
@@ -1258,7 +1404,7 @@ function runValidate(loaded) {
|
|
|
1258
1404
|
const conditions = validateConditions({ project, scenes }, { foreignScopes, gameScopes: merged });
|
|
1259
1405
|
const interpolation = validateInterpolation({ project, scenes, locales }, { foreignScopes, gameScopes: merged });
|
|
1260
1406
|
const gameScopes = gameScopesIssues(loaded, scopes.notes);
|
|
1261
|
-
const hygiene = checkHygiene([loaded.projectFile, ...Object.values(loaded.sceneFiles), ...loaded.localeFiles, ...loaded.authoringFiles]);
|
|
1407
|
+
const hygiene = checkHygiene([loaded.projectFile, ...Object.values(loaded.sceneFiles), ...loaded.localeFiles, ...loaded.authoringFiles, ...loaded.extensionShardFiles]);
|
|
1262
1408
|
const tree = walkFilesByExt(loaded.root, [...SHARD_EXTENSIONS, CONFLICT_SIDECAR, ".patterc"]);
|
|
1263
1409
|
const localisation = locIssues(loaded);
|
|
1264
1410
|
let compiled;
|
|
@@ -1269,8 +1415,9 @@ function runValidate(loaded) {
|
|
|
1269
1415
|
}
|
|
1270
1416
|
const staleBundles = checkBundles(loaded, compiled, tree.get(".patterc"));
|
|
1271
1417
|
if (compiled) gameScopes.push(...unplayableScopes(loaded, compiled));
|
|
1272
|
-
const unresolvedMerges = sidecarIssues(tree.get(CONFLICT_SIDECAR));
|
|
1418
|
+
const unresolvedMerges = [...sidecarIssues(tree.get(CONFLICT_SIDECAR)), ...markedShards(SHARD_EXTENSIONS.flatMap((ext) => tree.get(ext)))];
|
|
1273
1419
|
const orphans = orphanShards(loaded, tree);
|
|
1420
|
+
const extensions = [...extensionIssues(loaded), ...gitattributesHint(loaded)];
|
|
1274
1421
|
const structuralErrors = structural.filter((i) => i.severity !== "warning");
|
|
1275
1422
|
const reachability = compiled && structuralErrors.length === 0 && !conditions.some(isError) ? reachabilityIssues(loaded, compiled) : [];
|
|
1276
1423
|
return {
|
|
@@ -1284,7 +1431,8 @@ function runValidate(loaded) {
|
|
|
1284
1431
|
orphans,
|
|
1285
1432
|
gameScopes,
|
|
1286
1433
|
localisation,
|
|
1287
|
-
|
|
1434
|
+
extensions,
|
|
1435
|
+
ok: !localisation.some(isError) && !extensions.some(isError) && structuralErrors.length === 0 && !conditions.some(isError) && !interpolation.some(isError) && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0 && orphans.length === 0 && !gameScopes.some(isError)
|
|
1288
1436
|
};
|
|
1289
1437
|
}
|
|
1290
1438
|
var isError = (i) => i.severity === "error";
|
|
@@ -1298,9 +1446,53 @@ function exportBlockers(loaded) {
|
|
|
1298
1446
|
return [
|
|
1299
1447
|
...structural.map((i) => `[${i.code}] ${i.message}`),
|
|
1300
1448
|
...[...conditions, ...interpolation].map((i) => `[${i.field}] ${i.nodeId}: ${i.message} (${i.src})`),
|
|
1301
|
-
...locIssues(loaded).filter(isError).map((i) => `[localisation] ${i.file}: ${i.message}`)
|
|
1449
|
+
...locIssues(loaded).filter(isError).map((i) => `[localisation] ${i.file}: ${i.message}`),
|
|
1450
|
+
...extensionIssues(loaded).filter(isError).map((i) => `[extension] ${i.file}: ${i.message}`)
|
|
1302
1451
|
];
|
|
1303
1452
|
}
|
|
1453
|
+
function extensionIssues(loaded) {
|
|
1454
|
+
const loadIssues = loaded.extensionIssues.map((i) => ({ severity: "error", file: i.file, message: i.message }));
|
|
1455
|
+
if (loaded.project.extensions === void 0 && loaded.manifests.length === 0 && loaded.extensionShards.length === 0) return loadIssues;
|
|
1456
|
+
const { issues } = extensionPass({
|
|
1457
|
+
project: loaded.project,
|
|
1458
|
+
scenes: loaded.scenes,
|
|
1459
|
+
...loadedExtensions(loaded),
|
|
1460
|
+
projectPath: loaded.projectFile,
|
|
1461
|
+
sceneFiles: loaded.sceneFiles
|
|
1462
|
+
});
|
|
1463
|
+
return [...loadIssues, ...issues];
|
|
1464
|
+
}
|
|
1465
|
+
function gitattributesHint(loaded) {
|
|
1466
|
+
if (loaded.extensionShardFiles.length === 0) return [];
|
|
1467
|
+
const files = [];
|
|
1468
|
+
let dir = loaded.root, repo = false;
|
|
1469
|
+
for (; ; ) {
|
|
1470
|
+
const f = join5(dir, ".gitattributes");
|
|
1471
|
+
if (existsSync4(f)) files.push(f);
|
|
1472
|
+
if (existsSync4(join5(dir, ".git"))) {
|
|
1473
|
+
repo = true;
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1476
|
+
const up = dirname5(dir);
|
|
1477
|
+
if (up === dir) break;
|
|
1478
|
+
dir = up;
|
|
1479
|
+
}
|
|
1480
|
+
const consulted = repo ? files : files.filter((f) => dirname5(f) === loaded.root);
|
|
1481
|
+
const texts = consulted.map((f) => {
|
|
1482
|
+
try {
|
|
1483
|
+
return readFileSync5(f, "utf8");
|
|
1484
|
+
} catch {
|
|
1485
|
+
return "";
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
if (!texts.some((t) => /\bmerge=patter\b/.test(t))) return [];
|
|
1489
|
+
if (texts.some((t) => /^\s*\*\.patterextension\b.*\bmerge=patter\b/m.test(t))) return [];
|
|
1490
|
+
return [{
|
|
1491
|
+
severity: "warning",
|
|
1492
|
+
file: consulted[0],
|
|
1493
|
+
message: 'the project has extension data, but .gitattributes has no merge rule for it, so git merges it as plain text: add the line "*.patterextension text eol=lf merge=patter"'
|
|
1494
|
+
}];
|
|
1495
|
+
}
|
|
1304
1496
|
function gameScopesIssues(loaded, notes) {
|
|
1305
1497
|
const out = [];
|
|
1306
1498
|
if (loaded.gameScopesMissing) out.push({ file: loaded.projectFile, severity: "error", message: loaded.gameScopesMissing });
|
|
@@ -1326,7 +1518,8 @@ function orphanShards(loaded, tree = walkFilesByExt(loaded.root, SHARD_EXTENSION
|
|
|
1326
1518
|
".patterflow": { what: "scene", home: layout.flow },
|
|
1327
1519
|
".patterloc": { what: "strings", home: layout.strings },
|
|
1328
1520
|
".patterx": { what: "authoring", home: layout.authoring },
|
|
1329
|
-
".patterproj": { what: "project", home: null }
|
|
1521
|
+
".patterproj": { what: "project", home: null },
|
|
1522
|
+
[EXTENSION_SHARD3]: { what: "extension data", home: layout.extensions }
|
|
1330
1523
|
};
|
|
1331
1524
|
const inside = (file, dir) => {
|
|
1332
1525
|
const r = relative3(dir, file);
|
|
@@ -1390,6 +1583,34 @@ function locIssues(loaded) {
|
|
|
1390
1583
|
return out;
|
|
1391
1584
|
}
|
|
1392
1585
|
var hygieneCache = /* @__PURE__ */ new Map();
|
|
1586
|
+
var markCache = /* @__PURE__ */ new Map();
|
|
1587
|
+
function markedShards(files) {
|
|
1588
|
+
const wanted = new Set(files);
|
|
1589
|
+
for (const file of markCache.keys()) if (!wanted.has(file)) markCache.delete(file);
|
|
1590
|
+
const issues = [];
|
|
1591
|
+
for (const file of files) {
|
|
1592
|
+
let mtimeMs, text;
|
|
1593
|
+
try {
|
|
1594
|
+
mtimeMs = statSync2(file).mtimeMs;
|
|
1595
|
+
} catch {
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
const hit = markCache.get(file);
|
|
1599
|
+
if (hit && hit.mtimeMs === mtimeMs) {
|
|
1600
|
+
issues.push(...hit.issues);
|
|
1601
|
+
continue;
|
|
1602
|
+
}
|
|
1603
|
+
try {
|
|
1604
|
+
text = readFileSync5(file, "utf8");
|
|
1605
|
+
} catch {
|
|
1606
|
+
continue;
|
|
1607
|
+
}
|
|
1608
|
+
const found = unresolvedMarkIssues([{ file, text }]);
|
|
1609
|
+
markCache.set(file, { mtimeMs, issues: found });
|
|
1610
|
+
issues.push(...found);
|
|
1611
|
+
}
|
|
1612
|
+
return issues;
|
|
1613
|
+
}
|
|
1393
1614
|
function checkHygiene(files) {
|
|
1394
1615
|
const wanted = new Set(files);
|
|
1395
1616
|
for (const file of hygieneCache.keys()) if (!wanted.has(file)) hygieneCache.delete(file);
|
|
@@ -1425,10 +1646,11 @@ function checkHygiene(files) {
|
|
|
1425
1646
|
}
|
|
1426
1647
|
|
|
1427
1648
|
// src/export.ts
|
|
1649
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
1428
1650
|
import { canonicalStringify as canonicalStringify3 } from "@patterkit/core";
|
|
1429
1651
|
|
|
1430
1652
|
// src/audio-index.ts
|
|
1431
|
-
import { readdirSync as
|
|
1653
|
+
import { readdirSync as readdirSync4 } from "fs";
|
|
1432
1654
|
import { join as join6, relative as relative4, resolve as resolve4, sep as sep2 } from "path";
|
|
1433
1655
|
import { deriveRecordingFolders, DEFAULT_RECORDING_STATUSES } from "@patterkit/model";
|
|
1434
1656
|
var AUDIO_MANIFEST_SCHEMA = "patter/audio@0";
|
|
@@ -1464,7 +1686,7 @@ function scanAudio(loaded) {
|
|
|
1464
1686
|
return pickAudio(rungs.map((r) => {
|
|
1465
1687
|
let files;
|
|
1466
1688
|
try {
|
|
1467
|
-
files =
|
|
1689
|
+
files = readdirSync4(r.dir);
|
|
1468
1690
|
} catch {
|
|
1469
1691
|
files = void 0;
|
|
1470
1692
|
}
|
|
@@ -1493,11 +1715,19 @@ function audioManifestWrite(loaded, snapshot) {
|
|
|
1493
1715
|
// src/export.ts
|
|
1494
1716
|
function refuseUnresolvedMerge(loaded) {
|
|
1495
1717
|
const issues = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
|
|
1496
|
-
if (
|
|
1497
|
-
|
|
1498
|
-
|
|
1718
|
+
if (issues.length) {
|
|
1719
|
+
throw new Error(
|
|
1720
|
+
`${issues.length} unresolved merge conflict(s) - resolve them and delete the .patterconflict sidecar(s) first:
|
|
1499
1721
|
` + issues.map((i) => ` ${i.file}`).join("\n")
|
|
1500
|
-
|
|
1722
|
+
);
|
|
1723
|
+
}
|
|
1724
|
+
const marked = [...new Set(unresolvedMarkIssues(SHARD_EXTENSIONS.flatMap((ext) => walkFiles(loaded.root, ext)).map((file) => ({ file, text: readFileSync6(file, "utf8") }))).map((i) => i.file))];
|
|
1725
|
+
if (marked.length) {
|
|
1726
|
+
throw new Error(
|
|
1727
|
+
`${marked.length} shard(s) still marked with an unresolved merge - resolve the clashes each names under "unresolved", then remove the entries:
|
|
1728
|
+
` + marked.map((f) => ` ${f}`).join("\n")
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1501
1731
|
}
|
|
1502
1732
|
function refuseInvalid(loaded, opts) {
|
|
1503
1733
|
if (opts.allowInvalid) return;
|
|
@@ -1540,7 +1770,7 @@ import { canonicalStringify as canonicalStringify4 } from "@patterkit/core";
|
|
|
1540
1770
|
import { DEFAULT_PAD_AFTER } from "@patterkit/model";
|
|
1541
1771
|
|
|
1542
1772
|
// src/playable-runtime.ts
|
|
1543
|
-
var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var re=Object.defineProperty;var at=Object.getOwnPropertyDescriptor;var ct=Object.getOwnPropertyNames;var lt=Object.prototype.hasOwnProperty;var pt=(n,e)=>{for(var t in e)re(n,t,{get:e[t],enumerable:!0})},dt=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ct(e))!lt.call(n,s)&&s!==t&&re(n,s,{get:()=>e[s],enumerable:!(r=at(e,s))||r.enumerable});return n};var ut=n=>dt(re({},"__esModule",{value:!0}),n);var jt={};pt(jt,{Checkpoint:()=>_,DEFAULT_PAD_AFTER:()=>x,Engine:()=>ee,Flow:()=>P,buildTagIndex:()=>Z,compareStages:()=>O,describeBundle:()=>tt,effectiveGameData:()=>nt,gameDataFields:()=>rt,gameDataValue:()=>ge,nextStage:()=>B,stageIndex:()=>R});function A(n,e){return n!==void 0&&Object.prototype.hasOwnProperty.call(n,e)?n[e]:void 0}function E(n){switch(n[0]){case"b":return{kind:"bool",value:n[1]};case"n":return{kind:"number",value:n[1]};case"s":return{kind:"string",value:n[1]};case"sv":return{kind:"scopedvar",scope:n[1],name:n[2]};case"u":return{kind:"unary",op:n[1],operand:E(n[2])};case"bin":return{kind:"binary",op:n[1],left:E(n[2]),right:E(n[3])};case"call":{let e=n.slice(2).map(E);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var u=class extends Error{constructor(e){super(e),this.name="EvalError"}};function W(n,e,t){let r=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),s=i=>{switch(i.kind){case"bool":return i.value;case"number":return i.value;case"string":return i.value;case"scopedvar":{let o=A(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):A(o,i.name);if(a===void 0){if(r.get(i.scope)==="throw")throw new u(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!A(t.functions,i.name)){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new u(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=ne(a,e);if(c===void 0)throw new u("advance() needs a quality reference (@scope.name of a quality property)");let l=b(s(a),c,"advance");return c[Math.min(l+1,c.length-1)]}let o=A(t.functions,i.name);if(!o)throw new u(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new u("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=s(i.operand);if(typeof a!="boolean")throw new u(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new u(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'and\' requires boolean operands, left is ${typeof h}`);if(!h)return!1;let g=s(i.right);if(typeof g!="boolean")throw new u(`\'and\' requires boolean operands, right is ${typeof g}`);return g}if(i.op==="or"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'or\' requires boolean operands, left is ${typeof h}`);if(h)return!0;let g=s(i.right);if(typeof g!="boolean")throw new u(`\'or\' requires boolean operands, right is ${typeof g}`);return g}let o=s(i.left),a=s(i.right),c=ne(i.left,e),l=ne(i.right,e),p=c??l;if(p!==void 0){if(c&&l&&!gt(c,l)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new u(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return b(o,p,">")>b(a,p,">");case">=":return b(o,p,">=")>=b(a,p,">=");case"<":return b(o,p,"<")<b(a,p,"<");case"<=":return b(o,p,"<=")<=b(a,p,"<=");case"+":case"-":case"*":case"/":throw new u(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return xe(o,a);case"!=":return!xe(o,a);case">":return C(o,a,">"),o>a;case">=":return C(o,a,">="),o>=a;case"<":return C(o,a,"<"),o<a;case"<=":return C(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new u(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return C(o,a,"-"),o-a;case"*":return C(o,a,"*"),o*a;case"/":if(C(o,a,"/"),a===0)throw new u("division by zero");return o/a}}}};return s(n)}function xe(n,e){if(Array.isArray(n)||Array.isArray(e)){if(!Array.isArray(n)||!Array.isArray(e)||n.length!==e.length)return!1;let t=[...n].sort(),r=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==r[s])return!1;return!0}return n===e}function C(n,e,t){if(typeof n!="number"||typeof e!="number")throw new u(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}function ne(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function b(n,e,t){if(typeof n!="string")throw new u(`\'${t}\' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new u(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var gt=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);function F(n){if(Number.isNaN(n)||!Number.isFinite(n))return 0;let e=Math.trunc(n)%4294967296;return e<0?e+4294967296:e}function se(n){let e=F(n);return{next(){e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296},state(){return e}}}var oe=n=>Array.isArray(n)?n:n.stages??[],ft=n=>Array.isArray(n)?"flags":n==null?"nothing":typeof n;function R(n,e){return typeof e=="string"?oe(n).indexOf(e):-1}function ie(n,e){if(typeof e!="string")throw new u(`a stage is a name, got ${ft(e)}`);let t=n.indexOf(e);if(t<0)throw new u(`"${e}" is not a stage of this quality (stages: ${n.join(", ")})`);return t}function O(n,e,t){let r=oe(n),s=ie(r,e)-ie(r,t);return s<0?-1:s>0?1:0}function B(n,e){let t=oe(n);return t[Math.min(ie(t,e)+1,t.length-1)]}var ht={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},mt=[ht];function ke(n,e,t){let r=t?.countingCalls??mt;return K(n,t?.want??!0,e,r)}function K(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=K(n.left,e,t,r),i=K(n.right,e,t,r);return n.op==="and"===e?s>0&&i>0?s+i:0:Math.max(s,i)}if(n.kind==="unary"&&n.op==="not")return K(n.operand,!e,t,r);if(n.kind==="call"){let s=r.find(i=>i.name===n.name);if(s){let i=s.count(n),o=t(n);return e?o?i:0:o?0:1}}return t(n)===e?1:0}var v=class n{values=Object.create(null);decls=new Map;subscribers=new Set;auditors=new Set;norm;pathPrefix;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),this.pathPrefix=t?.pathPrefix??"",this.seed(e)}seed(e){for(let t of e){let r=this.norm(t.name);this.decls.set(r,t),this.values[r]=structuredClone(t.default??N(t))}}get(e){return this.values[this.norm(e)]}normalise(e){return this.norm(e)}writable(e){return this.decls.get(this.norm(e))?.writable!==!1}set(e,t,r){let s=this.norm(e);if(!r?.host&&!this.writable(s))throw new Error(`\'${e}\' is read-only`);let i={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let o of[...this.auditors])o(i);if(!i.silent)for(let o of[...this.subscribers])o(i);return i}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>Ce(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function Ce(n,e,t,r,s=""){let i=r??n.name.toLowerCase();return{name:i,path:s+i,type:n.type,value:e,default:n.default??N(n),...n.values!==void 0?{values:n.values}:{},...n.stages!==void 0?{stages:n.stages}:{},writable:t??n.writable??!0}}var St=n=>n.toLowerCase(),ae=1,Q=class{scopes=new Map;parked=new Map;rev=0;get revision(){return this.rev}defineOwned(e,t,r){let s=typeof r=="string"?{pathPrefix:r}:r??{},i=new v(t,{pathPrefix:s.pathPrefix??`${e}.`,...s.normalise?{normalise:s.normalise}:{}});return this.mountOwned(e,i,s.owner!==void 0?{owner:s.owner}:void 0)}mountOwned(e,t,r){this.assertFree(e,r?.owner),this.scopes.set(e,{kind:"owned",bag:t,...r?.owner!==void 0?{owner:r.owner}:{}}),this.rev++;let s=this.parked.get(e);return s&&(t.load(s),this.parked.delete(e)),this}remove(e,t){let r=this.scopes.get(e);if(!r)throw new Error(`unknown scope \'@${e}\'`);return t?.keep&&r.kind==="owned"&&this.parked.set(e,r.bag.save()),this.scopes.delete(e),this.rev++,this}discardParked(e){if(e===void 0)this.parked.clear();else for(let t of[...this.parked.keys()])t.startsWith(e)&&this.parked.delete(t);return this}ownedBag(e){let t=this.scopes.get(e);if(!t||t.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);return t.bag}reseedOwned(e,t){return this.ownedBag(e).reseed(t),this}defineForeign(e,t,r=[],s=!0){let i=typeof s=="boolean"?{writable:s}:s;this.assertFree(e,i.owner);let o=i.normalise??St,a=new Map;for(let c of r)a.set(o(c.name),c);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:a,scopeWritable:i.writable??!0,norm:o,...i.owner!==void 0?{owner:i.owner}:{}}),this.rev++,this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(r)return r.kind==="owned"?r.bag.get(t):r.resolver.get(r.norm(t))}set(e,t,r,s){let i=this.scopes.get(e);if(!i)throw new Error(`unknown scope \'@${e}\'`);if(i.kind==="owned"){if(!s?.host&&!i.bag.writable(t))throw new Error(`\'@${e}.${t}\' is read-only`);i.bag.set(t,r,s?.host?{host:!0}:void 0);return}let o=i.norm(t);if(!i.resolver.set)throw new Error(`\'@${e}.${t}\' is read-only`);if(!s?.host&&!this.foreignWritable(i,o))throw new Error(`\'@${e}.${t}\' is read-only`);i.resolver.set(o,r)}foreignWritable(e,t){return e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}listProperties(){let e=[];for(let[t,r]of this.scopes){let s=r.owner!==void 0?{owner:r.owner}:{};if(r.kind==="owned")for(let i of r.bag.rows())e.push({scope:t,...s,...i});else for(let[i,o]of r.decls)e.push({scope:t,...s,...Ce(o,r.resolver.get(i),this.foreignWritable(r,i),i,`${t}.`)})}return e}toEvalContext(e,t){let r=this.view(t?.aliases),s={};for(let[o,a]of r)s[o]=a.kind==="owned"?a.bag.values:a.resolver;let i=this.qualityLadders(r);return i.size===0?{scopes:s,host:e}:{scopes:s,host:e,qualities:(o,a)=>{let c=r.get(o);return c?i.get(o)?.get(yt(c)(a)):void 0}}}view(e){let t=new Map(this.scopes);for(let[r,s]of Object.entries(e??{})){let i=this.scopes.get(s);if(!i)throw new Error(`alias \'@${r}\' names \'${s}\', which is not registered`);t.set(r,i)}return t}qualityLadders(e){let t=new Map;for(let[r,s]of e)for(let[i,o]of Ee(s)){if(o.type!=="quality"||o.stages===void 0)continue;let a=t.get(r);a||(a=new Map,t.set(r,a)),a.set(i,o.stages)}return t}toSchema(e){let t=new Map;for(let[r,s]of this.view(e?.aliases)){let i=Ee(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(r,o)}return{properties:t}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());for(let[t,r]of this.parked)e[t]=structuredClone(r);return e}load(e,t){t?.keepParked||this.parked.clear();for(let[r,s]of Object.entries(e)){let i=this.scopes.get(r);i?.kind==="owned"?i.bag.load(s):i||this.parked.set(r,structuredClone(s))}}saveFragment(){return{version:ae,scopes:this.save()}}loadFragment(e){if(e.version!==ae)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${ae})`);this.load(e.scopes)}assertFree(e,t){let r=this.scopes.get(e);if(!r)return;let s=r.owner!==void 0?` by ${r.owner}`:"",i=t!==void 0?` (wanted by ${t})`:"";throw new Error(`scope \'@${e}\' is already registered${s}${i}`)}};function Ee(n){return n.kind==="foreign"?[...n.decls.entries()]:n.bag.declarations().map(e=>[n.bag.normalise(e.name),e])}function yt(n){return n.kind==="foreign"?n.norm:e=>n.bag.normalise(e)}function N(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return n.values?.[0]??"";case"flags":return[];case"quality":return n.stages?.[0]??"";default:return!1}}var De=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function T(n){return n.ctx.host??{}}var ce={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new u("random(a, b) requires exactly 2 arguments");let t=T(e).nextRandom;if(!t)throw new u("random() called without a PRNG in context");let r=e.evaluate(n[0]),s=e.evaluate(n[1]);if(typeof r!="number"||typeof s!="number")throw new u("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new u("random(a, b) arguments must be integers");let i=Math.min(r,s),o=Math.max(r,s);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:Pe("check_flags"),eval(n,e){let t=Ie(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new u("check_flags() flag args must be +flagName or -flagName");if(s.sign==="+"?!t.includes(s.name):t.includes(s.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:Pe("set_flags"),eval(n,e){let t=[...Ie(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new u("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let i=t.indexOf(s.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:z("visits"),eval:(n,e)=>T(e).visits?.(J(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:z("seen"),eval:(n,e)=>(T(e).visits?.(J(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:z("patter_visits"),eval:(n,e)=>T(e).patterVisits?.(J(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:z("patter_seen"),eval:(n,e)=>(T(e).patterVisits?.(J(n,e,"patter_seen"))??0)>0}}},ir=De.map(n=>n.token).filter(n=>n!=="patter"&&n!=="scene");function Re(n,e){let t=n.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function J(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new u(`${t}(id) requires a string node id`);return r}function z(n){return(e,t)=>{let r=e[0];r&&r.kind!=="string"&&t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(id): the argument must be a string id literal (a scene / block / node id)`})}}function Ie(n,e,t){if(!n)throw new u(`${t}() requires at least one argument (the flags variable)`);let r=e.evaluate(n);if(Array.isArray(r))return r;if(r===!1||r===null||r===void 0)return[];throw new u(`${t}() first argument must be a flags property`)}function Pe(n){return(e,t)=>{if(e.length===0)return;let r=e[0];if(r.kind!=="scopedvar"){t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(): first argument must be a flags property reference (@name or @scope.name)`});return}let s=t.schema.properties.get(r.scope)?.get(r.name);if(s&&s.type!=="flags"){let i=r.scope===t.defaultScope?r.name:`${r.scope}.${r.name}`;t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(): \'@${i}\' is not a flags property (got ${s.type})`});return}for(let i=1;i<e.length;i++){let o=e[i];o.kind!=="flagdelta"?t.report({path:[...t.path,"args",i],kind:"wrong-arg-type",severity:"error",message:`${n}(): argument ${i+1} must be +flagName or -flagName`}):s?.type==="flags"&&s.enumValues&&!s.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${n}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var wt=/^@[A-Za-z0-9_.]+$/;function*bt(n){let e="",t=0;for(;t<n.length;){let r=n[t];if(r==="{"&&n[t+1]==="{"){e+="{",t+=2;continue}if(r==="}"&&n[t+1]==="}"){e+="}",t+=2;continue}if(r==="{"){let s=n.indexOf("}",t+1);if(s!==-1){let i=n.slice(t,s+1),o=n.slice(t+1,s).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:wt.test(o)?o:void 0},t=s+1;continue}e+=i,t=s+1;continue}}e+=r,t+=1}e&&(yield{kind:"text",value:e})}function vt(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function xt(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function kt(n){let e="",t=!1;for(let r of n){if(xt(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function Oe(n,e,t){if(e.length===0||n.indexOf(e)<0)return n;let r="",s=0,i=!1;for(;s<n.length;){if(n.startsWith(e,s)){let o=n.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}r+=n.slice(s);break}r+=n[s],s+=1}return i?kt(r):n}function Be(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of bt(n)){if(r.kind==="text"){t+=r.value;continue}if(!r.ref){t+=r.raw;continue}let s=e(r.ref);t+=s===void 0?"":vt(s)}return t}function D(n,e){for(let t of n){e(t);let r=t.children;r&&D(r,e)}}function Et(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function w(n){let e=n.gameId?.trim();return e||Et(n.name)}var x=.6;function Y(n){return`cast:${n}`}function X(n){return`qualifier:${n}`}function Ae(n,e){return`speaker:${n}:${e}`}var Fe="speaker:pattern",Ne="{name} ({qualifier})";function Te(n,e,t){return n.split("{name}").map(r=>r.split("{qualifier}").join(t)).join(e)}var le={open:"[",close:"]"},Le="SFX";var L=3;function j(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function Z(n){let e=new Map,t=(r,s)=>{let i=j([...s,...r.tags??[]]);if(e.set(r.id,i),r.type==="group"&&r.prompt&&e.set(r.prompt.id,j([...i,...r.prompt.tags??[]])),r.type==="group")for(let o of r.children)t(o,i);else for(let o of r.beats??[])e.set(o.id,j([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=j(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=j([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var je=n=>n.kind==="line"||n.kind==="text",V=n=>typeof n=="number"&&Number.isFinite(n)?n:void 0;function Ve(n){let e=new Map,t=(i,o,a)=>{let c=i.kind==="gameEvent"?void 0:V(i.padAfter),l=c??o;a&&l<0&&(l=0),e.set(i.id,c!==void 0?{own:c,resolved:l}:{resolved:l})},r=(i,o)=>{let a=V(i.padAfterDefault)??o;if(i.type==="group"){i.prompt&&t(i.prompt,a,!1);for(let p of i.children)r(p,a);return}let c=i.beats??[],l=-1;c.forEach((p,h)=>{je(p)&&(l=h)}),c.forEach((p,h)=>{je(p)&&t(p,a,h===l)})},s=V(n.padAfterDefault)??x;for(let i of Object.values(n.scenes)){let o=V(i.padAfterDefault)??s;for(let a of i.blocks){let c=V(a.padAfterDefault)??o;for(let l of a.children)r(l,c)}}return e}var Ge=new WeakMap,qe=new WeakMap;function Ct(n){let e=qe.get(n);return e||(e=Z(n),qe.set(n,e)),e}var Me=new WeakMap;function Dt(n){let e=Me.get(n);return e||(e=Ve(n),Me.set(n,e)),e}var $e=new WeakMap;function It(n){let e=$e.get(n);if(e)return e;e=new Map;for(let t of Object.values(n.scenes))for(let r of t.blocks)D(r.children,s=>{if(s.type==="group"){s.prompt&&e.set(s.prompt.id,s.prompt);return}for(let i of s.beats??[])e.set(i.id,i)});return $e.set(n,e),e}function I(n,e){return n.tagIndex.get(e)?.slice()}var M="Patter",G=n=>n.replace(/%/g,"%25").replace(/\\//g,"%2F"),Pt=n=>n.replace(/%2F/g,"/").replace(/%25/g,"%"),_e=Symbol("ownsRegistry"),y={stage:n=>`patter/scene/${G(n)}`,flow:n=>`patter/flow/${G(n)}/`,flowGlobals:n=>`patter/flow/${G(n)}/patter`,flowScene:(n,e)=>`patter/flow/${G(n)}/scene/${G(e)}`},_=class{constructor(e){this.journal=e}journal},ee=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=r;let i=s[r]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let l=new Map;for(let d of e.cast??[])d.displayName&&l.set(d.name,d.displayName);let p=new Map;for(let d of e.qualifiers??[])d?.gameId&&p.set(d.gameId,d.name);let h=new Map;for(let d of e.cast??[])for(let[f,S]of Object.entries(d.qualifierLabels??{}))typeof S=="string"&&h.set(`${d.name}\\0${f}`,S);let g=e.speakerPattern??Ne;this.defaultSeed=(t.seed??2654435769)>>>0;let fe=new Map,he=new Map,me=new Map;for(let[d,f]of Object.entries(e.scenes)){this.sceneGameIdToId.set(w(f),d);let S=new Map;for(let m of f.blocks)he.set(m.id,{sceneId:d}),me.set(m.id,m),S.set(w(m),m.id),D(m.children,U=>fe.set(U.id,U));this.blockGameIdToId.set(d,S)}let Se=e.properties??[],te=Se.filter(d=>d.shared??!0).map(Ke),st=Se.filter(d=>!(d.shared??!0)).map(Ke),it=new Set(te.map(d=>d.name.toLowerCase())),k=t.registry??new Q,ye=!t.registry||t[_e]===!0,we=new v(te,{pathPrefix:"@patter."}),be=[],H=[];try{k.mountOwned("patter",we,{owner:M}),H.push("patter");let d=new Set;for(let[f,S]of Object.entries(Qe(t))){let m=e.scopeRegistry?.scopes.find(ot=>ot.token===f),U=(m?.declarations??[]).map(Xe);k.defineForeign(f,S,U,{writable:m?.writable??!0,owner:M}),H.push(f),d.add(f)}for(let f of ye?e.scopeRegistry?.scopes??[]:[]){if(d.has(f.token)||k.has(f.token))continue;let S=(f.declarations??[]).map(m=>Bt(m,f.writable));k.defineOwned(f.token,S,{owner:M}),H.push(f.token),be.push(f.token)}}catch(d){for(let f of H)k.remove(f,{keep:!0});throw d}let ve=new Map;for(let[d,f]of Object.entries(e.scenes)){let S=new Set((f.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));ve.set(d,S)}this.host={logEnabled:t.log??!1,tracing:t.log??!1,emitEngine:(d,f,S)=>this.emitEngine(d,f,S),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,qualifierDisplay:p,speakerLabels:h,speakerPattern:g,nodeIndex:fe,blockIndex:he,blockById:me,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:Ct(e),padIndex:Dt(e),registry:k,ownsRegistry:ye,patterBag:we,hostScopes:be,patterSharedDecls:te,patterLocalDecls:st,patterSharedNames:it,sceneSharedNames:ve,sharedVisits:new Map,qualityLadders:new Map,sharedSelectors:new Map,stageBags:new Map,journal:null,customRng:t.rng,onDryChoice:t.onDryChoice,onError:t.onError??(d=>console.warn(`Patterplay: ${d.kind} on ${d.node} in flow \'${d.flow}\' failed, played through: ${d.message}`)),replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??le).open,captionClose:(e.closedCaptions??le).close,captionCharacter:e.closedCaptions?.character||Le,refSplitCache:new Map,refSplitRevision:k.revision}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),r=o=>(o.setLocale(this.currentLocale),o.setClosedCaptions(this.host.captionsOn),o),s=()=>new n(e,{...this.creationOptions,registry:this.host.registry,[_e]:this.host.ownsRegistry});this.release(!0);let i=s();try{return i.loadGame(t),r(i)}catch{i.release(!0);let o=s();for(let[a,c]of Object.entries(t.flows)){let l=c.cursor.currentSceneId;try{o.openFlow(a,l!==null?{scene:l}:{})}catch{}}return r(o)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){this.assertExternalScopes();let{sceneId:r,blockId:s}=this.resolveOpenAddress(t.scene,t.block),i=this.flowsById.get(e),o=this.host.journal;if(o&&i&&!i.isClosed)throw new Error(`openFlow would replace the open flow \'${e}\', which can\'t be undone while a checkpoint is open`);i?.close();let a=new P(e,this.host,t.seed??this.defaultSeed);return o&&(o.flows.add(a),o.opened.add(a),o.undo.push(()=>{a.close(),i?this.flowsById.set(e,i):this.flowsById.delete(e)})),this.flowsById.set(e,a),a.begin(r,s),a}runFlow(e,t,r){let s=this.flowsById.get(e);if(!s)return this.openFlow(e,{scene:t,block:r}).advanceToStop().played;if(!s.goto(t,r))throw new Error(`runFlow: address not found: ${t}${r===void 0?"":` / ${r}`}`);return s.advanceToStop().played}assertExternalScopes(){for(let e of this.host.bundle.externalScopes??[])if(!this.host.registry.has(e))throw new Error(`this content names @${e}, which no engine on this registry registered: give every engine the game\'s one registry`)}resolveOpenAddress(e,t){if(e!==void 0){let r=pe(this.host,e);if(r===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:r};let s=de(this.host,r,t);if(s===void 0)throw new Error(`unknown block: ${t}`);return{sceneId:r,blockId:s}}if(t!==void 0){if(!this.host.blockIndex.has(t))throw new Error(`unknown block: ${t}`);return{blockId:t}}if(Object.keys(this.host.bundle.scenes).length===0)throw new Error("no scenes in bundle");return{}}resolveSceneRef(e){if(e!=null)return pe(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:de(this.host,e,t)}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?w(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?w(t):void 0}tagsForBeat(e){return I(this.host,e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?I(this.host,t):void 0)??[]}tagsForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t);return(s!=null?I(this.host,s):void 0)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.gameData}}gameDataForBlock(e,t){let r=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(r!=null?this.host.blockById.get(r):void 0)?.gameData}}getCast(){let e=[];for(let t of this.host.bundle.cast??[])t?.name&&e.push(t.name);return e}castForScene(e){let t=this.resolveSceneRef(e),r=t!=null?this.host.bundle.scenes[t]:void 0;if(!r)return[];let s=new Set;for(let i of r.blocks)He(i.children,s);return[...s]}castForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t),i=s!=null?this.host.blockById.get(s):void 0;if(!i)return[];let o=new Set;return He(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...w(e)?{gameId:w(e)}:{},name:e.name,...Ue(e.gameData),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...w(t)?{gameId:w(t)}:{},name:t.name,...Ue(t.gameData),...this.tagsField(t.id),children:t.children.map(r=>this.outlineNode(r))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let r of t.blocks)D(r.children,s=>{if(s.type==="snippet")for(let i of s.beats??[])e.push({sceneId:t.id,blockId:r.id,snippetId:s.id,beat:this.beatInfo(i)})});return e}outlineNode(e){return e.type==="group"?{type:"group",id:e.id,...this.tagsField(e.id),...e.selector?{selector:e.selector}:{},...e.prompt?{prompt:this.beatInfo(e.prompt)}:{},children:e.children.map(t=>this.outlineNode(t))}:{type:"snippet",id:e.id,...this.tagsField(e.id),beats:(e.beats??[]).map(t=>this.beatInfo(t)),...e.jump?{jumpTo:e.jump.to,...e.jump.mode?{jumpMode:e.jump.mode}:{}}:{}}}beatInfo(e){let t=I(this.host,e.id),r={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){r.character=e.character;let i=this.host.defaultStrings[Y(e.character)]??this.host.castDisplay.get(e.character);i!==void 0&&(r.characterName=i)}if(e.direction!==void 0&&(r.direction=e.direction),e.qualifier!==void 0){r.qualifier=e.qualifier;let i=this.host.defaultStrings[X(e.qualifier)]??this.host.qualifierDisplay.get(e.qualifier);i!==void 0&&(r.qualifierName=i)}let s=q(this.host,e.character,e.qualifier,!0);s!==void 0&&(r.speakerLabel=s)}if(e.kind==="line"||e.kind==="text"){let s=this.host.padIndex.get(e.id);r.padAfter=s?.resolved??x,s?.own!==void 0&&(r.ownPadAfter=s.own);let i=this.host.defaultStrings[e.id];i!==void 0&&(r.text=i)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=I(this.host,e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}listBags(){let e=[{bag:this.host.patterBag}];for(let[t,r]of this.host.stageBags)e.push({bag:r,pathPrefix:`@scene:${t}.`});return e}checkpoint(){if(this.host.journal)throw new Error("a checkpoint is already open: roll it back or commit it first");let e={undo:[],flows:new Set,opened:new Set,selectors:new Set,flowSelectors:new Map};return this.host.journal=e,new _(e)}rollback(e){this.endCheckpoint(e);let t=e.journal.undo;for(let r=t.length-1;r>=0;r--)t[r]();n.forget(e.journal)}commit(e){this.endCheckpoint(e),n.forget(e.journal)}get inCheckpoint(){return this.host.journal!==null}endCheckpoint(e){if(this.host.journal!==e.journal)throw new Error("that checkpoint is not the open one");this.host.journal=null}static forget(e){e.undo=[],e.flows.clear(),e.opened.clear(),e.selectors.clear(),e.flowSelectors.clear()}refuseInCheckpoint(e){if(this.host.journal)throw new Error(`${e} can\'t be undone, so it isn\'t allowed while a checkpoint is open`)}captureFlow(e){let t=this.flowsById.get(e);if(!t)throw new Error(`no open flow \'${e}\' to capture`);return{version:L,...t.snapshot(),...Ft(e,this.host.registry.save())}}restoreFlow(e,t){this.refuseInCheckpoint("restoreFlow");let r=t.version;if(r!==2&&r!==L)throw new Error(`unsupported flow state version: ${String(r)}`);let s=t.cursor;if(typeof s!="object"||s===null)throw new Error("malformed flow state: no cursor");if(this.flowsById.has(e))throw new Error(`a flow \'${e}\' is already open; close it before restoring another under that id`);this.assertExternalScopes();let i=y.flow(e);this.host.registry.discardParked(i),this.host.registry.load(et(e,t),{keepParked:!0});let o=new P(e,this.host,this.defaultSeed);try{o.restore(t)}catch(a){throw o.close(),this.host.registry.discardParked(i),a}return this.flowsById.set(e,o),o}closeFlow(e){this.refuseInCheckpoint("closeFlow"),this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){this.refuseInCheckpoint("reset");for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.patterBag.reseed(this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear();for(let e of this.host.stageBags.keys())this.host.registry.remove(y.stage(e));this.host.stageBags.clear(),this.host.registry.discardParked("patter/")}release(e){for(let r of this.flowsById.values())r.releaseBags(e),r.close();this.flowsById.clear();let t=this.host.registry;for(let r of this.host.stageBags.keys())t.remove(y.stage(r),{keep:e});this.host.stageBags.clear();for(let r of["patter",...this.host.hostScopes])t.has(r)&&t.remove(r,{keep:e});for(let r of Object.keys(Qe(this.creationOptions)))t.has(r)&&t.remove(r)}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.registry.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e),i=this.host.journal?this.host.registry.get(r,s):void 0;this.host.registry.set(r,s,t,{host:!0}),this.host.journal&&i!==void 0&&this.host.journal.undo.push(()=>this.host.registry.set(r,s,i,{host:!0}))}stageIndex(e,t){return R(this.ladderOf(e),t)}compareStages(e,t,r){return O(this.ladderOf(e),t,r)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:r}=this.splitShared(e),s=Ot(this.host,t,r);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}log(){return this.engineLog}clearLog(){this.engineLog.length=0}onTrace(e){return this.engineTraceHandlers.add(e),this.host.tracing=!0,()=>{this.engineTraceHandlers.delete(e),this.host.tracing=this.host.logEnabled||this.engineTraceHandlers.size>0}}emitEngine(e,t,r){for(let s of this.engineTraceHandlers)s(e,t);this.host.logEnabled&&this.engineLog.push({...t,flow:e,seq:this.engineSeq++,...r?{scene:r}:{}})}getVisitCounts(){return Object.fromEntries(this.host.sharedVisits)}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:N(e),writable:e.writable??!0}))}splitShared(e){let t=Ze(this.host,e);if(t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return{patter:this.host.patterBag.save()}}load(e){e.patter&&this.host.patterBag.load(e.patter)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:L,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:ze(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==L)throw new Error(`unsupported save version: ${String(t)}`);let r=e.flows;if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("malformed save: no flows");this.assertExternalScopes();let s=this.host.registry;for(let[o,a]of this.flowsById)a.releaseBags(o in e.flows),a.close();this.flowsById.clear();for(let o of this.host.stageBags.keys())s.remove(y.stage(o),{keep:!0});this.host.stageBags.clear();let i=e.version===2?At(e):e.registry;i&&(this.host.ownsRegistry?s.load(i):s.load(i,{keepParked:!0})),this.host.sharedVisits.clear();for(let[o,a]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(o,a);this.host.sharedSelectors.clear();for(let[o,a]of Ye(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new P(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}}},P=class{id;host;local;registered=new Set;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPrompt=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.patterBag.get(e):this.local.get(e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.registry.set("patter",e,t):this.local.set(e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;if(t!==null)return this.sceneBagFor(t,e)?.get(e)},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.sceneBagFor(r,e);s&&s.set(e,t)}};evalCtx;ctxRevision=-1;registryQualities;flowLog=[];flowSeq=0;constructor(e,t,r){this.id=e,this.host=t,this.rngState=F(r),this.local=this.newLocal(),this.evalCtx={scopes:{},host:{nextRandom:this.rng,visits:s=>this.visitCounts.get(s)??0,patterVisits:s=>this.host.sharedVisits.get(s)??0},qualities:(s,i)=>this.stagesFor(s,i)}}context(){let e=this.host.registry;if(e.revision!==this.ctxRevision){let t=e.toEvalContext(),r=this.evalCtx.scopes;for(let s of Object.keys(r))delete r[s];Object.assign(r,t.scopes),r.patter=this.patterResolver,r.scene=this.sceneResolver,this.registryQualities=t.qualities,this.ctxRevision=e.revision}return this.evalCtx}stagesFor(e,t){let r=t.toLowerCase();if(e==="patter")return $(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(r);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:$(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(r)}return this.registryQualities?.(e,t)??$(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(r)}begin(e,t){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");if(this.releaseBags(!1),this.host.registry.discardParked(y.flow(this.id)),this.mountLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.clearPending(),this.started=!0,t){let r=this.host.blockIndex.get(t);if(!r)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(r.sceneId),this.stack=[{sceneId:r.sceneId,containerId:t,index:0}],this.enter(t)}else{let r=e??Object.keys(this.host.bundle.scenes)[0],s=r?this.host.bundle.scenes[r]:void 0;if(!s)throw new Error(r?`unknown scene: ${r}`:"no scenes in bundle");this.enterSceneSetup(r);let i=s.blocks[0];i&&(this.stack=[{sceneId:r,containerId:i.id,index:0}],this.enter(i.id))}this.settle()}reset(e,t){this.begin(e,t)}start(e,t){this.reset(e,t)}goto(e,t){if(this.closed)return!1;if(this.touch(),e==="END")return this.started=!0,this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=pe(this.host,e);if(r===void 0)return!1;let s;return t!==void 0&&(s=de(this.host,r,t),s===void 0)?!1:this.started?(this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??r,"jump"),this.settle(),!0):(this.begin(r,s),!0)}close(){this.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.clearPending()}clearPending(){this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started:r,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:p}=this,h=this.stack.map(g=>({...g}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:r,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:p,stack:h})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.touch(),this.pendingPrompt){let e=this.pendingPrompt;return this.pendingPrompt=null,this.pendingPromptOwnerId=null,e.shown?this.promptResult(e.beat,e.shown):this.beatResult(e.beat)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit,this.activeSnippet.id);let i=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(i);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}let s=t.index;for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(this.host.tracing&&t.index!==s&&this.emit({type:"select",group:t.containerId,selector:"run",children:r.slice(s,t.index+1).map((i,o)=>({id:i.id,eligible:s+o===t.index})),picked:r[t.index]?.id??null}),t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}listBags(){let e=[{bag:this.local,pathPrefix:`${this.id}/@patter.`}];for(let[t,r]of this.sceneBags)e.push({bag:r,pathPrefix:`${this.id}/@scene:${t}.`});return e}log(){return this.flowLog}clearLog(){this.flowLog.length=0}emit(e){let t=this.currentSceneId??void 0;this.host.emitEngine(this.id,e,t),this.host.logEnabled&&this.flowLog.push({...e,seq:this.flowSeq++,...t?{scene:t}:{}})}getVisitCounts(){return Object.fromEntries(this.visitCounts)}stepFor(e){let t=It(this.host.bundle).get(e);return t?this.beatResult(t):void 0}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let r=t.options.find(o=>o.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);this.touch();let s=t.byId.get(e);this.host.tracing&&this.emit({type:"chose",group:t.groupId,option:e}),this.pendingChoice=null;let i=this.host.replayPromptOnChoose?this.authoredPromptOf(s):void 0;this.pendingPrompt=i&&r.prompt?{beat:i,shown:{...r.prompt}}:null,this.pendingPromptOwnerId=this.pendingPrompt?s.id:null,this.enterChild(s)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:r}=this.splitRef(e);return t==="patter"?this.patterResolver.get(r):t==="scene"?this.sceneResolver.get(r):this.host.registry.get(t,r)}setProperty(e,t){this.writeProperty(e,t,!0)}stageIndex(e,t){return R(this.ladderOf(e),t)}compareStages(e,t,r){return O(this.ladderOf(e),t,r)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:r}=this.splitRef(e);this.context();let s=this.stagesFor(t,r);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}writeProperty(e,t,r){let{scope:s,name:i}=this.splitRef(e),o=this.host.journal;if(s==="patter"){if(o){let a=this.host.patterSharedNames.has(i),c=a?this.host.patterBag.get(i):this.local.get(i),l=this.local;c!==void 0&&o.undo.push(a?()=>this.host.registry.set("patter",i,c):()=>l.set(i,c))}this.patterResolver.set(i,t)}else if(s==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);if(o){let a=this.sceneBagFor(this.currentSceneId,i),c=a?.get(i);a&&c!==void 0&&o.undo.push(()=>a.set(i,c))}this.sceneResolver.set(i,t)}else{let a=o?this.host.registry.get(s,i):void 0;this.host.registry.set(s,i,t,r?{host:!0}:void 0),o&&a!==void 0&&o.undo.push(()=>this.host.registry.set(s,i,a,{host:!0}))}}snapshot(){return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:Rt(this.pendingPrompt.shown)}:{},selectors:ze(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");this.restoreCursor(e),this.releaseBags(!0),this.mountLocal();for(let t of new Set([this.currentSceneId,...this.stack.map(r=>r.sceneId)]))t!==null&&this.host.bundle.scenes[t]&&this.ensureSceneBags(t)}restoreCursor(e){this.rngState=F(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(r=>{let{nextId:s,...i}=r;if(s!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===s)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=Ye(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let r=new Map,s=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);if(!o)continue;r.set(i.id,o);let{prompt:a,...c}=i,l=a?.kind==="line"&&a.speakerLabel===void 0?q(this.host,a.character,a.qualifier):void 0;s.push({...c,...a?{prompt:{...a,padAfter:a.padAfter??this.promptPadOf(o),...l!==void 0?{speakerLabel:l}:{}}}:{}})}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:r})}if(this.pendingPrompt=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId),s=r?this.authoredPromptOf(r):void 0,i=t.pendingPrompt,o=i?.kind==="line"&&i.speakerLabel===void 0?q(this.host,i.character,i.qualifier):void 0;s?this.pendingPrompt={beat:s,...i?{shown:{...i,padAfter:i.padAfter??this.padOf(s.id),...o!==void 0?{speakerLabel:o}:{}}}:{}}:this.pendingPromptOwnerId=null}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry,t.id)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let r=this.selectChild(e);r&&this.enterChild(r)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let r=this.host.nodeIndex.get(e);if(r&&r.type==="group")return r.children}beginSnippet(e){this.runEffects(e.onEnter,e.id),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],r=new Map,s=[];for(let o of e.children){if(o.fallback===!0){s.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),c=o.secretUntilEligible===!0;!a&&c||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData}),r.set(o.id,o))}if(t.some(o=>o.eligible)){this.host.tracing&&this.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.tracing&&this.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.host.tracing&&this.emit({type:"jump",to:e,mode:t}),e==="END"){this.flowEnded=!0,this.stack=[];return}let r,s,i=this.host.bundle.scenes[e];if(i){this.enterSceneSetup(e);let a=i.blocks[0];if(!a){t==="jump"&&(this.stack=[]);return}r=e,s=a.id}else{let a=this.host.blockIndex.get(e);if(!a)throw new Error(`jump target not found: ${e}`);a.sceneId!==this.currentSceneId&&this.enterSceneSetup(a.sceneId),r=a.sceneId,s=e}this.enter(s);let o={sceneId:r,containerId:s,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.map(c=>({id:c.id,eligible:this.eligible(c)})),r=e.children.filter((c,l)=>t[l].eligible),s=e.options?.order??"sequential",i=e.options?.exhaust??"once",o=c=>(this.host.tracing&&this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(r.length===0)return o(null);let a=this.selectorState(e);switch(e.selector){case"branch":return o(r[0]);case"sequence":return o(s==="shuffle"?this.pickShuffle(r,i,a):s==="specificity"?this.pickSpecificity(r,i,a):this.pickSequential(r,i,a));default:return null}}pickSequential(e,t,r){let s=e.length,i=r.seq??0;return r.seq=i+1,t==="repeat"?e[i%s]:i<s?e[i]:t==="stick"?e[s-1]:null}pickShuffle(e,t,r){let s=e.length,i=t==="stick",o=()=>(i?e.slice(0,s-1):e).map(g=>g.id),a=new Set(e.map(g=>g.id));if(r.bag===void 0&&(r.bag=o()),!r.bag.some(g=>a.has(g))){if(t==="once")return null;if(i){let g=e[s-1];return r.last=g.id,g}if(r.bag=o(),r.bag.length===0)return null}let c=r.bag.filter(g=>a.has(g)),l=r.last!==void 0&&c.length>1?c.indexOf(r.last):-1,p=Math.floor(this.rng()*(l>=0?c.length-1:c.length));l>=0&&p>=l&&p++;let h=c[p];return r.bag.splice(r.bag.indexOf(h),1),r.last=h,e.find(g=>g.id===h)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(p=>p.id));let l=new Set(r.bag);if(s=e.filter(p=>l.has(p.id)),s.length===0)return t==="stick"&&r.last!==void 0?e.find(p=>p.id===r.last)??null:null}let i=-1,a=s.map(l=>{let p=this.specScore(l);return p>i&&(i=p),{c:l,s:p}}).filter(l=>l.s===i).map(l=>l.c),c;if(a.length===1)c=a[0];else{let l=r.last!==void 0?a.findIndex(h=>h.id===r.last):-1,p=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&p>=l&&p++,c=a[p]}return t!=="repeat"&&(r.bag=r.bag.filter(l=>l!==c.id)),r.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0,e):0}matchedSpec(e,t,r){return ke(e,i=>{try{return Je(W(i,this.context(),ce))}catch(o){return this.reportError("best-match",r.id,r.condition,o),!1}},{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=this.host.journal,s;if(r&&(s=e.shared?r.selectors:r.flowSelectors.get(this),s||(s=new Set,r.flowSelectors.set(this,s))),r&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;r.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e,t){for(let r of e??[]){let s;try{s=this.evalExpr(r.value);let i=this.host.tracing?this.getProperty(r.target):void 0;this.writeProperty(r.target,s,!1),this.host.tracing&&this.emit({type:"write",target:r.target,value:s,...i!==void 0?{prev:i}:{}})}catch(i){this.reportError("effect",t,r.value,i)}}}eligible(e){if(!e.condition)return!0;try{return Je(this.evalExpr(e.condition))}catch(t){return this.reportError("condition",e.id,e.condition,t),!1}}reportError(e,t,r,s){let i=s instanceof Error?s.message:String(s),o=r?.src;this.host.onError({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i}),this.host.tracing&&this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return W(this.conditionAst(e),this.context(),ce)}conditionAst(e){let t=Ge.get(e);return t||(t=E(e.ast),Ge.set(e,t)),t}enter(e){let t=this.visitCounts,r=t.get(e);t.set(e,(r??0)+1);let s=this.host.sharedVisits,i=s.get(e);s.set(e,(i??0)+1),this.host.journal&&this.host.journal.undo.push(()=>{r===void 0?t.delete(e):t.set(e,r),i===void 0?s.delete(e):s.set(e,i)})}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=se(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=I(this.host,e.id),r=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...r};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),padAfter:this.padOf(e.id),gameData:e.gameData,...r};case"line":{let s=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?s:this.interpolate(s)),c=i&&a.length===0;return{type:"line",id:e.id,text:a,character:c?void 0:e.character,characterName:c?void 0:this.resolveCharacterName(e.character),direction:c?void 0:e.direction,qualifier:c?void 0:e.qualifier,qualifierName:c?void 0:this.resolveQualifierName(e.qualifier),speakerLabel:c?void 0:q(this.host,e.character,e.qualifier),padAfter:this.padOf(e.id),gameData:e.gameData,...r}}}}interpolate(e){return Be(e,t=>this.getProperty(t))}stripCaptions(e){return Oe(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let r=this.interpolate(this.resolveString(t.id)),s=this.padOf(t.id);return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction,qualifier:t.qualifier,qualifierName:this.resolveQualifierName(t.qualifier),speakerLabel:q(this.host,t.character,t.qualifier),padAfter:s}:{kind:"text",text:r,padAfter:s}}authoredPromptOf(e){return e.type==="group"?e.prompt:void 0}promptResult(e,t){let r=I(this.host,e.id),s=r&&r.length?{tags:r}:{};return t.kind==="text"?{type:"text",id:e.id,text:t.text,padAfter:this.padOf(e.id),gameData:e.gameData,...s}:{type:"line",id:e.id,text:t.text,character:t.character,characterName:t.characterName,direction:t.direction,qualifier:t.qualifier,qualifierName:t.qualifierName,speakerLabel:t.speakerLabel,padAfter:this.padOf(e.id),gameData:e.gameData,...s}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(r=>r.kind==="line"||r.kind==="text")}firstTextSnippetIn(e){let t;return D(e,r=>{!t&&r.type==="snippet"&&(r.beats??[]).some(s=>s.kind==="line"||s.kind==="text")&&(t=r)}),t}resolveString(e){if(this.host.emitIds)return e;let t=this.host.strings[e];if(t!==void 0)return t;let r=this.host.defaultStrings[e];return r!==void 0?`<Untranslated: ${e}> ${r}`:e}resolveCharacterName(e){if(e===void 0||this.host.emitIds)return;let t=Y(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}promptPadOf(e){let t=this.promptBeatOf(e);return t?this.padOf(t.id):x}padOf(e){return this.host.padIndex.get(e)?.resolved??x}resolveQualifierName(e){if(e===void 0||this.host.emitIds)return;let t=X(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.qualifierDisplay.get(e)}splitRef(e){return Ze(this.host,e)}newLocal(){return new v(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=y.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:M}),this.registered.add(e)}releaseBags(e){for(let t of this.registered)this.host.registry.remove(t,{keep:e});this.registered.clear(),this.sceneBags.clear()}sceneBagFor(e,t){if(this.host.bundle.scenes[e])return this.ensureSceneBags(e),this.host.sceneSharedNames.get(e)?.has(t.toLowerCase())?this.host.stageBags.get(e):this.sceneBags.get(e)}ensureSceneBags(e){let t=this.host.sceneSharedNames.get(e)??new Set,r=this.host.bundle.scenes[e]?.sceneProps??[],s=this.host.journal;if(!this.sceneBags.has(e)){let i=new v(r.filter(c=>!t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),o=y.flowScene(this.id,e),a=We(this.host.registry,o,i,s!==null);this.registered.add(o),this.sceneBags.set(e,i),s&&s.undo.push(()=>{this.sceneBags.get(e)===i&&(this.host.registry.remove(o,a?{keep:!0}:void 0),this.registered.delete(o),this.sceneBags.delete(e))})}if(!this.host.stageBags.has(e)){let i=new v(r.filter(a=>t.has(a.name.toLowerCase())),{pathPrefix:"@scene."}),o=We(this.host.registry,y.stage(e),i,s!==null);this.host.stageBags.set(e,i),s&&s.undo.push(()=>{this.host.stageBags.get(e)===i&&(this.host.registry.remove(y.stage(e),o?{keep:!0}:void 0),this.host.stageBags.delete(e))})}}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;this.ensureSceneBags(e.id);for(let r of e.sceneProps??[]){if(!r.temporary)continue;let s=r.name.toLowerCase(),i=t.has(s)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);if(!i)continue;let o=this.host.journal?i.get(s):void 0;o!==void 0&&this.host.journal.undo.push(()=>i.set(s,o)),i.set(s,N(r))}}};function He(n,e){D(n,t=>{if(t.type==="group"){t.prompt?.kind==="line"&&t.prompt.character&&e.add(t.prompt.character);return}for(let r of t.beats??[])r.kind==="line"&&r.character&&e.add(r.character)})}function q(n,e,t,r=!1){if(!e||n.emitIds)return;let s=c=>(r?void 0:n.strings[c])??n.defaultStrings[c],i=s(Y(e))??n.castDisplay.get(e)??e;if(t===void 0)return i;let o=n.speakerLabels.get(`${e}\\0${t}`);if(o!==void 0)return s(Ae(e,t))??o;let a=s(X(t))??n.qualifierDisplay.get(t)??t;return Te(s(Fe)??n.speakerPattern,i,a)}function Ue(n){return n&&Object.keys(n).length?{gameData:n}:{}}function Rt(n){let e={kind:n.kind,text:n.text};return n.character!==void 0&&(e.character=n.character),n.characterName!==void 0&&(e.characterName=n.characterName),n.direction!==void 0&&(e.direction=n.direction),n.qualifier!==void 0&&(e.qualifier=n.qualifier),n.qualifierName!==void 0&&(e.qualifierName=n.qualifierName),n.speakerLabel!==void 0&&(e.speakerLabel=n.speakerLabel),typeof n.padAfter=="number"&&(e.padAfter=n.padAfter),e}function ze(n){let e={};for(let[t,r]of n){let s={};r.seq!==void 0&&(s.seq=r.seq),r.bag&&(s.bag=[...r.bag]),r.last!==void 0&&(s.last=r.last),e[t]=s}return e}function Ye(n){let e=new Map;for(let[t,r]of Object.entries(n??{})){let s={};r.seq!==void 0&&(s.seq=r.seq),r.bag&&(s.bag=[...r.bag]),r.last!==void 0&&(s.last=r.last),e.set(t,s)}return e}function We(n,e,t,r){let s=r?JSON.stringify(t.save()):"";return n.mountOwned(e,t,{owner:M}),r&&JSON.stringify(t.save())!==s}function Ke(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Qe(n){return{...n.world?{world:n.world}:{},...n.hostScopes}}function $(n,e,t){let r=n.qualityLadders.get(e);if(!r){r=new Map;for(let s of t()??[]){let i=s.name.toLowerCase();s.type==="quality"&&s.stages&&!r.has(i)&&r.set(i,s.stages)}n.qualityLadders.set(e,r)}return r}function Ot(n,e,t){let r=t.toLowerCase();return e==="patter"?$(n,"patter",()=>[...n.patterSharedDecls,...n.patterLocalDecls]).get(r):n.registry.toEvalContext().qualities?.(e,t)??$(n,`host/${e}`,()=>n.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(r)}function Xe(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function Bt(n,e){let t=Xe(n),r=n.writable??e;return r===void 0?t:{...t,writable:r}}function pe(n,e){return n.sceneGameIdToId.get(e)??(n.bundle.scenes[e]?e:void 0)}function de(n,e,t){return n.blockGameIdToId.get(e)?.get(t)??(n.blockIndex.get(t)?.sceneId===e?t:void 0)}function Ze(n,e){n.refSplitRevision!==n.registry.revision&&(n.refSplitCache.clear(),n.refSplitRevision=n.registry.revision);let t=n.refSplitCache.get(e);return t||(t=Re(e,r=>r==="scene"||n.registry.has(r)||(n.bundle.externalScopes?.includes(r)??!1)),n.refSplitCache.set(e,t)),t}function At(n){let e={};n.shared?.patter&&(e.patter=n.shared.patter);for(let[t,r]of Object.entries(n.stageBags??{}))e[y.stage(t)]=r;for(let[t,r]of Object.entries(n.flows??{}))Object.assign(e,et(t,r));return e}function et(n,e){let t={};e.scopes?.patter&&(t[y.flowGlobals(n)]=e.scopes.patter);for(let[r,s]of Object.entries(e.sceneBags??{}))t[y.flowScene(n,r)]=s;return t}function Ft(n,e){let t={scopes:{},sceneBags:{}},r=y.flow(n);for(let[s,i]of Object.entries(e)){if(!s.startsWith(r))continue;let o=s.slice(r.length);o==="patter"?t.scopes.patter=i:o.startsWith("scene/")&&(t.sceneBags[Pt(o.slice(6))]=i)}return t}function Je(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Nt=(n,e)=>n.shared??e;function ue(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:Nt(n,e)}}function Tt(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function Lt(n,e){e.blocks++;let t=[...n.children];for(;t.length;){let r=t.pop();if(r.type==="group"){e.groups++,r.prompt&&e.prompts++,t.push(...r.children);continue}e.snippets++;for(let s of r.beats??[])e.beats++,s.kind==="gameEvent"&&e.gameEvents++}}function tt(n){let e={scenes:0,blocks:0,groups:0,snippets:0,beats:0,prompts:0,gameEvents:0,cast:n.cast?.length??0},t=[],r=[];for(let o of Object.values(n.scenes)){e.scenes++;let a=w(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:w(c),name:c.name}))});for(let c of o.blocks)Lt(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>ue(c,!1))})}let s=(n.scopeRegistry?.scopes??[]).map(o=>({token:o.token,writable:o.writable??!0,opaque:o.declarations===void 0,properties:(o.declarations??[]).map(a=>ue(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Tt)}));return{identity:{schema:n.schema,project:n.content.project,...n.content.version!==void 0?{version:n.content.version}:{},...n.content.hash!==void 0?{hash:n.content.hash}:{},...n.content.structureHash!==void 0?{structureHash:n.content.structureHash}:{},voiced:n.voiced,defaultLocale:n.locales.default,locales:[...n.locales.included],localisation:n.localisation?.mode??"embedded",sourceDebug:n.localisation?.sourceDebug??!1},addresses:t,hostScopes:s,properties:{patter:(n.properties??[]).map(o=>ue(o,!0)),scene:r},gameData:i,counts:e}}function rt(n,e){return n.gameDataFields?.[e]??[]}function ge(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function nt(n,e){let t={};for(let r of n){let s=ge(n,e,r.name);s!==void 0&&(t[r.name]=s)}for(let[r,s]of Object.entries(e??{}))r in t||(t[r]=s);return t}return ut(jt);})();\n';
|
|
1773
|
+
var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var ie=Object.defineProperty;var ut=Object.getOwnPropertyDescriptor;var gt=Object.getOwnPropertyNames;var ft=Object.prototype.hasOwnProperty;var ht=(r,e)=>{for(var t in e)ie(r,t,{get:e[t],enumerable:!0})},mt=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of gt(e))!ft.call(r,s)&&s!==t&&ie(r,s,{get:()=>e[s],enumerable:!(n=ut(e,s))||n.enumerable});return r};var yt=r=>mt(ie({},"__esModule",{value:!0}),r);var $t={};ht($t,{Checkpoint:()=>H,DEFAULT_PAD_AFTER:()=>E,Engine:()=>ne,Flow:()=>O,buildTagIndex:()=>te,compareStages:()=>N,describeBundle:()=>st,effectiveGameData:()=>ot,extensionDecls:()=>at,extensionsOf:()=>ct,gameDataFields:()=>it,gameDataValue:()=>ye,nextStage:()=>B,stageIndex:()=>P});function F(r,e){return r!==void 0&&Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0}function C(r){switch(r[0]){case"b":return{kind:"bool",value:r[1]};case"n":return{kind:"number",value:r[1]};case"s":return{kind:"string",value:r[1]};case"sv":return{kind:"scopedvar",scope:r[1],name:r[2]};case"u":return{kind:"unary",op:r[1],operand:C(r[2])};case"bin":return{kind:"binary",op:r[1],left:C(r[2]),right:C(r[3])};case"call":{let e=r.slice(2).map(C);return{kind:"call",name:r[1],args:e}}case"fd":return{kind:"flagdelta",sign:r[1],name:r[2]}}}var u=class extends Error{constructor(e){super(e),this.name="EvalError"}};function J(r,e,t){let n=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),s=i=>{switch(i.kind){case"bool":return i.value;case"number":return i.value;case"string":return i.value;case"scopedvar":{let o=F(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):F(o,i.name);if(a===void 0){if(n.get(i.scope)==="throw")throw new u(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!F(t.functions,i.name)){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new u(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=oe(a,e);if(c===void 0)throw new u("advance() needs a quality reference (@scope.name of a quality property)");let l=v(s(a),c,"advance");return c[Math.min(l+1,c.length-1)]}let o=F(t.functions,i.name);if(!o)throw new u(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new u("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=s(i.operand);if(typeof a!="boolean")throw new u(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new u(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'and\' requires boolean operands, left is ${typeof h}`);if(!h)return!1;let f=s(i.right);if(typeof f!="boolean")throw new u(`\'and\' requires boolean operands, right is ${typeof f}`);return f}if(i.op==="or"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'or\' requires boolean operands, left is ${typeof h}`);if(h)return!0;let f=s(i.right);if(typeof f!="boolean")throw new u(`\'or\' requires boolean operands, right is ${typeof f}`);return f}let o=s(i.left),a=s(i.right),c=oe(i.left,e),l=oe(i.right,e),d=c??l;if(d!==void 0){if(c&&l&&!St(c,l)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new u(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return v(o,d,">")>v(a,d,">");case">=":return v(o,d,">=")>=v(a,d,">=");case"<":return v(o,d,"<")<v(a,d,"<");case"<=":return v(o,d,"<=")<=v(a,d,"<=");case"+":case"-":case"*":case"/":throw new u(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return Ce(o,a);case"!=":return!Ce(o,a);case">":return R(o,a,">"),o>a;case">=":return R(o,a,">="),o>=a;case"<":return R(o,a,"<"),o<a;case"<=":return R(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new u(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return R(o,a,"-"),o-a;case"*":return R(o,a,"*"),o*a;case"/":if(R(o,a,"/"),a===0)throw new u("division by zero");return o/a}}}};return s(r)}function Ce(r,e){if(Array.isArray(r)||Array.isArray(e)){if(!Array.isArray(r)||!Array.isArray(e)||r.length!==e.length)return!1;let t=[...r].sort(),n=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==n[s])return!1;return!0}return r===e}function R(r,e,t){if(typeof r!="number"||typeof e!="number")throw new u(`\'${t}\' requires numeric operands, got ${typeof r} and ${typeof e}`)}function oe(r,e){if(!(r.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(r.scope,r.name)}function v(r,e,t){if(typeof r!="string")throw new u(`\'${t}\' on a quality compares stages, got ${typeof r}`);let n=e.indexOf(r);if(n<0)throw new u(`"${r}" is not a stage of this quality (stages: ${e.join(", ")})`);return n}var St=(r,e)=>r.length===e.length&&r.every((t,n)=>t===e[n]);function T(r){if(Number.isNaN(r)||!Number.isFinite(r))return 0;let e=Math.trunc(r)%4294967296;return e<0?e+4294967296:e}function ae(r){let e=T(r);return{next(){e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296},state(){return e}}}var le=r=>Array.isArray(r)?r:r.stages??[],wt=r=>Array.isArray(r)?"flags":r==null?"nothing":typeof r;function P(r,e){return typeof e=="string"?le(r).indexOf(e):-1}function ce(r,e){if(typeof e!="string")throw new u(`a stage is a name, got ${wt(e)}`);let t=r.indexOf(e);if(t<0)throw new u(`"${e}" is not a stage of this quality (stages: ${r.join(", ")})`);return t}function N(r,e,t){let n=le(r),s=ce(n,e)-ce(n,t);return s<0?-1:s>0?1:0}function B(r,e){let t=le(r);return t[Math.min(ce(t,e)+1,t.length-1)]}function de(r,e,t){switch(r.type){case"boolean":return typeof e=="boolean";case"number":return typeof e=="number";case"string":case"text":case"multiline":return typeof e=="string";case"enum":return typeof e!="string"?!1:e===""&&t?.noneChosen?!0:!Array.isArray(r.values)||r.values.includes(e);case"flags":return Array.isArray(e)&&e.every(n=>typeof n=="string"&&(!Array.isArray(r.values)||r.values.includes(n)));case"quality":return typeof e=="string"&&(!Array.isArray(r.stages)||r.stages.includes(e));default:return!0}}var vt={name:"check_flags",count:r=>Math.max(1,r.args.length-1)},bt=[vt];function Re(r,e,t){let n=t?.countingCalls??bt;return Q(r,t?.want??!0,e,n)}function Q(r,e,t,n){if(r.kind==="binary"&&(r.op==="and"||r.op==="or")){let s=Q(r.left,e,t,n),i=Q(r.right,e,t,n);return r.op==="and"===e?s>0&&i>0?s+i:0:Math.max(s,i)}if(r.kind==="unary"&&r.op==="not")return Q(r.operand,!e,t,n);if(r.kind==="call"){let s=n.find(i=>i.name===r.name);if(s){let i=s.count(r),o=t(r);return e?o?i:0:o?0:1}}return t(r)===e?1:0}var k=class r{values=Object.create(null);decls=new Map;subscribers=new Set;auditors=new Set;norm;pathPrefix;constructor(e=[],t){this.norm=t?.normalise??(n=>n.toLowerCase()),this.pathPrefix=t?.pathPrefix??"",this.seed(e)}seed(e){for(let t of e){let n=this.norm(t.name);this.decls.set(n,t),this.values[n]=structuredClone(t.default??A(t))}}get(e){return this.values[this.norm(e)]}normalise(e){return this.norm(e)}writable(e){return this.decls.get(this.norm(e))?.writable!==!1}set(e,t,n){let s=this.norm(e);if(!n?.host&&!this.writable(s))throw new Error(`\'${e}\' is read-only`);let i={name:s,prev:this.values[s],next:t,silent:n?.silent??!1,reason:n?.reason};this.values[s]=t;for(let o of[...this.auditors])o(i);if(!i.silent)for(let o of[...this.subscribers])o(i);return i}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>Ie(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new r([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e,t){let n=new Map;for(let[i,o]of Object.entries(e)){let a=this.norm(i);this.values[a]=structuredClone(o),n.set(a,o)}let s=[];if(!t?.refit)return s;for(let[i,o]of this.decls){if(!n.has(i))continue;let a=n.get(i);de(o,a)||(this.values[i]=structuredClone(A(o)),s.push({name:i,value:a}))}return s}};function Ie(r,e,t,n,s=""){let i=n??r.name.toLowerCase();return{name:i,path:s+i,type:r.type,value:e,default:r.default??A(r),...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:t??r.writable??!0}}var xt=r=>r.toLowerCase(),pe=1,z=class{scopes=new Map;parked=new Map;parkedRefit=new Set;misfits=[];rev=0;get revision(){return this.rev}defineOwned(e,t,n){let s=typeof n=="string"?{pathPrefix:n}:n??{},i=new k(t,{pathPrefix:s.pathPrefix??`${e}.`,...s.normalise?{normalise:s.normalise}:{}});return this.mountOwned(e,i,s.owner!==void 0?{owner:s.owner}:void 0)}mountOwned(e,t,n){this.assertFree(e,n?.owner),this.scopes.set(e,{kind:"owned",bag:t,...n?.owner!==void 0?{owner:n.owner}:{}}),this.rev++;let s=this.parked.get(e);return s&&(this.note(e,t.load(s,{refit:this.parkedRefit.has(e)})),this.parked.delete(e),this.parkedRefit.delete(e)),this}remove(e,t){let n=this.scopes.get(e);if(!n)throw new Error(`unknown scope \'@${e}\'`);return t?.keep&&n.kind==="owned"&&(this.parked.set(e,n.bag.save()),this.parkedRefit.delete(e)),this.scopes.delete(e),this.rev++,this}discardParked(e){if(e===void 0)this.parked.clear(),this.parkedRefit.clear();else for(let t of[...this.parked.keys()])t.startsWith(e)&&(this.parked.delete(t),this.parkedRefit.delete(t));return this}takeMisfits(e){if(!e){let n=this.misfits;return this.misfits=[],n}let t=this.misfits.filter(n=>e(n.scope));return this.misfits=this.misfits.filter(n=>!e(n.scope)),t}note(e,t){for(let n of t)this.misfits.push({scope:e,name:n.name,value:n.value})}ownedBag(e){let t=this.scopes.get(e);if(!t||t.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);return t.bag}reseedOwned(e,t){return this.ownedBag(e).reseed(t),this}defineForeign(e,t,n=[],s=!0){let i=typeof s=="boolean"?{writable:s}:s;this.assertFree(e,i.owner);let o=i.normalise??xt,a=new Map;for(let c of n)a.set(o(c.name),c);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:a,scopeWritable:i.writable??!0,norm:o,...i.owner!==void 0?{owner:i.owner}:{}}),this.rev++,this}has(e){return this.scopes.has(e)}get(e,t){let n=this.scopes.get(e);if(n)return n.kind==="owned"?n.bag.get(t):n.resolver.get(n.norm(t))}set(e,t,n,s){let i=this.scopes.get(e);if(!i)throw new Error(`unknown scope \'@${e}\'`);if(i.kind==="owned"){if(!s?.host&&!i.bag.writable(t))throw new Error(`\'@${e}.${t}\' is read-only`);i.bag.set(t,n,s?.host?{host:!0}:void 0);return}let o=i.norm(t);if(!i.resolver.set)throw new Error(`\'@${e}.${t}\' is read-only`);if(!s?.host&&!this.foreignWritable(i,o))throw new Error(`\'@${e}.${t}\' is read-only`);i.resolver.set(o,n)}foreignWritable(e,t){return e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}listProperties(){let e=[];for(let[t,n]of this.scopes){let s=n.owner!==void 0?{owner:n.owner}:{};if(n.kind==="owned")for(let i of n.bag.rows())e.push({scope:t,...s,...i});else for(let[i,o]of n.decls)e.push({scope:t,...s,...Ie(o,n.resolver.get(i),this.foreignWritable(n,i),i,`${t}.`)})}return e}toEvalContext(e,t){let n=this.view(t?.aliases),s={};for(let[o,a]of n)s[o]=a.kind==="owned"?a.bag.values:a.resolver;let i=this.qualityLadders(n);return i.size===0?{scopes:s,host:e}:{scopes:s,host:e,qualities:(o,a)=>{let c=n.get(o);return c?i.get(o)?.get(kt(c)(a)):void 0}}}view(e){let t=new Map(this.scopes);for(let[n,s]of Object.entries(e??{})){let i=this.scopes.get(s);if(!i)throw new Error(`alias \'@${n}\' names \'${s}\', which is not registered`);t.set(n,i)}return t}qualityLadders(e){let t=new Map;for(let[n,s]of e)for(let[i,o]of De(s)){if(o.type!=="quality"||o.stages===void 0)continue;let a=t.get(n);a||(a=new Map,t.set(n,a)),a.set(i,o.stages)}return t}toSchema(e){let t=new Map;for(let[n,s]of this.view(e?.aliases)){let i=De(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(n,o)}return{properties:t}}save(){let e={};for(let[t,n]of this.scopes)n.kind==="owned"&&(e[t]=n.bag.save());for(let[t,n]of this.parked)e[t]=structuredClone(n);return e}load(e,t){t?.keepParked||(this.parked.clear(),this.parkedRefit.clear());let n=t?.refit===!0;for(let[s,i]of Object.entries(e)){let o=this.scopes.get(s);o?.kind==="owned"?this.note(s,o.bag.load(i,{refit:n})):o||(this.parked.set(s,structuredClone(i)),n?this.parkedRefit.add(s):this.parkedRefit.delete(s))}}saveFragment(){return{version:pe,scopes:this.save()}}loadFragment(e){if(e.version!==pe)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${pe})`);this.load(e.scopes)}assertFree(e,t){let n=this.scopes.get(e);if(!n)return;let s=n.owner!==void 0?` by ${n.owner}`:"",i=t!==void 0?` (wanted by ${t})`:"";throw new Error(`scope \'@${e}\' is already registered${s}${i}`)}};function De(r){return r.kind==="foreign"?[...r.decls.entries()]:r.bag.declarations().map(e=>[r.bag.normalise(e.name),e])}function kt(r){return r.kind==="foreign"?r.norm:e=>r.bag.normalise(e)}function A(r){if(r.default!==void 0)return r.default;switch(r.type){case"boolean":return!1;case"number":return 0;case"string":case"text":case"multiline":return"";case"enum":return r.values?.[0]??"";case"flags":return[];case"quality":return r.stages?.[0]??"";default:return!1}}var Oe=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function L(r){return r.ctx.host??{}}var ue={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(r,e){if(r.length!==2)throw new u("random(a, b) requires exactly 2 arguments");let t=L(e).nextRandom;if(!t)throw new u("random() called without a PRNG in context");let n=e.evaluate(r[0]),s=e.evaluate(r[1]);if(typeof n!="number"||typeof s!="number")throw new u("random(a, b) arguments must be numbers");if(!Number.isInteger(n)||!Number.isInteger(s))throw new u("random(a, b) arguments must be integers");let i=Math.min(n,s),o=Math.max(n,s);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:Ne("check_flags"),eval(r,e){let t=Pe(r[0],e,"check_flags");for(let n=1;n<r.length;n++){let s=r[n];if(s.kind!=="flagdelta")throw new u("check_flags() flag args must be +flagName or -flagName");if(s.sign==="+"?!t.includes(s.name):t.includes(s.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:Ne("set_flags"),eval(r,e){let t=[...Pe(r[0],e,"set_flags")];for(let n=1;n<r.length;n++){let s=r[n];if(s.kind!=="flagdelta")throw new u("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let i=t.indexOf(s.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:X("visits"),eval:(r,e)=>L(e).visits?.(Y(r,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:X("seen"),eval:(r,e)=>(L(e).visits?.(Y(r,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:X("patter_visits"),eval:(r,e)=>L(e).patterVisits?.(Y(r,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:X("patter_seen"),eval:(r,e)=>(L(e).patterVisits?.(Y(r,e,"patter_seen"))??0)>0}}},yn=Oe.map(r=>r.token).filter(r=>r!=="patter"&&r!=="scene");function Be(r,e){let t=r.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function Y(r,e,t){let n=e.evaluate(r[0]);if(typeof n!="string")throw new u(`${t}(id) requires a string node id`);return n}function X(r){return(e,t)=>{let n=e[0];n&&n.kind!=="string"&&t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(id): the argument must be a string id literal (a scene / block / node id)`})}}function Pe(r,e,t){if(!r)throw new u(`${t}() requires at least one argument (the flags variable)`);let n=e.evaluate(r);if(Array.isArray(n))return n;if(n===!1||n===null||n===void 0)return[];throw new u(`${t}() first argument must be a flags property`)}function Ne(r){return(e,t)=>{if(e.length===0)return;let n=e[0];if(n.kind!=="scopedvar"){t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(): first argument must be a flags property reference (@name or @scope.name)`});return}let s=t.schema.properties.get(n.scope)?.get(n.name);if(s&&s.type!=="flags"){let i=n.scope===t.defaultScope?n.name:`${n.scope}.${n.name}`;t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(): \'@${i}\' is not a flags property (got ${s.type})`});return}for(let i=1;i<e.length;i++){let o=e[i];o.kind!=="flagdelta"?t.report({path:[...t.path,"args",i],kind:"wrong-arg-type",severity:"error",message:`${r}(): argument ${i+1} must be +flagName or -flagName`}):s?.type==="flags"&&s.enumValues&&!s.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${r}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var Et=/^@[A-Za-z0-9_.]+$/;function*Ct(r){let e="",t=0;for(;t<r.length;){let n=r[t];if(n==="{"&&r[t+1]==="{"){e+="{",t+=2;continue}if(n==="}"&&r[t+1]==="}"){e+="}",t+=2;continue}if(n==="{"){let s=r.indexOf("}",t+1);if(s!==-1){let i=r.slice(t,s+1),o=r.slice(t+1,s).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:Et.test(o)?o:void 0},t=s+1;continue}e+=i,t=s+1;continue}}e+=n,t+=1}e&&(yield{kind:"text",value:e})}function Rt(r){return Array.isArray(r)?r.join(", "):typeof r=="boolean"?r?"true":"false":String(r)}function Dt(r){return r===" "||r===" "||r===`\n`||r==="\\r"||r==="\\f"||r==="\\v"}function It(r){let e="",t=!1;for(let n of r){if(Dt(n)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=n}return e}function Ae(r,e,t){if(e.length===0||r.indexOf(e)<0)return r;let n="",s=0,i=!1;for(;s<r.length;){if(r.startsWith(e,s)){let o=r.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}n+=r.slice(s);break}n+=r[s],s+=1}return i?It(n):r}function Fe(r,e){if(r.indexOf("{")<0)return r;let t="";for(let n of Ct(r)){if(n.kind==="text"){t+=n.value;continue}if(!n.ref){t+=n.raw;continue}let s=e(n.ref);t+=s===void 0?"":Rt(s)}return t}function D(r,e){for(let t of r){e(t);let n=t.children;n&&D(n,e)}}function Ot(r){return r.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function y(r){let e=r.gameId?.trim();return e||Ot(r.name)}var E=.6;function Z(r){return`cast:${r}`}function ee(r){return`qualifier:${r}`}function Te(r,e){return`speaker:${r}:${e}`}var Le="speaker:pattern",je="{name} ({qualifier})";function Me(r,e,t){return r.split("{name}").map(n=>n.split("{qualifier}").join(t)).join(e)}var ge={open:"[",close:"]"},Ve="SFX";var j=3;function M(r){let e=new Set,t=[];for(let n of r)e.has(n)||(e.add(n),t.push(n));return t}function te(r){let e=new Map,t=(n,s)=>{let i=M([...s,...n.tags??[]]);if(e.set(n.id,i),n.type==="group"&&n.prompt&&e.set(n.prompt.id,M([...i,...n.prompt.tags??[]])),n.type==="group")for(let o of n.children)t(o,i);else for(let o of n.beats??[])e.set(o.id,M([...i,...o.tags??[]]))};for(let n of Object.values(r.scenes)){let s=M(n.tags??[]);e.set(n.id,s);for(let i of n.blocks){let o=M([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var Ge=r=>r.kind==="line"||r.kind==="text",V=r=>typeof r=="number"&&Number.isFinite(r)?r:void 0;function qe(r){let e=new Map,t=(i,o,a)=>{let c=i.kind==="gameEvent"?void 0:V(i.padAfter),l=c??o;a&&l<0&&(l=0),e.set(i.id,c!==void 0?{own:c,resolved:l}:{resolved:l})},n=(i,o)=>{let a=V(i.padAfterDefault)??o;if(i.type==="group"){i.prompt&&t(i.prompt,a,!1);for(let d of i.children)n(d,a);return}let c=i.beats??[],l=-1;c.forEach((d,h)=>{Ge(d)&&(l=h)}),c.forEach((d,h)=>{Ge(d)&&t(d,a,h===l)})},s=V(r.padAfterDefault)??E;for(let i of Object.values(r.scenes)){let o=V(i.padAfterDefault)??s;for(let a of i.blocks){let c=V(a.padAfterDefault)??o;for(let l of a.children)n(l,c)}}return e}var $e=new WeakMap,_e=new WeakMap;function Pt(r){let e=_e.get(r);return e||(e=te(r),_e.set(r,e)),e}var He=new WeakMap;function Nt(r){let e=He.get(r);return e||(e=qe(r),He.set(r,e)),e}var Ue=new WeakMap;function Bt(r){let e=Ue.get(r);if(e)return e;e=new Map;for(let t of Object.values(r.scenes))for(let n of t.blocks)D(n.children,s=>{if(s.type==="group"){s.prompt&&e.set(s.prompt.id,s.prompt);return}for(let i of s.beats??[])e.set(i.id,i)});return Ue.set(r,e),e}function I(r,e){return r.tagIndex.get(e)?.slice()}var $="Patter",G=r=>r.replace(/%/g,"%25").replace(/\\//g,"%2F"),At=r=>r.replace(/%2F/g,"/").replace(/%25/g,"%"),We=Symbol("ownsRegistry"),w={stage:r=>`patter/scene/${G(r)}`,flow:r=>`patter/flow/${G(r)}/`,flowGlobals:r=>`patter/flow/${G(r)}/patter`,flowScene:(r,e)=>`patter/flow/${G(r)}/scene/${G(e)}`},H=class{constructor(e){this.journal=e}journal},ne=class r{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let n=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=n;let i=s[n]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let l=new Map;for(let p of e.cast??[])p.displayName&&l.set(p.name,p.displayName);let d=new Map;for(let p of e.qualifiers??[])p?.gameId&&d.set(p.gameId,p.name);let h=new Map;for(let p of e.cast??[])for(let[g,S]of Object.entries(p.qualifierLabels??{}))typeof S=="string"&&h.set(`${p.name}\\0${g}`,S);let f=e.speakerPattern??je;this.defaultSeed=(t.seed??2654435769)>>>0;let Se=new Map,we=new Map,ve=new Map;for(let[p,g]of Object.entries(e.scenes)){this.sceneGameIdToId.set(y(g),p);let S=new Map;for(let m of g.blocks)we.set(m.id,{sceneId:p}),ve.set(m.id,m),S.set(y(m),m.id),D(m.children,K=>Se.set(K.id,K));this.blockGameIdToId.set(p,S)}let be=e.properties??[],re=be.filter(p=>p.shared??!0).map(ze),lt=be.filter(p=>!(p.shared??!0)).map(ze),dt=new Set(re.map(p=>p.name.toLowerCase())),x=t.registry??new z,xe=!t.registry||t[We]===!0,ke=new k(re,{pathPrefix:"@patter."}),se=[],W=[];try{x.mountOwned("patter",ke,{owner:$}),W.push("patter");let p=new Set;for(let[g,S]of Object.entries(Ye(t))){let m=e.scopeRegistry?.scopes.find(pt=>pt.token===g),K=(m?.declarations??[]).map(tt);x.defineForeign(g,S,K,{writable:m?.writable??!0,owner:$}),W.push(g),p.add(g)}for(let g of xe?e.scopeRegistry?.scopes??[]:[]){if(p.has(g.token)||x.has(g.token))continue;let S=(g.declarations??[]).map(m=>Lt(m,g.writable));x.defineOwned(g.token,S,{owner:$}),W.push(g.token),se.push(g.token)}}catch(p){for(let g of W)x.remove(g,{keep:!0});throw p}let Ee=new Map;for(let[p,g]of Object.entries(e.scenes)){let S=new Set((g.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));Ee.set(p,S)}this.host={logEnabled:t.log??!1,tracing:t.log??!1,emitEngine:(p,g,S)=>this.emitEngine(p,g,S),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,qualifierDisplay:d,speakerLabels:h,speakerPattern:f,nodeIndex:Se,blockIndex:we,blockById:ve,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:Pt(e),padIndex:Nt(e),registry:x,ownsRegistry:xe,patterBag:ke,hostScopes:se,patterSharedDecls:re,patterLocalDecls:lt,patterSharedNames:dt,sceneSharedNames:Ee,sharedVisits:new Map,qualityLadders:new Map,sharedSelectors:new Map,stageBags:new Map,journal:null,customRng:t.rng,onDryChoice:t.onDryChoice,onError:t.onError??(p=>console.warn(`Patterplay: ${p.kind} on ${p.node} in flow \'${p.flow}\' failed, played through: ${p.message}`)),reportRefits:()=>{let p=x.takeMisfits(g=>g==="patter"||g.startsWith("patter/")||se.includes(g));if(p.length!==0)if(t.onRefit)t.onRefit(p);else for(let g of p)console.warn(`Patterplay: the save\'s ${g.scope}.${g.name} was ${JSON.stringify(g.value)}, which no longer fits its declaration; it is back at its default`)},replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??ge).open,captionClose:(e.closedCaptions??ge).close,captionCharacter:e.closedCaptions?.character||Ve,refSplitCache:new Map,refSplitRevision:x.revision},this.host.reportRefits()}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),n=c=>(c.setLocale(this.currentLocale),c.setClosedCaptions(this.host.captionsOn),c),s=()=>new r(e,{...this.creationOptions,registry:this.host.registry,[We]:this.host.ownsRegistry}),i=new Set(["patter",...this.host.hostScopes]),o=this.host.ownsRegistry?void 0:Object.fromEntries(Object.entries(this.host.registry.save()).filter(([c])=>i.has(c)||c.startsWith("patter/")));this.release(!0),o&&this.host.registry.load(o,{keepParked:!0,refit:!0});let a=s();try{return a.loadGame(t),n(a)}catch{a.release(!0);let c=this.host.ownsRegistry?t.registry:o;c&&this.host.registry.load(c,{keepParked:!0,refit:!0});let l=s();for(let[d,h]of Object.entries(t.flows)){let f=h.cursor.currentSceneId;try{l.openFlow(d,f!==null?{scene:f}:{})}catch{}}return n(l)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){this.assertExternalScopes();let{sceneId:n,blockId:s}=this.resolveOpenAddress(t.scene,t.block),i=this.flowsById.get(e),o=this.host.journal;if(o&&i&&!i.isClosed)throw new Error(`openFlow would replace the open flow \'${e}\', which can\'t be undone while a checkpoint is open`);i?.close();let a=new O(e,this.host,t.seed??this.defaultSeed);return o&&(o.flows.add(a),o.opened.add(a),o.undo.push(()=>{a.close(),i?this.flowsById.set(e,i):this.flowsById.delete(e)})),this.flowsById.set(e,a),a.begin(n,s),a}runFlow(e,t,n){let s=this.flowsById.get(e);if(!s)return this.openFlow(e,{scene:t,block:n}).advanceToStop().played;if(!s.goto(t,n))throw new Error(`runFlow: address not found: ${t}${n===void 0?"":` / ${n}`}`);return s.advanceToStop().played}assertExternalScopes(){for(let e of this.host.bundle.externalScopes??[])if(!this.host.registry.has(e))throw new Error(`this content names @${e}, which no engine on this registry registered: give every engine the game\'s one registry`)}resolveOpenAddress(e,t){if(e!==void 0){let n=fe(this.host,e);if(n===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:n};let s=he(this.host,n,t);if(s===void 0)throw new Error(`unknown block: ${t}`);return{sceneId:n,blockId:s}}if(t!==void 0){if(!this.host.blockIndex.has(t))throw new Error(`unknown block: ${t}`);return{blockId:t}}if(Object.keys(this.host.bundle.scenes).length===0)throw new Error("no scenes in bundle");return{}}resolveSceneRef(e){if(e!=null)return fe(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:he(this.host,e,t)}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?y(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?y(t):void 0}tagsForBeat(e){return I(this.host,e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?I(this.host,t):void 0)??[]}tagsForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t);return(s!=null?I(this.host,s):void 0)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.gameData}}gameDataForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(n!=null?this.host.blockById.get(n):void 0)?.gameData}}extensionsForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.extensions}}extensionsForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(n!=null?this.host.blockById.get(n):void 0)?.extensions}}getCast(){let e=[];for(let t of this.host.bundle.cast??[])t?.name&&e.push(t.name);return e}castForScene(e){let t=this.resolveSceneRef(e),n=t!=null?this.host.bundle.scenes[t]:void 0;if(!n)return[];let s=new Set;for(let i of n.blocks)Ke(i.children,s);return[...s]}castForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t),i=s!=null?this.host.blockById.get(s):void 0;if(!i)return[];let o=new Set;return Ke(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...y(e)?{gameId:y(e)}:{},name:e.name,...Je(e.gameData),...b(e.extensions),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...y(t)?{gameId:y(t)}:{},name:t.name,...Je(t.gameData),...b(t.extensions),...this.tagsField(t.id),children:t.children.map(n=>this.outlineNode(n))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let n of t.blocks)D(n.children,s=>{if(s.type==="snippet")for(let i of s.beats??[])e.push({sceneId:t.id,blockId:n.id,snippetId:s.id,beat:this.beatInfo(i)})});return e}outlineNode(e){return e.type==="group"?{type:"group",id:e.id,...this.tagsField(e.id),...e.selector?{selector:e.selector}:{},...e.prompt?{prompt:this.beatInfo(e.prompt)}:{},children:e.children.map(t=>this.outlineNode(t))}:{type:"snippet",id:e.id,...this.tagsField(e.id),beats:(e.beats??[]).map(t=>this.beatInfo(t)),...e.jump?{jumpTo:e.jump.to,...e.jump.mode?{jumpMode:e.jump.mode}:{}}:{}}}beatInfo(e){let t=I(this.host,e.id),n={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){n.character=e.character;let i=this.host.defaultStrings[Z(e.character)]??this.host.castDisplay.get(e.character);i!==void 0&&(n.characterName=i)}if(e.direction!==void 0&&(n.direction=e.direction),e.qualifier!==void 0){n.qualifier=e.qualifier;let i=this.host.defaultStrings[ee(e.qualifier)]??this.host.qualifierDisplay.get(e.qualifier);i!==void 0&&(n.qualifierName=i)}let s=q(this.host,e.character,e.qualifier,!0);s!==void 0&&(n.speakerLabel=s)}if(e.kind==="line"||e.kind==="text"){let s=this.host.padIndex.get(e.id);n.padAfter=s?.resolved??E,s?.own!==void 0&&(n.ownPadAfter=s.own);let i=this.host.defaultStrings[e.id];i!==void 0&&(n.text=i)}return e.gameData&&Object.keys(e.gameData).length&&(n.gameData=e.gameData),e.extensions&&Object.keys(e.extensions).length&&(n.extensions=e.extensions),t&&t.length&&(n.tags=t),n}tagsField(e){let t=I(this.host,e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}listBags(){let e=[{bag:this.host.patterBag}];for(let[t,n]of this.host.stageBags)e.push({bag:n,pathPrefix:`@scene:${t}.`});return e}checkpoint(){if(this.host.journal)throw new Error("a checkpoint is already open: roll it back or commit it first");let e={undo:[],flows:new Set,opened:new Set,selectors:new Set,flowSelectors:new Map};return this.host.journal=e,new H(e)}rollback(e){this.endCheckpoint(e);let t=e.journal.undo;for(let n=t.length-1;n>=0;n--)t[n]();r.forget(e.journal)}commit(e){this.endCheckpoint(e),r.forget(e.journal)}get inCheckpoint(){return this.host.journal!==null}endCheckpoint(e){if(this.host.journal!==e.journal)throw new Error("that checkpoint is not the open one");this.host.journal=null}static forget(e){e.undo=[],e.flows.clear(),e.opened.clear(),e.selectors.clear(),e.flowSelectors.clear()}refuseInCheckpoint(e){if(this.host.journal)throw new Error(`${e} can\'t be undone, so it isn\'t allowed while a checkpoint is open`)}captureFlow(e){let t=this.flowsById.get(e);if(!t)throw new Error(`no open flow \'${e}\' to capture`);return{version:j,...t.snapshot(),...Mt(e,this.host.registry.save())}}restoreFlow(e,t){this.refuseInCheckpoint("restoreFlow");let n=t.version;if(n!==2&&n!==j)throw new Error(`unsupported flow state version: ${String(n)}`);let s=t.cursor;if(typeof s!="object"||s===null)throw new Error("malformed flow state: no cursor");if(this.flowsById.has(e))throw new Error(`a flow \'${e}\' is already open; close it before restoring another under that id`);this.assertExternalScopes();let i=w.flow(e);this.host.registry.discardParked(i),this.host.registry.load(rt(e,t),{keepParked:!0,refit:!0});let o=new O(e,this.host,this.defaultSeed);try{o.restore(t)}catch(a){throw o.close(),this.host.registry.discardParked(i),a}return this.flowsById.set(e,o),this.host.reportRefits(),o}closeFlow(e){this.refuseInCheckpoint("closeFlow"),this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){this.refuseInCheckpoint("reset");for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.patterBag.reseed(this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear();for(let e of this.host.stageBags.keys())this.host.registry.remove(w.stage(e));this.host.stageBags.clear(),this.host.registry.discardParked("patter/")}release(e){for(let n of this.flowsById.values())n.releaseBags(e),n.close();this.flowsById.clear();let t=this.host.registry;for(let n of this.host.stageBags.keys())t.remove(w.stage(n),{keep:e});this.host.stageBags.clear();for(let n of["patter",...this.host.hostScopes])t.has(n)&&t.remove(n,{keep:e});for(let n of Object.keys(Ye(this.creationOptions)))t.has(n)&&t.remove(n)}getProperty(e){let{scope:t,name:n}=this.splitShared(e);return this.host.registry.get(t,n)}setProperty(e,t){let{scope:n,name:s}=this.splitShared(e),i=this.host.journal?this.host.registry.get(n,s):void 0;this.host.registry.set(n,s,t,{host:!0}),this.host.journal&&i!==void 0&&this.host.journal.undo.push(()=>this.host.registry.set(n,s,i,{host:!0}))}stageIndex(e,t){return P(this.ladderOf(e),t)}compareStages(e,t,n){return N(this.ladderOf(e),t,n)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitShared(e),s=Tt(this.host,t,n);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}log(){return this.engineLog}clearLog(){this.engineLog.length=0}onTrace(e){return this.engineTraceHandlers.add(e),this.host.tracing=!0,()=>{this.engineTraceHandlers.delete(e),this.host.tracing=this.host.logEnabled||this.engineTraceHandlers.size>0}}emitEngine(e,t,n){for(let s of this.engineTraceHandlers)s(e,t);this.host.logEnabled&&this.engineLog.push({...t,flow:e,seq:this.engineSeq++,...n?{scene:n}:{}})}getVisitCounts(){return Object.fromEntries(this.host.sharedVisits)}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:A(e),writable:e.writable??!0}))}splitShared(e){let t=nt(this.host,e);if(t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return{patter:this.host.patterBag.save()}}load(e){e.patter&&this.host.patterBag.load(e.patter)}saveGame(){let e={};for(let[t,n]of this.flowsById)e[t]=n.snapshot();return{version:j,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:Ze(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==j)throw new Error(`unsupported save version: ${String(t)}`);let n=e.flows;if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("malformed save: no flows");this.assertExternalScopes();let s=this.host.registry;for(let[o,a]of this.flowsById)a.releaseBags(o in e.flows),a.close();this.flowsById.clear();for(let o of this.host.stageBags.keys())s.remove(w.stage(o),{keep:!0});this.host.stageBags.clear();let i=e.version===2?jt(e):e.registry;i&&(this.host.ownsRegistry?s.load(i,{refit:!0}):s.load(i,{keepParked:!0,refit:!0})),this.host.sharedVisits.clear();for(let[o,a]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(o,a);this.host.sharedSelectors.clear();for(let[o,a]of et(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new O(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}this.host.reportRefits()}},O=class{id;host;local;registered=new Set;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPrompt=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.patterBag.get(e):this.local.get(e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.registry.set("patter",e,t):this.local.set(e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;if(t!==null)return this.sceneBagFor(t,e)?.get(e)},set:(e,t)=>{let n=this.currentSceneId;if(n===null)return;let s=this.sceneBagFor(n,e);s&&s.set(e,t)}};evalCtx;ctxRevision=-1;registryQualities;flowLog=[];flowSeq=0;constructor(e,t,n){this.id=e,this.host=t,this.rngState=T(n),this.local=this.newLocal(),this.evalCtx={scopes:{},host:{nextRandom:this.rng,visits:s=>this.visitCounts.get(s)??0,patterVisits:s=>this.host.sharedVisits.get(s)??0},qualities:(s,i)=>this.stagesFor(s,i)}}context(){let e=this.host.registry;if(e.revision!==this.ctxRevision){let t=e.toEvalContext(),n=this.evalCtx.scopes;for(let s of Object.keys(n))delete n[s];Object.assign(n,t.scopes),n.patter=this.patterResolver,n.scene=this.sceneResolver,this.registryQualities=t.qualities,this.ctxRevision=e.revision}return this.evalCtx}stagesFor(e,t){let n=t.toLowerCase();if(e==="patter")return _(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(n);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:_(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(n)}return this.registryQualities?.(e,t)??_(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}begin(e,t){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");if(this.releaseBags(!1),this.host.registry.discardParked(w.flow(this.id)),this.mountLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.clearPending(),this.started=!0,t){let n=this.host.blockIndex.get(t);if(!n)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(n.sceneId),this.stack=[{sceneId:n.sceneId,containerId:t,index:0}],this.enter(t)}else{let n=e??Object.keys(this.host.bundle.scenes)[0],s=n?this.host.bundle.scenes[n]:void 0;if(!s)throw new Error(n?`unknown scene: ${n}`:"no scenes in bundle");this.enterSceneSetup(n);let i=s.blocks[0];i&&(this.stack=[{sceneId:n,containerId:i.id,index:0}],this.enter(i.id))}this.settle()}reset(e,t){this.begin(e,t)}start(e,t){this.reset(e,t)}goto(e,t){if(this.closed)return!1;if(this.touch(),e==="END")return this.started=!0,this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let n=fe(this.host,e);if(n===void 0)return!1;let s;return t!==void 0&&(s=he(this.host,n,t),s===void 0)?!1:this.started?(this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??n,"jump"),this.settle(),!0):(this.begin(n,s),!0)}close(){this.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.clearPending()}clearPending(){this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started:n,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:d}=this,h=this.stack.map(f=>({...f}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:n,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:d,stack:h})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.touch(),this.pendingPrompt){let e=this.pendingPrompt;return this.pendingPrompt=null,this.pendingPromptOwnerId=null,e.shown?this.promptResult(e.beat,e.shown):this.beatResult(e.beat)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit,this.activeSnippet.id);let i=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(i);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let n=this.childrenOf(t.containerId);if(!n){this.stack.pop();continue}let s=t.index;for(;t.index<n.length&&!this.eligible(n[t.index]);)t.index++;if(this.host.tracing&&t.index!==s&&this.emit({type:"select",group:t.containerId,selector:"run",children:n.slice(s,t.index+1).map((i,o)=>({id:i.id,eligible:s+o===t.index})),picked:n[t.index]?.id??null}),t.index>=n.length){this.stack.pop();continue}this.enterChild(n[t.index++])}}listBags(){let e=[{bag:this.local,pathPrefix:`${this.id}/@patter.`}];for(let[t,n]of this.sceneBags)e.push({bag:n,pathPrefix:`${this.id}/@scene:${t}.`});return e}log(){return this.flowLog}clearLog(){this.flowLog.length=0}emit(e){let t=this.currentSceneId??void 0;this.host.emitEngine(this.id,e,t),this.host.logEnabled&&this.flowLog.push({...e,seq:this.flowSeq++,...t?{scene:t}:{}})}getVisitCounts(){return Object.fromEntries(this.visitCounts)}stepFor(e){let t=Bt(this.host.bundle).get(e);return t?this.beatResult(t):void 0}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let n=t.options.find(o=>o.id===e);if(!n)throw new Error(`unknown choice option: ${e}`);if(!n.eligible)throw new Error(`choice option is not eligible: ${e}`);this.touch();let s=t.byId.get(e);this.host.tracing&&this.emit({type:"chose",group:t.groupId,option:e}),this.pendingChoice=null;let i=this.host.replayPromptOnChoose?this.authoredPromptOf(s):void 0;this.pendingPrompt=i&&n.prompt?{beat:i,shown:{...n.prompt}}:null,this.pendingPromptOwnerId=this.pendingPrompt?s.id:null,this.enterChild(s)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:n}=this.splitRef(e);return t==="patter"?this.patterResolver.get(n):t==="scene"?this.sceneResolver.get(n):this.host.registry.get(t,n)}setProperty(e,t){this.writeProperty(e,t,!0)}stageIndex(e,t){return P(this.ladderOf(e),t)}compareStages(e,t,n){return N(this.ladderOf(e),t,n)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitRef(e);this.context();let s=this.stagesFor(t,n);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}writeProperty(e,t,n){let{scope:s,name:i}=this.splitRef(e),o=this.host.journal;if(s==="patter"){if(o){let a=this.host.patterSharedNames.has(i),c=a?this.host.patterBag.get(i):this.local.get(i),l=this.local;c!==void 0&&o.undo.push(a?()=>this.host.registry.set("patter",i,c):()=>l.set(i,c))}this.patterResolver.set(i,t)}else if(s==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);if(o){let a=this.sceneBagFor(this.currentSceneId,i),c=a?.get(i);a&&c!==void 0&&o.undo.push(()=>a.set(i,c))}this.sceneResolver.set(i,t)}else{let a=o?this.host.registry.get(s,i):void 0;this.host.registry.set(s,i,t,n?{host:!0}:void 0),o&&a!==void 0&&o.undo.push(()=>this.host.registry.set(s,i,a,{host:!0}))}}snapshot(){return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(({extensions:e,...t})=>({...t}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:Ft(this.pendingPrompt.shown)}:{},selectors:Ze(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");this.restoreCursor(e),this.releaseBags(!0),this.mountLocal();for(let t of new Set([this.currentSceneId,...this.stack.map(n=>n.sceneId)]))t!==null&&this.host.bundle.scenes[t]&&this.ensureSceneBags(t)}restoreCursor(e){this.rngState=T(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(n=>{let{nextId:s,...i}=n;if(s!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===s)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.activeSnippet=null,t.activeSnippetId!==null){let n=this.host.nodeIndex.get(t.activeSnippetId);n&&n.type==="snippet"&&(this.activeSnippet=n)}if(this.selectors=et(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let n=new Map,s=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);if(!o)continue;n.set(i.id,o);let{prompt:a,extensions:c,...l}=i,d=a?.kind==="line"&&a.speakerLabel===void 0?q(this.host,a.character,a.qualifier):void 0;s.push({...l,...a?{prompt:{...a,padAfter:a.padAfter??this.promptPadOf(o),...d!==void 0?{speakerLabel:d}:{}}}:{},...b(o.extensions)})}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:n})}if(this.pendingPrompt=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let n=this.host.nodeIndex.get(this.pendingPromptOwnerId),s=n?this.authoredPromptOf(n):void 0,i=t.pendingPrompt,o=i?.kind==="line"&&i.speakerLabel===void 0?q(this.host,i.character,i.qualifier):void 0;s?this.pendingPrompt={beat:s,...i?{shown:{...i,padAfter:i.padAfter??this.padOf(s.id),...o!==void 0?{speakerLabel:o}:{}}}:{}}:this.pendingPromptOwnerId=null}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry,t.id)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let n=this.selectChild(e);n&&this.enterChild(n)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let n=this.host.nodeIndex.get(e);if(n&&n.type==="group")return n.children}beginSnippet(e){this.runEffects(e.onEnter,e.id),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],n=new Map,s=[];for(let o of e.children){if(o.fallback===!0){s.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),c=o.secretUntilEligible===!0;!a&&c||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData,...b(o.extensions)}),n.set(o.id,o))}if(t.some(o=>o.eligible)){this.host.tracing&&this.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),this.pendingChoice={groupId:e.id,options:t,byId:n};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.tracing&&this.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.host.tracing&&this.emit({type:"jump",to:e,mode:t}),e==="END"){this.flowEnded=!0,this.stack=[];return}let n,s,i=this.host.bundle.scenes[e];if(i){this.enterSceneSetup(e);let a=i.blocks[0];if(!a){t==="jump"&&(this.stack=[]);return}n=e,s=a.id}else{let a=this.host.blockIndex.get(e);if(!a)throw new Error(`jump target not found: ${e}`);a.sceneId!==this.currentSceneId&&this.enterSceneSetup(a.sceneId),n=a.sceneId,s=e}this.enter(s);let o={sceneId:n,containerId:s,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.map(c=>({id:c.id,eligible:this.eligible(c)})),n=e.children.filter((c,l)=>t[l].eligible),s=e.options?.order??"sequential",i=e.options?.exhaust??"once",o=c=>(this.host.tracing&&this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(n.length===0)return o(null);let a=this.selectorState(e);switch(e.selector){case"branch":return o(n[0]);case"sequence":return o(s==="shuffle"?this.pickShuffle(n,i,a):s==="specificity"?this.pickSpecificity(n,i,a):this.pickSequential(n,i,a));default:return null}}pickSequential(e,t,n){let s=e.length,i=n.seq??0;return n.seq=i+1,t==="repeat"?e[i%s]:i<s?e[i]:t==="stick"?e[s-1]:null}pickShuffle(e,t,n){let s=e.length,i=t==="stick",o=()=>(i?e.slice(0,s-1):e).map(f=>f.id),a=new Set(e.map(f=>f.id));if(n.bag===void 0&&(n.bag=o()),!n.bag.some(f=>a.has(f))){if(t==="once")return null;if(i){let f=e[s-1];return n.last=f.id,f}if(n.bag=o(),n.bag.length===0)return null}let c=n.bag.filter(f=>a.has(f)),l=n.last!==void 0&&c.length>1?c.indexOf(n.last):-1,d=Math.floor(this.rng()*(l>=0?c.length-1:c.length));l>=0&&d>=l&&d++;let h=c[d];return n.bag.splice(n.bag.indexOf(h),1),n.last=h,e.find(f=>f.id===h)}pickSpecificity(e,t,n){let s=e;if(t!=="repeat"){n.bag===void 0&&(n.bag=e.map(d=>d.id));let l=new Set(n.bag);if(s=e.filter(d=>l.has(d.id)),s.length===0)return t==="stick"&&n.last!==void 0?e.find(d=>d.id===n.last)??null:null}let i=-1,a=s.map(l=>{let d=this.specScore(l);return d>i&&(i=d),{c:l,s:d}}).filter(l=>l.s===i).map(l=>l.c),c;if(a.length===1)c=a[0];else{let l=n.last!==void 0?a.findIndex(h=>h.id===n.last):-1,d=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&d>=l&&d++,c=a[d]}return t!=="repeat"&&(n.bag=n.bag.filter(l=>l!==c.id)),n.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0,e):0}matchedSpec(e,t,n){return Re(e,i=>{try{return Xe(J(i,this.context(),ue))}catch(o){return this.reportError("best-match",n.id,n.condition,o),!1}},{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,n=this.host.journal,s;if(n&&(s=e.shared?n.selectors:n.flowSelectors.get(this),s||(s=new Set,n.flowSelectors.set(this,s))),n&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;n.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e,t){for(let n of e??[]){let s;try{s=this.evalExpr(n.value);let i=this.host.tracing?this.getProperty(n.target):void 0;this.writeProperty(n.target,s,!1),this.host.tracing&&this.emit({type:"write",target:n.target,value:s,...i!==void 0?{prev:i}:{}})}catch(i){this.reportError("effect",t,n.value,i)}}}eligible(e){if(!e.condition)return!0;try{return Xe(this.evalExpr(e.condition))}catch(t){return this.reportError("condition",e.id,e.condition,t),!1}}reportError(e,t,n,s){let i=s instanceof Error?s.message:String(s),o=n?.src;this.host.onError({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i}),this.host.tracing&&this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return J(this.conditionAst(e),this.context(),ue)}conditionAst(e){let t=$e.get(e);return t||(t=C(e.ast),$e.set(e,t)),t}enter(e){let t=this.visitCounts,n=t.get(e);t.set(e,(n??0)+1);let s=this.host.sharedVisits,i=s.get(e);s.set(e,(i??0)+1),this.host.journal&&this.host.journal.undo.push(()=>{n===void 0?t.delete(e):t.set(e,n),i===void 0?s.delete(e):s.set(e,i)})}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=ae(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=I(this.host,e.id),n=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...b(e.extensions),...n};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...n};case"line":{let s=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?s:this.interpolate(s)),c=i&&a.length===0;return{type:"line",id:e.id,text:a,character:c?void 0:e.character,characterName:c?void 0:this.resolveCharacterName(e.character),direction:c?void 0:e.direction,qualifier:c?void 0:e.qualifier,qualifierName:c?void 0:this.resolveQualifierName(e.qualifier),speakerLabel:c?void 0:q(this.host,e.character,e.qualifier),padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...n}}}}interpolate(e){return Fe(e,t=>this.getProperty(t))}stripCaptions(e){return Ae(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let n=this.interpolate(this.resolveString(t.id)),s=this.padOf(t.id);return t.kind==="line"?{kind:"line",text:this.captionLine(n),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction,qualifier:t.qualifier,qualifierName:this.resolveQualifierName(t.qualifier),speakerLabel:q(this.host,t.character,t.qualifier),padAfter:s}:{kind:"text",text:n,padAfter:s}}authoredPromptOf(e){return e.type==="group"?e.prompt:void 0}promptResult(e,t){let n=I(this.host,e.id),s=n&&n.length?{tags:n}:{};return t.kind==="text"?{type:"text",id:e.id,text:t.text,padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...s}:{type:"line",id:e.id,text:t.text,character:t.character,characterName:t.characterName,direction:t.direction,qualifier:t.qualifier,qualifierName:t.qualifierName,speakerLabel:t.speakerLabel,padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...s}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(n=>n.kind==="line"||n.kind==="text")}firstTextSnippetIn(e){let t;return D(e,n=>{!t&&n.type==="snippet"&&(n.beats??[]).some(s=>s.kind==="line"||s.kind==="text")&&(t=n)}),t}resolveString(e){if(this.host.emitIds)return e;let t=this.host.strings[e];if(t!==void 0)return t;let n=this.host.defaultStrings[e];return n!==void 0?`<Untranslated: ${e}> ${n}`:e}resolveCharacterName(e){if(e===void 0||this.host.emitIds)return;let t=Z(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}promptPadOf(e){let t=this.promptBeatOf(e);return t?this.padOf(t.id):E}padOf(e){return this.host.padIndex.get(e)?.resolved??E}resolveQualifierName(e){if(e===void 0||this.host.emitIds)return;let t=ee(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.qualifierDisplay.get(e)}splitRef(e){return nt(this.host,e)}newLocal(){return new k(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=w.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:$}),this.registered.add(e),this.host.reportRefits()}releaseBags(e){for(let t of this.registered)this.host.registry.remove(t,{keep:e});this.registered.clear(),this.sceneBags.clear()}sceneBagFor(e,t){if(this.host.bundle.scenes[e])return this.ensureSceneBags(e),this.host.sceneSharedNames.get(e)?.has(t.toLowerCase())?this.host.stageBags.get(e):this.sceneBags.get(e)}ensureSceneBags(e){let t=this.host.sceneSharedNames.get(e)??new Set,n=this.host.bundle.scenes[e]?.sceneProps??[],s=this.host.journal,i=!this.sceneBags.has(e)||!this.host.stageBags.has(e);if(!this.sceneBags.has(e)){let o=new k(n.filter(l=>!t.has(l.name.toLowerCase())),{pathPrefix:"@scene."}),a=w.flowScene(this.id,e),c=Qe(this.host.registry,a,o,s!==null);this.registered.add(a),this.sceneBags.set(e,o),s&&s.undo.push(()=>{this.sceneBags.get(e)===o&&(this.host.registry.remove(a,c?{keep:!0}:void 0),this.registered.delete(a),this.sceneBags.delete(e))})}if(!this.host.stageBags.has(e)){let o=new k(n.filter(c=>t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),a=Qe(this.host.registry,w.stage(e),o,s!==null);this.host.stageBags.set(e,o),s&&s.undo.push(()=>{this.host.stageBags.get(e)===o&&(this.host.registry.remove(w.stage(e),a?{keep:!0}:void 0),this.host.stageBags.delete(e))})}i&&this.host.reportRefits()}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;this.ensureSceneBags(e.id);for(let n of e.sceneProps??[]){if(!n.temporary)continue;let s=n.name.toLowerCase(),i=t.has(s)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);if(!i)continue;let o=this.host.journal?i.get(s):void 0;o!==void 0&&this.host.journal.undo.push(()=>i.set(s,o)),i.set(s,A(n))}}};function Ke(r,e){D(r,t=>{if(t.type==="group"){t.prompt?.kind==="line"&&t.prompt.character&&e.add(t.prompt.character);return}for(let n of t.beats??[])n.kind==="line"&&n.character&&e.add(n.character)})}function q(r,e,t,n=!1){if(!e||r.emitIds)return;let s=c=>(n?void 0:r.strings[c])??r.defaultStrings[c],i=s(Z(e))??r.castDisplay.get(e)??e;if(t===void 0)return i;let o=r.speakerLabels.get(`${e}\\0${t}`);if(o!==void 0)return s(Te(e,t))??o;let a=s(ee(t))??r.qualifierDisplay.get(t)??t;return Me(s(Le)??r.speakerPattern,i,a)}function Je(r){return r&&Object.keys(r).length?{gameData:r}:{}}function b(r){return r&&Object.keys(r).length?{extensions:r}:{}}function Ft(r){let e={kind:r.kind,text:r.text};return r.character!==void 0&&(e.character=r.character),r.characterName!==void 0&&(e.characterName=r.characterName),r.direction!==void 0&&(e.direction=r.direction),r.qualifier!==void 0&&(e.qualifier=r.qualifier),r.qualifierName!==void 0&&(e.qualifierName=r.qualifierName),r.speakerLabel!==void 0&&(e.speakerLabel=r.speakerLabel),typeof r.padAfter=="number"&&(e.padAfter=r.padAfter),e}function Ze(r){let e={};for(let[t,n]of r){let s={};n.seq!==void 0&&(s.seq=n.seq),n.bag&&(s.bag=[...n.bag]),n.last!==void 0&&(s.last=n.last),e[t]=s}return e}function et(r){let e=new Map;for(let[t,n]of Object.entries(r??{})){let s={};n.seq!==void 0&&(s.seq=n.seq),n.bag&&(s.bag=[...n.bag]),n.last!==void 0&&(s.last=n.last),e.set(t,s)}return e}function Qe(r,e,t,n){let s=n?JSON.stringify(t.save()):"";return r.mountOwned(e,t,{owner:$}),n&&JSON.stringify(t.save())!==s}function ze(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default}}function Ye(r){return{...r.world?{world:r.world}:{},...r.hostScopes}}function _(r,e,t){let n=r.qualityLadders.get(e);if(!n){n=new Map;for(let s of t()??[]){let i=s.name.toLowerCase();s.type==="quality"&&s.stages&&!n.has(i)&&n.set(i,s.stages)}r.qualityLadders.set(e,n)}return n}function Tt(r,e,t){let n=t.toLowerCase();return e==="patter"?_(r,"patter",()=>[...r.patterSharedDecls,...r.patterLocalDecls]).get(n):r.registry.toEvalContext().qualities?.(e,t)??_(r,`host/${e}`,()=>r.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}function tt(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default,writable:r.writable}}function Lt(r,e){let t=tt(r),n=r.writable??e;return n===void 0?t:{...t,writable:n}}function fe(r,e){return r.sceneGameIdToId.get(e)??(r.bundle.scenes[e]?e:void 0)}function he(r,e,t){return r.blockGameIdToId.get(e)?.get(t)??(r.blockIndex.get(t)?.sceneId===e?t:void 0)}function nt(r,e){r.refSplitRevision!==r.registry.revision&&(r.refSplitCache.clear(),r.refSplitRevision=r.registry.revision);let t=r.refSplitCache.get(e);return t||(t=Be(e,n=>n==="scene"||r.registry.has(n)||(r.bundle.externalScopes?.includes(n)??!1)),r.refSplitCache.set(e,t)),t}function jt(r){let e={};r.shared?.patter&&(e.patter=r.shared.patter);for(let[t,n]of Object.entries(r.stageBags??{}))e[w.stage(t)]=n;for(let[t,n]of Object.entries(r.flows??{}))Object.assign(e,rt(t,n));return e}function rt(r,e){let t={};e.scopes?.patter&&(t[w.flowGlobals(r)]=e.scopes.patter);for(let[n,s]of Object.entries(e.sceneBags??{}))t[w.flowScene(r,n)]=s;return t}function Mt(r,e){let t={scopes:{},sceneBags:{}},n=w.flow(r);for(let[s,i]of Object.entries(e)){if(!s.startsWith(n))continue;let o=s.slice(n.length);o==="patter"?t.scopes.patter=i:o.startsWith("scene/")&&(t.sceneBags[At(o.slice(6))]=i)}return t}function Xe(r){return typeof r=="boolean"?r:typeof r=="number"?r!==0:typeof r=="string"?r!=="":r.length>0}var Vt=(r,e)=>r.shared??e;function me(r,e){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.default!==void 0?{default:r.default}:{},shared:Vt(r,e)}}function Gt(r){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.values?{values:[...r.values]}:{},...r.purpose?{purpose:r.purpose}:{}}}function qt(r,e){e.blocks++;let t=[...r.children];for(;t.length;){let n=t.pop();if(n.type==="group"){e.groups++,n.prompt&&e.prompts++,t.push(...n.children);continue}e.snippets++;for(let s of n.beats??[])e.beats++,s.kind==="gameEvent"&&e.gameEvents++}}function st(r){let e={scenes:0,blocks:0,groups:0,snippets:0,beats:0,prompts:0,gameEvents:0,cast:r.cast?.length??0},t=[],n=[];for(let o of Object.values(r.scenes)){e.scenes++;let a=y(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:y(c),name:c.name}))});for(let c of o.blocks)qt(c,e);o.sceneProps?.length&&n.push({gameId:a,properties:o.sceneProps.map(c=>me(c,!1))})}let s=(r.scopeRegistry?.scopes??[]).map(o=>({token:o.token,writable:o.writable??!0,opaque:o.declarations===void 0,properties:(o.declarations??[]).map(a=>me(a,!0))})),i=Object.entries(r.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Gt)}));return{identity:{schema:r.schema,project:r.content.project,...r.content.version!==void 0?{version:r.content.version}:{},...r.content.hash!==void 0?{hash:r.content.hash}:{},...r.content.structureHash!==void 0?{structureHash:r.content.structureHash}:{},voiced:r.voiced,defaultLocale:r.locales.default,locales:[...r.locales.included],localisation:r.localisation?.mode??"embedded",sourceDebug:r.localisation?.sourceDebug??!1},addresses:t,hostScopes:s,properties:{patter:(r.properties??[]).map(o=>me(o,!0)),scene:n},gameData:i,counts:e}}var U=(r,e)=>Object.prototype.hasOwnProperty.call(r,e);function it(r,e){return r.gameDataFields?.[e]??[]}function ye(r,e,t){return e&&U(e,t)?e[t]:r.find(n=>n.name===t)?.default}function ot(r,e){let t={};for(let n of r){let s=ye(r,e,n.name);s!==void 0&&(t[n.name]=s)}for(let[n,s]of Object.entries(e??{}))U(t,n)||(t[n]=s);return t}function at(r,e,t){let n=r.extensions!==void 0&&U(r.extensions,e)?r.extensions[e]:void 0;return n!==void 0&&U(n.fields,t)?n.fields[t]??[]:[]}function ct(r,e,t=""){let n=o=>({...o?.extensions??{}}),s=Object.values(r.scenes??{}),i=o=>(U(r.scenes??{},o)?r.scenes[o]:void 0)??s.find(a=>y(a)===o);switch(e){case"project":{let o={};for(let[a,c]of Object.entries(r.extensions??{}))c.project!==void 0&&(o[a]=c.project);return o}case"scene":return n(i(t));case"block":{let o=t.indexOf("/");if(o<0)return{};let a=i(t.slice(0,o))?.blocks??[],c=t.slice(o+1);return n(a.find(l=>l.id===c)??a.find(l=>y(l)===c))}case"snippet":case"option":case"line":case"text":case"gameEvent":{let o,a=(c,l)=>{for(let d of c??[]){if(o!==void 0)return;if(d.type==="group"){if(l&&d.id===t&&e==="option"){o=d;return}if(l&&d.prompt?.id===t&&d.prompt.kind===e){o=d.prompt;return}a(d.children??[],d.selector==="choice");continue}if(d.id===t&&e==="snippet"){o=d;return}let h=(d.beats??[]).find(f=>f.id===t);if(h!==void 0){h.kind===e&&(o=h);return}}};for(let c of s)for(let l of c.blocks??[])o===void 0&&a(l.children??[],!1);return n(o)}default:return{}}}return yt($t);})();\n';
|
|
1544
1774
|
var PLAYABLE_TIMING_JS = 'var GameSubtitles=(()=>{var l=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var u=(t,e)=>{for(var s in e)l(t,s,{get:e[s],enumerable:!0})},f=(t,e,s,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of c(e))!_.call(t,i)&&i!==s&&l(t,i,{get:()=>e[i],enumerable:!(n=a(e,i))||n.enumerable});return t};var p=t=>f(l({},"__esModule",{value:!0}),t);var d={};u(d,{estimateDuration:()=>h});function h(t,{charsPerSecond:e=14,minSeconds:s=1.5,maxSeconds:n=18}={}){let i=0;for(let o of t??"")o!=="\\xAD"&&i++;let r=e>0?e:14;return Math.min(Math.max(i/r,s),n)}return p(d);})();\n';
|
|
1545
1775
|
|
|
1546
1776
|
// src/play-timeline.ts
|
|
@@ -1987,7 +2217,7 @@ function renderPlay(result) {
|
|
|
1987
2217
|
// src/coverage.ts
|
|
1988
2218
|
import { Engine as Engine2 } from "@patterkit/runtime";
|
|
1989
2219
|
import { isContentlessBeat as isContentlessBeat2, walkNodes } from "@patterkit/model";
|
|
1990
|
-
import { deserialiseAst, makePrng } from "@wildwinter/expr";
|
|
2220
|
+
import { deserialiseAst, makePrng, stagesEitherSide } from "@wildwinter/expr";
|
|
1991
2221
|
var CADENCE_PROB = {
|
|
1992
2222
|
rarely: 0.15,
|
|
1993
2223
|
sometimes: 0.4,
|
|
@@ -2208,6 +2438,11 @@ function proposeCoverageDrivers(loaded) {
|
|
|
2208
2438
|
else if (decl.type === "quality" && decl.stages) values = [...decl.stages];
|
|
2209
2439
|
}
|
|
2210
2440
|
if (values.length === 0) continue;
|
|
2441
|
+
if (decl?.type === "quality" && decl.stages) {
|
|
2442
|
+
const near = new Set(values.flatMap((v) => stagesEitherSide(decl, v)));
|
|
2443
|
+
drivers.push({ ref, kind: "recurring", cadence: "sometimes", values: decl.stages.filter((st) => near.has(st)) });
|
|
2444
|
+
continue;
|
|
2445
|
+
}
|
|
2211
2446
|
drivers.push({ ref, kind: "recurring", cadence: "sometimes", values: sortValues(values) });
|
|
2212
2447
|
}
|
|
2213
2448
|
return drivers.sort((a, b) => a.ref.localeCompare(b.ref));
|
|
@@ -2484,9 +2719,160 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
|
|
|
2484
2719
|
}
|
|
2485
2720
|
|
|
2486
2721
|
// src/format.ts
|
|
2487
|
-
import { readFileSync as
|
|
2488
|
-
import { dirname as
|
|
2489
|
-
import { canonicalStringify as
|
|
2722
|
+
import { readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
2723
|
+
import { dirname as dirname6, resolve as resolve5 } from "path";
|
|
2724
|
+
import { canonicalStringify as canonicalStringify6, parseSource as parseSource6 } from "@patterkit/core";
|
|
2725
|
+
|
|
2726
|
+
// src/extensions.ts
|
|
2727
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
2728
|
+
import { join as join7, relative as relative5, sep as sep3 } from "path";
|
|
2729
|
+
import { canonicalStringify as canonicalStringify5, parseSource as parseSource5 } from "@patterkit/core";
|
|
2730
|
+
import { EXTENSIONS_DIR as EXTENSIONS_DIR3, MANIFEST_EXTENSION as MANIFEST_EXTENSION3 } from "@patterkit/model";
|
|
2731
|
+
import { manifestProblems, parseManifest, renameStages, targetOf } from "@wildwinter/toolkit/extensions";
|
|
2732
|
+
var CONFLICT_MARKERS = /^(<{7}|={7}|>{7})( |$)/m;
|
|
2733
|
+
var manifestPath = (projectDir, id) => join7(projectDir, EXTENSIONS_DIR3, `${id}${MANIFEST_EXTENSION3}`);
|
|
2734
|
+
function readProjectManifest(projectDir, id) {
|
|
2735
|
+
if (!/^[a-z][a-z0-9-]*$/.test(id)) return void 0;
|
|
2736
|
+
let text;
|
|
2737
|
+
try {
|
|
2738
|
+
text = readFileSync7(manifestPath(projectDir, id), "utf8");
|
|
2739
|
+
} catch {
|
|
2740
|
+
return void 0;
|
|
2741
|
+
}
|
|
2742
|
+
return soundManifest(text);
|
|
2743
|
+
}
|
|
2744
|
+
function soundManifest(text) {
|
|
2745
|
+
if (CONFLICT_MARKERS.test(text)) return void 0;
|
|
2746
|
+
try {
|
|
2747
|
+
const manifest = parseManifest(text);
|
|
2748
|
+
return manifestProblems(manifest).some((p) => p.severity === "error") ? void 0 : manifest;
|
|
2749
|
+
} catch {
|
|
2750
|
+
return void 0;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
var projectManifests = (projectDir) => (id) => readProjectManifest(projectDir, id);
|
|
2754
|
+
var soundOf = (manifest) => manifestProblems(manifest).some((p) => p.severity === "error") ? void 0 : manifest;
|
|
2755
|
+
function planExtensionUpgrades(loaded) {
|
|
2756
|
+
const writes = /* @__PURE__ */ new Map();
|
|
2757
|
+
const report = [];
|
|
2758
|
+
const notes = /* @__PURE__ */ new Map();
|
|
2759
|
+
const note = (path, line) => {
|
|
2760
|
+
notes.set(path, [...notes.get(path) ?? [], line]);
|
|
2761
|
+
};
|
|
2762
|
+
const taken = loaded.project.extensions ?? {};
|
|
2763
|
+
const moved = {};
|
|
2764
|
+
const rel = (path) => relative5(loaded.root, path).split(sep3).join("/");
|
|
2765
|
+
for (const [id, pin] of Object.entries(taken)) {
|
|
2766
|
+
const file = loaded.manifests.find((m) => m.id === id);
|
|
2767
|
+
const manifest = file !== void 0 ? soundOf(file.manifest) : void 0;
|
|
2768
|
+
if (manifest === void 0 || pin?.version === manifest.version) continue;
|
|
2769
|
+
moved[id] = { ...pin, version: manifest.version };
|
|
2770
|
+
const took = `took the Patter extension "${id}" at version ${manifest.version}${pin?.version ? ` (was ${pin.version})` : ""}`;
|
|
2771
|
+
report.push(took);
|
|
2772
|
+
note(loaded.projectFile, took);
|
|
2773
|
+
loaded.extensionShards.forEach((shard, i) => {
|
|
2774
|
+
if (shard.extension !== id) return;
|
|
2775
|
+
let renamed = 0;
|
|
2776
|
+
const entries = {};
|
|
2777
|
+
for (const [kind, byNode] of Object.entries(shard.entries ?? {})) {
|
|
2778
|
+
const target = targetOf(manifest, "patter", kind);
|
|
2779
|
+
const out = {};
|
|
2780
|
+
for (const [node, entry] of Object.entries(byNode ?? {})) {
|
|
2781
|
+
if (target === void 0 || typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
2782
|
+
out[node] = entry;
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
const r = renameStages(target, entry);
|
|
2786
|
+
renamed += r.renames.length;
|
|
2787
|
+
out[node] = r.entry;
|
|
2788
|
+
}
|
|
2789
|
+
entries[kind] = out;
|
|
2790
|
+
}
|
|
2791
|
+
if (renamed > 0) {
|
|
2792
|
+
const path = loaded.extensionShardFiles[i];
|
|
2793
|
+
writes.set(path, canonicalStringify5({ ...shard, entries }));
|
|
2794
|
+
const what = `renamed ${renamed} stage value${renamed === 1 ? "" : "s"} for "${id}"`;
|
|
2795
|
+
report.push(` ${what} in ${rel(path)}`);
|
|
2796
|
+
note(path, what);
|
|
2797
|
+
}
|
|
2798
|
+
});
|
|
2799
|
+
}
|
|
2800
|
+
if (Object.keys(moved).length > 0) {
|
|
2801
|
+
const project = parseSource5(readFileSync7(loaded.projectFile, "utf8"));
|
|
2802
|
+
writes.set(loaded.projectFile, canonicalStringify5({ ...project, extensions: { ...taken, ...moved } }));
|
|
2803
|
+
}
|
|
2804
|
+
return { writes, report, notes };
|
|
2805
|
+
}
|
|
2806
|
+
function renameCastKeys(shards, manifests, renames) {
|
|
2807
|
+
const out = [];
|
|
2808
|
+
if (renames.size === 0) return out;
|
|
2809
|
+
const sound = /* @__PURE__ */ new Map();
|
|
2810
|
+
for (const m of manifests) {
|
|
2811
|
+
const s = soundOf(m.manifest);
|
|
2812
|
+
if (s !== void 0) sound.set(m.id, s);
|
|
2813
|
+
}
|
|
2814
|
+
for (const { path, shard } of shards) {
|
|
2815
|
+
const manifest = sound.get(shard.extension);
|
|
2816
|
+
if (manifest === void 0) continue;
|
|
2817
|
+
const count = { n: 0 };
|
|
2818
|
+
const entries = {};
|
|
2819
|
+
for (const [kind, byNode] of Object.entries(shard.entries ?? {})) {
|
|
2820
|
+
const fields = targetOf(manifest, "patter", kind)?.fields ?? [];
|
|
2821
|
+
const nodes = {};
|
|
2822
|
+
for (const [node, entry] of Object.entries(byNode ?? {})) {
|
|
2823
|
+
nodes[node] = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? renameFields(fields, entry, renames, count) : entry;
|
|
2824
|
+
}
|
|
2825
|
+
entries[kind] = nodes;
|
|
2826
|
+
}
|
|
2827
|
+
if (count.n > 0) out.push({ path, shard: { ...shard, entries }, renamed: count.n });
|
|
2828
|
+
}
|
|
2829
|
+
return out;
|
|
2830
|
+
}
|
|
2831
|
+
function renameFields(fields, record, renames, count) {
|
|
2832
|
+
const decls = new Map(fields.map((f) => [f.name, f]));
|
|
2833
|
+
const out = {};
|
|
2834
|
+
for (const [k, v] of Object.entries(record)) {
|
|
2835
|
+
const decl = decls.get(k);
|
|
2836
|
+
out[k] = decl !== void 0 ? renameValue(decl, v, renames, count) : v;
|
|
2837
|
+
}
|
|
2838
|
+
return out;
|
|
2839
|
+
}
|
|
2840
|
+
function renameValue(decl, value, renames, count) {
|
|
2841
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2842
|
+
switch (decl.type) {
|
|
2843
|
+
case "reference": {
|
|
2844
|
+
const to = decl.to === "cast" && typeof value === "string" ? renames.get(value) : void 0;
|
|
2845
|
+
if (to === void 0) return value;
|
|
2846
|
+
count.n++;
|
|
2847
|
+
return to;
|
|
2848
|
+
}
|
|
2849
|
+
case "record":
|
|
2850
|
+
return isRecord(value) ? renameFields(decl.fields ?? [], value, renames, count) : value;
|
|
2851
|
+
case "list":
|
|
2852
|
+
return Array.isArray(value) && decl.of !== void 0 ? value.map((v) => renameValue(decl.of, v, renames, count)) : value;
|
|
2853
|
+
case "map": {
|
|
2854
|
+
if (!isRecord(value)) return value;
|
|
2855
|
+
const castKeys = decl.key?.type === "reference" && decl.key.to === "cast";
|
|
2856
|
+
const finals = Object.keys(value).map((k) => castKeys ? renames.get(k) ?? k : k);
|
|
2857
|
+
const taken = /* @__PURE__ */ new Map();
|
|
2858
|
+
for (const f of finals) taken.set(f, (taken.get(f) ?? 0) + 1);
|
|
2859
|
+
const out = {};
|
|
2860
|
+
Object.entries(value).forEach(([k, v], i) => {
|
|
2861
|
+
const renamed = decl.of !== void 0 ? renameValue(decl.of, v, renames, count) : v;
|
|
2862
|
+
const to = finals[i];
|
|
2863
|
+
if (to !== k && taken.get(to) === 1) {
|
|
2864
|
+
out[to] = renamed;
|
|
2865
|
+
count.n++;
|
|
2866
|
+
} else out[k] = renamed;
|
|
2867
|
+
});
|
|
2868
|
+
return out;
|
|
2869
|
+
}
|
|
2870
|
+
default:
|
|
2871
|
+
return value;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
// src/format.ts
|
|
2490
2876
|
var isShard = (file) => SHARD_EXTENSIONS.some((ext) => file.endsWith(ext));
|
|
2491
2877
|
function expand(path) {
|
|
2492
2878
|
let dir = false;
|
|
@@ -2495,21 +2881,43 @@ function expand(path) {
|
|
|
2495
2881
|
} catch {
|
|
2496
2882
|
}
|
|
2497
2883
|
if (!dir) return [path];
|
|
2498
|
-
const root =
|
|
2884
|
+
const root = dirname6(findProjectFile(path));
|
|
2499
2885
|
return SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).sort();
|
|
2500
2886
|
}
|
|
2887
|
+
function upgradesFor(paths) {
|
|
2888
|
+
const content = /* @__PURE__ */ new Map(), notes = /* @__PURE__ */ new Map();
|
|
2889
|
+
for (const path of paths) {
|
|
2890
|
+
try {
|
|
2891
|
+
if (!statSync3(path).isDirectory()) continue;
|
|
2892
|
+
} catch {
|
|
2893
|
+
continue;
|
|
2894
|
+
}
|
|
2895
|
+
let loaded;
|
|
2896
|
+
try {
|
|
2897
|
+
loaded = loadProject(path);
|
|
2898
|
+
} catch {
|
|
2899
|
+
continue;
|
|
2900
|
+
}
|
|
2901
|
+
const plan = planExtensionUpgrades(loaded);
|
|
2902
|
+
for (const [file, text] of plan.writes) content.set(resolve5(file), text);
|
|
2903
|
+
for (const [file, lines] of plan.notes) notes.set(resolve5(file), [...notes.get(resolve5(file)) ?? [], ...lines]);
|
|
2904
|
+
}
|
|
2905
|
+
return { content, notes };
|
|
2906
|
+
}
|
|
2501
2907
|
function runFormat(paths) {
|
|
2908
|
+
const upgrades = upgradesFor(paths);
|
|
2502
2909
|
return paths.flatMap(expand).map((file) => {
|
|
2503
2910
|
if (!isShard(file)) return { file, changed: false, skipped: true };
|
|
2504
|
-
const original =
|
|
2911
|
+
const original = readFileSync8(file, "utf8");
|
|
2912
|
+
const notes = upgrades.notes.get(resolve5(file));
|
|
2505
2913
|
let canonical;
|
|
2506
2914
|
try {
|
|
2507
|
-
canonical =
|
|
2915
|
+
canonical = upgrades.content.get(resolve5(file)) ?? canonicalStringify6(parseSource6(original));
|
|
2508
2916
|
} catch (e) {
|
|
2509
2917
|
throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2510
2918
|
}
|
|
2511
2919
|
const changed = canonical !== original;
|
|
2512
|
-
return
|
|
2920
|
+
return { file, changed, ...changed ? { write: { path: file, content: canonical } } : {}, ...notes ? { notes } : {} };
|
|
2513
2921
|
});
|
|
2514
2922
|
}
|
|
2515
2923
|
|
|
@@ -2904,8 +3312,8 @@ async function runReportXlsx(data) {
|
|
|
2904
3312
|
}
|
|
2905
3313
|
|
|
2906
3314
|
// src/localisation.ts
|
|
2907
|
-
import { basename as basename5, join as
|
|
2908
|
-
import { canonicalStringify as
|
|
3315
|
+
import { basename as basename5, join as join8, sep as sep4 } from "path";
|
|
3316
|
+
import { canonicalStringify as canonicalStringify7 } from "@patterkit/core";
|
|
2909
3317
|
import {
|
|
2910
3318
|
walkNodes as walkNodes2,
|
|
2911
3319
|
castStringKey,
|
|
@@ -3071,23 +3479,23 @@ function targetLocPath(loaded, scene, locale, defaultLocale) {
|
|
|
3071
3479
|
if (existing) return existing;
|
|
3072
3480
|
const fromDefault = existingLocPath(loaded, scene, defaultLocale);
|
|
3073
3481
|
if (fromDefault) {
|
|
3074
|
-
const parts = fromDefault.split(
|
|
3482
|
+
const parts = fromDefault.split(sep4);
|
|
3075
3483
|
for (let i = parts.length - 2; i >= 0; i--) {
|
|
3076
3484
|
if (parts[i] === defaultLocale) {
|
|
3077
3485
|
parts[i] = locale;
|
|
3078
|
-
return parts.join(
|
|
3486
|
+
return parts.join(sep4);
|
|
3079
3487
|
}
|
|
3080
3488
|
}
|
|
3081
3489
|
}
|
|
3082
3490
|
const stem = scene === PROJECT_LOCALE_SCENE2 ? "_project" : scene;
|
|
3083
|
-
return
|
|
3491
|
+
return join8(loaded.root, layoutOf(loaded).strings, locale, `${stem}.patterloc`);
|
|
3084
3492
|
}
|
|
3085
3493
|
function authoringPath(loaded, scene) {
|
|
3086
|
-
const dir =
|
|
3087
|
-
if (scene === PROJECT_LOCALE_SCENE2) return
|
|
3494
|
+
const dir = join8(loaded.root, layoutOf(loaded).authoring);
|
|
3495
|
+
if (scene === PROJECT_LOCALE_SCENE2) return join8(dir, "_project.patterx");
|
|
3088
3496
|
const flow = loaded.sceneFiles[scene];
|
|
3089
3497
|
const stem = flow ? basename5(flow).replace(/\.patterflow$/, "") : scene;
|
|
3090
|
-
return
|
|
3498
|
+
return join8(dir, `${stem}.patterx`);
|
|
3091
3499
|
}
|
|
3092
3500
|
var findFile = (files, items, path) => {
|
|
3093
3501
|
const i = files.indexOf(path);
|
|
@@ -3125,7 +3533,7 @@ function applyLoc(loaded, catalog, opts = {}) {
|
|
|
3125
3533
|
merged[id] = text;
|
|
3126
3534
|
}
|
|
3127
3535
|
const file = { schema: existing?.schema ?? STRINGS_SCHEMA, scene, locale, strings: merged };
|
|
3128
|
-
writes.push({ path, content:
|
|
3536
|
+
writes.push({ path, content: canonicalStringify7(file) });
|
|
3129
3537
|
const fresh = bySceneFresh.get(scene);
|
|
3130
3538
|
if (!fresh?.size) continue;
|
|
3131
3539
|
const aPath = authoringPath(loaded, scene);
|
|
@@ -3134,7 +3542,7 @@ function applyLoc(loaded, catalog, opts = {}) {
|
|
|
3134
3542
|
const editsMap = { ...authoring.edits ?? {} };
|
|
3135
3543
|
for (const id of fresh) editsMap[id] = { ...editsMap[id], localisedAt: { ...editsMap[id]?.localisedAt, [locale]: now } };
|
|
3136
3544
|
authoring.edits = editsMap;
|
|
3137
|
-
writes.push({ path: aPath, content:
|
|
3545
|
+
writes.push({ path: aPath, content: canonicalStringify7(authoring) });
|
|
3138
3546
|
}
|
|
3139
3547
|
return { writes, stats: { updated, files: bySceneText.size } };
|
|
3140
3548
|
}
|
|
@@ -3820,9 +4228,9 @@ async function scriptToDocx(doc) {
|
|
|
3820
4228
|
import PDFDocument from "pdfkit";
|
|
3821
4229
|
|
|
3822
4230
|
// src/script-fonts.ts
|
|
3823
|
-
import { existsSync as existsSync5, readdirSync as
|
|
4231
|
+
import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
|
|
3824
4232
|
import { createRequire } from "module";
|
|
3825
|
-
import { join as
|
|
4233
|
+
import { join as join9 } from "path";
|
|
3826
4234
|
|
|
3827
4235
|
// src/script-fonts.json
|
|
3828
4236
|
var script_fonts_default = {
|
|
@@ -3909,18 +4317,18 @@ function useScriptFontFiles(paths, licencePaths = {}) {
|
|
|
3909
4317
|
cache.clear();
|
|
3910
4318
|
}
|
|
3911
4319
|
function scriptFontLicencePath(pkg) {
|
|
3912
|
-
const dir = (require2.resolve.paths(pkg) ?? []).map((p) =>
|
|
3913
|
-
const file = dir &&
|
|
4320
|
+
const dir = (require2.resolve.paths(pkg) ?? []).map((p) => join9(p, pkg)).find((d) => existsSync5(d));
|
|
4321
|
+
const file = dir && readdirSync5(dir).find((f) => /^(licen[cs]e|ofl)/i.test(f));
|
|
3914
4322
|
if (!dir || !file) throw new Error(`no licence file found for ${pkg}`);
|
|
3915
|
-
return
|
|
4323
|
+
return join9(dir, file);
|
|
3916
4324
|
}
|
|
3917
4325
|
function scriptFontLicences() {
|
|
3918
|
-
return SCRIPT_FONT_PACKAGES.map((pkg) => ({ package: pkg, text:
|
|
4326
|
+
return SCRIPT_FONT_PACKAGES.map((pkg) => ({ package: pkg, text: readFileSync9(licences[pkg] ?? scriptFontLicencePath(pkg), "utf8") }));
|
|
3919
4327
|
}
|
|
3920
4328
|
function scriptFontFiles(face) {
|
|
3921
4329
|
return script_fonts_default[face].map((spec) => {
|
|
3922
4330
|
let bytes = cache.get(spec);
|
|
3923
|
-
if (!bytes) cache.set(spec, bytes =
|
|
4331
|
+
if (!bytes) cache.set(spec, bytes = readFileSync9(located[spec] ?? require2.resolve(spec)));
|
|
3924
4332
|
return bytes;
|
|
3925
4333
|
});
|
|
3926
4334
|
}
|
|
@@ -3960,7 +4368,7 @@ var ROLES = {
|
|
|
3960
4368
|
"Mono": "mono"
|
|
3961
4369
|
};
|
|
3962
4370
|
async function scriptToPdf(doc) {
|
|
3963
|
-
return new Promise((
|
|
4371
|
+
return new Promise((resolve8, reject) => {
|
|
3964
4372
|
const pdf = new PDFDocument({ size: "A4", margins: { top: MARGIN, bottom: MARGIN, left: MARGIN, right: MARGIN }, info: { Title: doc.project, Author: "Patter" }, font: null });
|
|
3965
4373
|
const register = (prefix, face, firstIsPrefix) => scriptFontFiles(face).map((bytes, i) => {
|
|
3966
4374
|
const name = firstIsPrefix && i === 0 ? prefix : `${prefix}~${i}`;
|
|
@@ -4004,7 +4412,7 @@ async function scriptToPdf(doc) {
|
|
|
4004
4412
|
const bodyPieces = (runs, color, base = {}) => runs.flatMap((r) => r.code ? split(r.text, "Mono", ACCENT) : split(r.text, SERIF2(base.bold || r.bold, base.italic || r.italic), color));
|
|
4005
4413
|
const chunks = [];
|
|
4006
4414
|
pdf.on("data", (c) => chunks.push(c));
|
|
4007
|
-
pdf.on("end", () =>
|
|
4415
|
+
pdf.on("end", () => resolve8(Buffer.concat(chunks)));
|
|
4008
4416
|
pdf.on("error", reject);
|
|
4009
4417
|
const pageRight = pdf.page.width - MARGIN;
|
|
4010
4418
|
const pageBottom = pdf.page.height - MARGIN;
|
|
@@ -4156,17 +4564,17 @@ async function scriptToPdf(doc) {
|
|
|
4156
4564
|
}
|
|
4157
4565
|
|
|
4158
4566
|
// src/init.ts
|
|
4159
|
-
import { join as
|
|
4160
|
-
import { readdirSync as
|
|
4161
|
-
import { newId, slug, canonicalStringify as
|
|
4567
|
+
import { join as join10, basename as basename6, resolve as resolve6 } from "path";
|
|
4568
|
+
import { readdirSync as readdirSync6, existsSync as existsSync6, readFileSync as readFileSync10 } from "fs";
|
|
4569
|
+
import { newId, slug, canonicalStringify as canonicalStringify8 } from "@patterkit/core";
|
|
4162
4570
|
import { FLOW_SCHEMA, PROJECT_SCHEMA, STRINGS_SCHEMA as STRINGS_SCHEMA2 } from "@patterkit/model";
|
|
4163
4571
|
function runInit(opts) {
|
|
4164
|
-
const dir =
|
|
4572
|
+
const dir = resolve6(opts.dir);
|
|
4165
4573
|
const name = opts.name?.trim() || basename6(dir).replace(/\.patter$/, "");
|
|
4166
4574
|
const fileSlug = slug(name);
|
|
4167
4575
|
let existing = [];
|
|
4168
4576
|
try {
|
|
4169
|
-
existing =
|
|
4577
|
+
existing = readdirSync6(dir).filter((f) => f.endsWith(".patterproj"));
|
|
4170
4578
|
} catch {
|
|
4171
4579
|
}
|
|
4172
4580
|
if (existing.length > 0) throw new Error(`a project already exists here: ${existing[0]}`);
|
|
@@ -4210,13 +4618,13 @@ function runInit(opts) {
|
|
|
4210
4618
|
strings: { [beatId]: `Welcome to ${name}. This is the first line of your story - replace it with your own.` }
|
|
4211
4619
|
};
|
|
4212
4620
|
const bundle = opts.bundle ?? "commit";
|
|
4213
|
-
const projectFile =
|
|
4621
|
+
const projectFile = join10(dir, `${fileSlug}.patterproj`);
|
|
4214
4622
|
const writes = [
|
|
4215
|
-
{ path:
|
|
4216
|
-
{ path:
|
|
4217
|
-
{ path:
|
|
4623
|
+
{ path: join10(dir, "scenes", "start.patterflow"), content: canonicalStringify8(flow) },
|
|
4624
|
+
{ path: join10(dir, "loc", "en", "start.patterloc"), content: canonicalStringify8(locale) },
|
|
4625
|
+
{ path: join10(dir, ".editorconfig"), content: EDITORCONFIG },
|
|
4218
4626
|
...vcsConfigWrites(dir, opts.vcs, bundle),
|
|
4219
|
-
{ path: projectFile, content:
|
|
4627
|
+
{ path: projectFile, content: canonicalStringify8(project) }
|
|
4220
4628
|
];
|
|
4221
4629
|
const collisions = writes.map((w) => w.path).filter((p) => existsSync6(p));
|
|
4222
4630
|
if (collisions.length > 0) {
|
|
@@ -4225,16 +4633,16 @@ function runInit(opts) {
|
|
|
4225
4633
|
return { writes, projectFile, name };
|
|
4226
4634
|
}
|
|
4227
4635
|
function vcsConfigWrites(dir, vcs, bundle = "commit") {
|
|
4228
|
-
const writes = [{ path:
|
|
4229
|
-
if (vcs === "git") writes.push({ path:
|
|
4636
|
+
const writes = [{ path: join10(dir, "vcs-setup.md"), content: vcsSetup(vcs, bundle) }];
|
|
4637
|
+
if (vcs === "git") writes.push({ path: join10(dir, ".gitattributes"), content: GITATTRIBUTES });
|
|
4230
4638
|
const ignore = ignoreFileFor(vcs);
|
|
4231
|
-
if (ignore) writes.push({ path:
|
|
4639
|
+
if (ignore) writes.push({ path: join10(dir, ignore), content: ignoreContent(bundle) });
|
|
4232
4640
|
return writes;
|
|
4233
4641
|
}
|
|
4234
4642
|
function currentBundlePosture(dir, vcs) {
|
|
4235
4643
|
const read = (name) => {
|
|
4236
4644
|
try {
|
|
4237
|
-
return
|
|
4645
|
+
return readFileSync10(join10(dir, name), "utf8");
|
|
4238
4646
|
} catch {
|
|
4239
4647
|
return "";
|
|
4240
4648
|
}
|
|
@@ -4251,7 +4659,7 @@ charset = utf-8
|
|
|
4251
4659
|
end_of_line = lf
|
|
4252
4660
|
insert_final_newline = true
|
|
4253
4661
|
|
|
4254
|
-
[*.{patterflow,patterloc,patterx,patterproj}]
|
|
4662
|
+
[*.{patterflow,patterloc,patterx,patterproj,patterextension}]
|
|
4255
4663
|
indent_style = space
|
|
4256
4664
|
indent_size = 2
|
|
4257
4665
|
`;
|
|
@@ -4260,6 +4668,7 @@ var GITATTRIBUTES = `# Patter source is UTF-8 + LF text (pinned; never let autoc
|
|
|
4260
4668
|
*.patterloc text eol=lf
|
|
4261
4669
|
*.patterx text eol=lf
|
|
4262
4670
|
*.patterproj text eol=lf
|
|
4671
|
+
*.patterextension text eol=lf
|
|
4263
4672
|
|
|
4264
4673
|
# Id-keyed structured merge for Patter source (the 'patter merge' driver).
|
|
4265
4674
|
# Register it once per clone (see vcs-setup.md); until then git falls back to a
|
|
@@ -4268,6 +4677,11 @@ var GITATTRIBUTES = `# Patter source is UTF-8 + LF text (pinned; never let autoc
|
|
|
4268
4677
|
*.patterloc merge=patter
|
|
4269
4678
|
*.patterx merge=patter
|
|
4270
4679
|
*.patterproj merge=patter
|
|
4680
|
+
*.patterextension merge=patter
|
|
4681
|
+
|
|
4682
|
+
# An extension's manifest is the extension's own JSON, kept in the project so
|
|
4683
|
+
# every machine reads the same one, and never edited here: text, merged as text.
|
|
4684
|
+
extensions/*.extension.json text eol=lf
|
|
4271
4685
|
|
|
4272
4686
|
# Generated artifacts (spec \xA711). The compiled bundle is committed but
|
|
4273
4687
|
# REGENERATED, never hand-merged - keep ours on conflict and rebuild (the
|
|
@@ -4294,7 +4708,9 @@ function ignoreContent(bundle) {
|
|
|
4294
4708
|
"# Patter generated artifacts - not source (see vcs-setup.md).",
|
|
4295
4709
|
"# Packed document (the send-and-return envelope):",
|
|
4296
4710
|
"*.patterpack",
|
|
4297
|
-
"# Unresolved merge sidecar
|
|
4711
|
+
"# Unresolved merge sidecar (a lingering one is a validate error). The merge also names its clashes",
|
|
4712
|
+
'# in the shard itself, under "unresolved", which IS committed with the file, so CI refuses an',
|
|
4713
|
+
"# unresolved merge wherever it goes:",
|
|
4298
4714
|
"*.patterconflict"
|
|
4299
4715
|
];
|
|
4300
4716
|
if (bundle === "ignore") {
|
|
@@ -4338,6 +4754,8 @@ compiled bundle are TEXT with LF; the packed document is BINARY:
|
|
|
4338
4754
|
text //....patterloc
|
|
4339
4755
|
text //....patterx
|
|
4340
4756
|
text //....patterproj
|
|
4757
|
+
text //....patterextension
|
|
4758
|
+
text //....extension.json
|
|
4341
4759
|
text //....patterc
|
|
4342
4760
|
binary //....patterpack
|
|
4343
4761
|
|
|
@@ -4376,6 +4794,8 @@ Set auto-props so Patter source keeps LF (\`~/.subversion/config\` or repo confi
|
|
|
4376
4794
|
*.patterloc = svn:eol-style=LF
|
|
4377
4795
|
*.patterx = svn:eol-style=LF
|
|
4378
4796
|
*.patterproj = svn:eol-style=LF
|
|
4797
|
+
*.patterextension = svn:eol-style=LF
|
|
4798
|
+
*.extension.json = svn:eol-style=LF
|
|
4379
4799
|
*.patterc = svn:eol-style=LF
|
|
4380
4800
|
*.patterpack = svn:mime-type=application/octet-stream
|
|
4381
4801
|
|
|
@@ -4663,7 +5083,7 @@ function buildIndex(loaded) {
|
|
|
4663
5083
|
}
|
|
4664
5084
|
|
|
4665
5085
|
// src/replace.ts
|
|
4666
|
-
import { canonicalStringify as
|
|
5086
|
+
import { canonicalStringify as canonicalStringify9 } from "@patterkit/core";
|
|
4667
5087
|
import { walkNodes as walkNodes4, PROJECT_LOCALE_SCENE as PROJECT_LOCALE_SCENE3, AUTHORING_SCHEMA as AUTHORING_SCHEMA2 } from "@patterkit/model";
|
|
4668
5088
|
import { findMatcher } from "@wildwinter/toolkit";
|
|
4669
5089
|
function runReplace(loaded, opts) {
|
|
@@ -4707,7 +5127,7 @@ function runReplace(loaded, opts) {
|
|
|
4707
5127
|
}
|
|
4708
5128
|
if (changedIds.length) {
|
|
4709
5129
|
const next = { ...shard, strings: merged };
|
|
4710
|
-
plan.writes.push({ path, content:
|
|
5130
|
+
plan.writes.push({ path, content: canonicalStringify9(next) });
|
|
4711
5131
|
plan.shards.push(next);
|
|
4712
5132
|
plan.scenes++;
|
|
4713
5133
|
const aPath = authoringPath(loaded, shard.scene);
|
|
@@ -4724,89 +5144,19 @@ function runReplace(loaded, opts) {
|
|
|
4724
5144
|
}
|
|
4725
5145
|
for (const [path, file] of stamped) {
|
|
4726
5146
|
plan.authoring.push({ path, file });
|
|
4727
|
-
plan.writes.push({ path, content:
|
|
5147
|
+
plan.writes.push({ path, content: canonicalStringify9(file) });
|
|
4728
5148
|
}
|
|
4729
5149
|
return plan;
|
|
4730
5150
|
}
|
|
4731
5151
|
|
|
4732
5152
|
// src/stages.ts
|
|
4733
|
-
import { parse,
|
|
5153
|
+
import { parse, renameStageInExpression, stageGateCount as gatesIn } from "@wildwinter/expr";
|
|
4734
5154
|
import { dialectWithForeignScopes } from "@patterkit/dialect";
|
|
4735
5155
|
import { hostScopesToSpec as hostScopesToSpec2, projectScopes as projectScopes2 } from "@patterkit/compiler";
|
|
4736
5156
|
import { walkNodes as walkNodes5 } from "@patterkit/model";
|
|
4737
|
-
var COMPARISONS = /* @__PURE__ */ new Set(["==", "!=", "<", "<=", ">", ">="]);
|
|
4738
|
-
var ORDERING = /* @__PURE__ */ new Set(["<", "<=", ">", ">="]);
|
|
4739
5157
|
function stageDialect(project, gameScopes) {
|
|
4740
5158
|
return dialectWithForeignScopes(hostScopesToSpec2(projectScopes2(project, gameScopes?.merged).host));
|
|
4741
5159
|
}
|
|
4742
|
-
function renameStageInExpression(src, dialect, isTarget, from, to, assigned = false) {
|
|
4743
|
-
let ast;
|
|
4744
|
-
try {
|
|
4745
|
-
ast = parse(src, dialect);
|
|
4746
|
-
} catch {
|
|
4747
|
-
return void 0;
|
|
4748
|
-
}
|
|
4749
|
-
let hits = 0;
|
|
4750
|
-
const isRef = (n) => n.kind === "scopedvar" && isTarget(n.scope, n.name);
|
|
4751
|
-
const walk = (n) => {
|
|
4752
|
-
switch (n.kind) {
|
|
4753
|
-
case "binary": {
|
|
4754
|
-
let left = walk(n.left);
|
|
4755
|
-
let right = walk(n.right);
|
|
4756
|
-
if (COMPARISONS.has(n.op)) {
|
|
4757
|
-
if (isRef(left) && right.kind === "string" && right.value === from) {
|
|
4758
|
-
right = { kind: "string", value: to };
|
|
4759
|
-
hits++;
|
|
4760
|
-
}
|
|
4761
|
-
if (isRef(right) && left.kind === "string" && left.value === from) {
|
|
4762
|
-
left = { kind: "string", value: to };
|
|
4763
|
-
hits++;
|
|
4764
|
-
}
|
|
4765
|
-
}
|
|
4766
|
-
return { ...n, left, right };
|
|
4767
|
-
}
|
|
4768
|
-
case "unary":
|
|
4769
|
-
return { ...n, operand: walk(n.operand) };
|
|
4770
|
-
case "call":
|
|
4771
|
-
return { ...n, args: n.args.map(walk) };
|
|
4772
|
-
default:
|
|
4773
|
-
return n;
|
|
4774
|
-
}
|
|
4775
|
-
};
|
|
4776
|
-
let next = walk(ast);
|
|
4777
|
-
if (assigned && next.kind === "string" && next.value === from) {
|
|
4778
|
-
next = { kind: "string", value: to };
|
|
4779
|
-
hits++;
|
|
4780
|
-
}
|
|
4781
|
-
if (hits === 0) return void 0;
|
|
4782
|
-
const wanted = JSON.stringify(next);
|
|
4783
|
-
const esc2 = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4784
|
-
const quotedTo = (q) => `${q}${to.replace(/\\/g, "\\\\").replace(new RegExp(q, "g"), `\\${q}`)}${q}`;
|
|
4785
|
-
const found = [...src.matchAll(new RegExp(`(["'])${esc2}\\1`, "g"))];
|
|
4786
|
-
const says = (text) => {
|
|
4787
|
-
try {
|
|
4788
|
-
return JSON.stringify(parse(text, dialect)) === wanted;
|
|
4789
|
-
} catch {
|
|
4790
|
-
return false;
|
|
4791
|
-
}
|
|
4792
|
-
};
|
|
4793
|
-
const replaced = (mask) => {
|
|
4794
|
-
let text = "";
|
|
4795
|
-
let at = 0;
|
|
4796
|
-
found.forEach((m, i) => {
|
|
4797
|
-
if (!(mask & 1 << i)) return;
|
|
4798
|
-
text += src.slice(at, m.index) + quotedTo(m[1]);
|
|
4799
|
-
at = m.index + m[0].length;
|
|
4800
|
-
});
|
|
4801
|
-
return text + src.slice(at);
|
|
4802
|
-
};
|
|
4803
|
-
if (found.length > 0 && found.length <= 8) {
|
|
4804
|
-
const all = (1 << found.length) - 1;
|
|
4805
|
-
if (says(replaced(all))) return replaced(all);
|
|
4806
|
-
for (let mask = all - 1; mask > 0; mask--) if (says(replaced(mask))) return replaced(mask);
|
|
4807
|
-
}
|
|
4808
|
-
return unparse(next, { defaultScope: dialect.defaultScope });
|
|
4809
|
-
}
|
|
4810
5160
|
var refersTo = (target) => (scope, name) => scope === target.scope && name === target.name;
|
|
4811
5161
|
function effectTarget(ref, dialect) {
|
|
4812
5162
|
try {
|
|
@@ -4864,38 +5214,48 @@ function renameStage(scenes, target, from, to, dialect) {
|
|
|
4864
5214
|
}
|
|
4865
5215
|
function stageGateCount(scenes, target, dialect) {
|
|
4866
5216
|
const isTarget = refersTo(target);
|
|
4867
|
-
const orders = (n) => {
|
|
4868
|
-
switch (n.kind) {
|
|
4869
|
-
case "binary": {
|
|
4870
|
-
const ref = (m) => m.kind === "scopedvar" && isTarget(m.scope, m.name);
|
|
4871
|
-
if (ORDERING.has(n.op) && (ref(n.left) || ref(n.right))) return true;
|
|
4872
|
-
return orders(n.left) || orders(n.right);
|
|
4873
|
-
}
|
|
4874
|
-
case "unary":
|
|
4875
|
-
return orders(n.operand);
|
|
4876
|
-
case "call":
|
|
4877
|
-
return n.args.some(orders);
|
|
4878
|
-
default:
|
|
4879
|
-
return false;
|
|
4880
|
-
}
|
|
4881
|
-
};
|
|
4882
5217
|
let count = 0;
|
|
4883
5218
|
for (const scene of reach(scenes, target)) {
|
|
4884
5219
|
for (const site of sceneSites(scene)) {
|
|
4885
5220
|
const src = site.get();
|
|
4886
|
-
if (src
|
|
4887
|
-
try {
|
|
4888
|
-
if (orders(parse(src, dialect))) count++;
|
|
4889
|
-
} catch {
|
|
4890
|
-
}
|
|
5221
|
+
if (src !== void 0) count += gatesIn(src, dialect, isTarget);
|
|
4891
5222
|
}
|
|
4892
5223
|
}
|
|
4893
5224
|
return count;
|
|
4894
5225
|
}
|
|
5226
|
+
function renameGameDataStage(scenes, kind, field, from, to) {
|
|
5227
|
+
const result = { scenes: [], values: 0 };
|
|
5228
|
+
if (to.trim() === "" || from === to) return result;
|
|
5229
|
+
for (const scene of scenes) {
|
|
5230
|
+
let changed = false;
|
|
5231
|
+
const visit = (holder) => {
|
|
5232
|
+
if (holder.gameData?.[field] !== from) return;
|
|
5233
|
+
holder.gameData[field] = to;
|
|
5234
|
+
result.values++;
|
|
5235
|
+
changed = true;
|
|
5236
|
+
};
|
|
5237
|
+
if (kind === "scene") visit(scene);
|
|
5238
|
+
for (const block of scene.blocks) {
|
|
5239
|
+
if (kind === "block") visit(block);
|
|
5240
|
+
walkNodes5(block.children, (node) => {
|
|
5241
|
+
if (node.type !== "snippet") {
|
|
5242
|
+
if (kind === "option" && node.selector === "choice") {
|
|
5243
|
+
for (const option of node.children) if (option.type === "group") visit(option);
|
|
5244
|
+
}
|
|
5245
|
+
return;
|
|
5246
|
+
}
|
|
5247
|
+
if (kind === "snippet") visit(node);
|
|
5248
|
+
for (const beat of node.beats ?? []) if (beat.kind === kind) visit(beat);
|
|
5249
|
+
});
|
|
5250
|
+
}
|
|
5251
|
+
if (changed) result.scenes.push(scene);
|
|
5252
|
+
}
|
|
5253
|
+
return result;
|
|
5254
|
+
}
|
|
4895
5255
|
|
|
4896
5256
|
// src/pin.ts
|
|
4897
|
-
import { readFileSync as
|
|
4898
|
-
import { canonicalStringify as
|
|
5257
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
5258
|
+
import { canonicalStringify as canonicalStringify10, parseSource as parseSource7 } from "@patterkit/core";
|
|
4899
5259
|
import { effectiveGameId as effectiveGameId3, gameIdify } from "@patterkit/model";
|
|
4900
5260
|
function toPin(node) {
|
|
4901
5261
|
if (node.gameId?.trim()) return void 0;
|
|
@@ -4922,8 +5282,8 @@ function planPins(loaded) {
|
|
|
4922
5282
|
});
|
|
4923
5283
|
if (!changed) continue;
|
|
4924
5284
|
const next = { ...scene, ...sceneGameId !== void 0 ? { gameId: sceneGameId } : {}, blocks };
|
|
4925
|
-
const file =
|
|
4926
|
-
plan.writes.push({ path, content:
|
|
5285
|
+
const file = parseSource7(readFileSync11(path, "utf8"));
|
|
5286
|
+
plan.writes.push({ path, content: canonicalStringify10({ ...file, scene: next }) });
|
|
4927
5287
|
plan.scenes.push(next);
|
|
4928
5288
|
}
|
|
4929
5289
|
return plan;
|
|
@@ -4931,9 +5291,9 @@ function planPins(loaded) {
|
|
|
4931
5291
|
|
|
4932
5292
|
// src/unpack.ts
|
|
4933
5293
|
import JSZip2 from "jszip";
|
|
4934
|
-
import { join as
|
|
4935
|
-
import { existsSync as existsSync7, readFileSync as
|
|
4936
|
-
import { parseSource as
|
|
5294
|
+
import { join as join11, resolve as resolve7, sep as sep5 } from "path";
|
|
5295
|
+
import { existsSync as existsSync7, readFileSync as readFileSync12, readdirSync as readdirSync7 } from "fs";
|
|
5296
|
+
import { parseSource as parseSource8, canonicalStringify as canonicalStringify11 } from "@patterkit/core";
|
|
4937
5297
|
import { isUnsafeEntry } from "@wildwinter/toolkit/archive";
|
|
4938
5298
|
import { SCOPES_FILE_SUFFIX as SCOPES_FILE_SUFFIX2 } from "@wildwinter/scoperegistry/scopes";
|
|
4939
5299
|
import { isUnsafeEntry as isUnsafeEntry2 } from "@wildwinter/toolkit/archive";
|
|
@@ -4950,7 +5310,7 @@ var UnsafeEntryError = class extends Error {
|
|
|
4950
5310
|
};
|
|
4951
5311
|
var NotAPackError = class extends Error {
|
|
4952
5312
|
};
|
|
4953
|
-
var isShardEntry = (name) => SHARD_EXTENSIONS.some((ext) => name.endsWith(ext));
|
|
5313
|
+
var isShardEntry = (name) => SHARD_EXTENSIONS.some((ext) => name.endsWith(ext)) || isManifestEntry(name);
|
|
4954
5314
|
function refuseUnsafe(name) {
|
|
4955
5315
|
if (isUnsafeEntry(name)) throw new UnsafeEntryError(`document entry escapes the target directory: ${name}`);
|
|
4956
5316
|
if (name.split("/").some((segment) => segment.startsWith("."))) {
|
|
@@ -5000,13 +5360,13 @@ async function runUnpack(bytes, targetDir) {
|
|
|
5000
5360
|
function projectIdIn(dir) {
|
|
5001
5361
|
let names;
|
|
5002
5362
|
try {
|
|
5003
|
-
names =
|
|
5363
|
+
names = readdirSync7(dir).filter((n) => n.endsWith(".patterproj"));
|
|
5004
5364
|
} catch {
|
|
5005
5365
|
return void 0;
|
|
5006
5366
|
}
|
|
5007
5367
|
for (const n of names) {
|
|
5008
5368
|
try {
|
|
5009
|
-
const id =
|
|
5369
|
+
const id = parseSource8(readFileSync12(join11(dir, n), "utf8")).project?.id;
|
|
5010
5370
|
if (id) return id;
|
|
5011
5371
|
} catch {
|
|
5012
5372
|
}
|
|
@@ -5017,7 +5377,7 @@ function projectIdOf(shards) {
|
|
|
5017
5377
|
const rel = projectEntry(shards);
|
|
5018
5378
|
if (rel === void 0) return void 0;
|
|
5019
5379
|
try {
|
|
5020
|
-
return
|
|
5380
|
+
return parseSource8(shards.get(rel)).project?.id;
|
|
5021
5381
|
} catch {
|
|
5022
5382
|
return void 0;
|
|
5023
5383
|
}
|
|
@@ -5033,7 +5393,7 @@ function checkProvenance(returned, base, target) {
|
|
|
5033
5393
|
}
|
|
5034
5394
|
function targetProjectId(projectDir) {
|
|
5035
5395
|
try {
|
|
5036
|
-
const pf =
|
|
5396
|
+
const pf = parseSource8(readFileSync12(findProjectFile(projectDir), "utf8"));
|
|
5037
5397
|
return pf.project?.id;
|
|
5038
5398
|
} catch {
|
|
5039
5399
|
return void 0;
|
|
@@ -5051,6 +5411,8 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5051
5411
|
let conflicts = 0, warnings = 0;
|
|
5052
5412
|
const projectRel = projectEntry(theirs);
|
|
5053
5413
|
let projectSides;
|
|
5414
|
+
const manifests = projectManifests(projectDir);
|
|
5415
|
+
const manifestsKept = [];
|
|
5054
5416
|
for (const [rel, theirText] of [...theirs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
5055
5417
|
const outPath = containedWrite(projectDir, rel);
|
|
5056
5418
|
const baseText = base.get(rel);
|
|
@@ -5060,10 +5422,15 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5060
5422
|
shards.push({ path: rel, added: true, changed: true });
|
|
5061
5423
|
continue;
|
|
5062
5424
|
}
|
|
5063
|
-
const oursText =
|
|
5425
|
+
const oursText = readFileSync12(outPath, "utf8");
|
|
5426
|
+
if (isManifestEntry(rel)) {
|
|
5427
|
+
if (theirText !== oursText) manifestsKept.push(rel);
|
|
5428
|
+
shards.push({ path: rel, added: false, changed: false });
|
|
5429
|
+
continue;
|
|
5430
|
+
}
|
|
5064
5431
|
if (theirText === baseText || theirText === oursText) {
|
|
5065
5432
|
if (rel === projectRel && baseText !== void 0) {
|
|
5066
|
-
const read = (t) =>
|
|
5433
|
+
const read = (t) => parseSource8(t);
|
|
5067
5434
|
const ours = read(oursText);
|
|
5068
5435
|
projectSides = { ours, base: read(baseText), theirs: read(theirText), merged: ours };
|
|
5069
5436
|
}
|
|
@@ -5072,7 +5439,7 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5072
5439
|
}
|
|
5073
5440
|
const readSide = (side, text) => {
|
|
5074
5441
|
try {
|
|
5075
|
-
return
|
|
5442
|
+
return parseSource8(text);
|
|
5076
5443
|
} catch (e) {
|
|
5077
5444
|
throw new Error(`${rel}: the ${side} copy is not readable Patter source - ${e instanceof Error ? e.message : String(e)}`);
|
|
5078
5445
|
}
|
|
@@ -5080,8 +5447,9 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5080
5447
|
const oursObj = readSide("ours", oursText);
|
|
5081
5448
|
const theirsObj = readSide("theirs", theirText);
|
|
5082
5449
|
const baseObj = baseText !== void 0 ? readSide("base", baseText) : {};
|
|
5083
|
-
const
|
|
5084
|
-
const
|
|
5450
|
+
const manifest = detectMergeType(oursObj) === "extension" && typeof oursObj["extension"] === "string" ? manifests(oursObj["extension"]) : void 0;
|
|
5451
|
+
const result = runMerge(baseObj, oursObj, theirsObj, manifest !== void 0 ? { manifest } : void 0);
|
|
5452
|
+
const mergedText = canonicalStringify11(result.merged);
|
|
5085
5453
|
const changed = mergedText !== oursText;
|
|
5086
5454
|
if (changed) writes.push({ path: outPath, content: mergedText });
|
|
5087
5455
|
if (rel === projectRel && baseText !== void 0) {
|
|
@@ -5095,13 +5463,13 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5095
5463
|
shards.push({ path: rel, result, added: false, changed });
|
|
5096
5464
|
}
|
|
5097
5465
|
let gameScopes;
|
|
5098
|
-
const local2 = projectSides && discoverGameScopes(
|
|
5466
|
+
const local2 = projectSides && discoverGameScopes(resolve7(projectDir), projectSides.ours).gameScopes;
|
|
5099
5467
|
if (projectSides && local2) {
|
|
5100
5468
|
const world = planReturnedWorld(projectSides.ours, projectSides.base, projectSides.theirs, projectSides.merged, local2);
|
|
5101
5469
|
if (world.write) {
|
|
5102
5470
|
writes.push(world.write);
|
|
5103
5471
|
gameScopes = { path: world.write.path };
|
|
5104
|
-
} else if (world.error) gameScopes = { path:
|
|
5472
|
+
} else if (world.error) gameScopes = { path: join11(local2.dir, GAME_SCOPES_FILE), error: world.error };
|
|
5105
5473
|
}
|
|
5106
5474
|
const handoffs = [];
|
|
5107
5475
|
for (const [rel, theirText] of [...returnedDoc.handoffs.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
@@ -5111,13 +5479,24 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
5111
5479
|
handoffs.push(rel);
|
|
5112
5480
|
continue;
|
|
5113
5481
|
}
|
|
5114
|
-
const merged = mergeHandoff(parseHandoff(
|
|
5482
|
+
const merged = mergeHandoff(parseHandoff(readFileSync12(outPath, "utf8")), parseHandoff(theirText));
|
|
5115
5483
|
if (merged) {
|
|
5116
5484
|
writes.push({ path: outPath, content: serialiseHandoff(merged) });
|
|
5117
5485
|
handoffs.push(rel);
|
|
5118
5486
|
}
|
|
5119
5487
|
}
|
|
5120
|
-
return {
|
|
5488
|
+
return {
|
|
5489
|
+
shards,
|
|
5490
|
+
writes,
|
|
5491
|
+
sidecars,
|
|
5492
|
+
other: returnedDoc.other,
|
|
5493
|
+
conflicts,
|
|
5494
|
+
warnings,
|
|
5495
|
+
provenance,
|
|
5496
|
+
...gameScopes ? { gameScopes } : {},
|
|
5497
|
+
...handoffs.length ? { handoffs } : {},
|
|
5498
|
+
...manifestsKept.length ? { manifestsKept } : {}
|
|
5499
|
+
};
|
|
5121
5500
|
}
|
|
5122
5501
|
function mergeHandoff(ours, theirs) {
|
|
5123
5502
|
const key = (i) => `${i.at}|${i.fileHash}`;
|
|
@@ -5133,18 +5512,76 @@ function projectEntry(shards) {
|
|
|
5133
5512
|
return at.length === 1 ? at[0] : void 0;
|
|
5134
5513
|
}
|
|
5135
5514
|
function containedWrite(dir, name) {
|
|
5136
|
-
const root =
|
|
5137
|
-
const full =
|
|
5138
|
-
if (full !== root && !full.startsWith(root +
|
|
5515
|
+
const root = resolve7(dir);
|
|
5516
|
+
const full = resolve7(join11(dir, name));
|
|
5517
|
+
if (full !== root && !full.startsWith(root + sep5)) {
|
|
5139
5518
|
throw new UnsafeEntryError(`document entry escapes the target directory: ${name}`);
|
|
5140
5519
|
}
|
|
5141
5520
|
return full;
|
|
5142
5521
|
}
|
|
5143
5522
|
|
|
5523
|
+
// src/cast-rename.ts
|
|
5524
|
+
import { castStringKey as castStringKey2, speakerLabelKey as speakerLabelKey2, walkNodes as walkNodes6 } from "@patterkit/model";
|
|
5525
|
+
function cleanCastRenames(renames) {
|
|
5526
|
+
const out = /* @__PURE__ */ new Map();
|
|
5527
|
+
for (const [from, to] of Object.entries(renames ?? {})) if (from && to && from !== to) out.set(from, to);
|
|
5528
|
+
return out.size ? out : null;
|
|
5529
|
+
}
|
|
5530
|
+
function renameCastInScene(scene, renames) {
|
|
5531
|
+
let n = 0;
|
|
5532
|
+
const visit = (beat) => {
|
|
5533
|
+
if (beat?.kind !== "line" || beat.character === void 0) return;
|
|
5534
|
+
const to = renames.get(beat.character);
|
|
5535
|
+
if (to !== void 0) {
|
|
5536
|
+
beat.character = to;
|
|
5537
|
+
n++;
|
|
5538
|
+
}
|
|
5539
|
+
};
|
|
5540
|
+
for (const block of scene.blocks ?? []) {
|
|
5541
|
+
walkNodes6(block.children, (node) => {
|
|
5542
|
+
if (node.type === "group") {
|
|
5543
|
+
visit(node.prompt);
|
|
5544
|
+
return;
|
|
5545
|
+
}
|
|
5546
|
+
for (const beat of node.beats ?? []) visit(beat);
|
|
5547
|
+
});
|
|
5548
|
+
}
|
|
5549
|
+
return n;
|
|
5550
|
+
}
|
|
5551
|
+
var CAST_PREFIX = castStringKey2("");
|
|
5552
|
+
var SPEAKER_PREFIX = speakerLabelKey2("", "").slice(0, -1);
|
|
5553
|
+
function renamedKey(key, renames) {
|
|
5554
|
+
if (key.startsWith(CAST_PREFIX)) {
|
|
5555
|
+
const to = renames.get(key.slice(CAST_PREFIX.length));
|
|
5556
|
+
return to !== void 0 ? castStringKey2(to) : void 0;
|
|
5557
|
+
}
|
|
5558
|
+
if (key.startsWith(SPEAKER_PREFIX)) {
|
|
5559
|
+
const at = key.lastIndexOf(":");
|
|
5560
|
+
if (at <= SPEAKER_PREFIX.length) return void 0;
|
|
5561
|
+
const to = renames.get(key.slice(SPEAKER_PREFIX.length, at));
|
|
5562
|
+
return to !== void 0 ? speakerLabelKey2(to, key.slice(at + 1)) : void 0;
|
|
5563
|
+
}
|
|
5564
|
+
return void 0;
|
|
5565
|
+
}
|
|
5566
|
+
function moveCastKeys(record, renames) {
|
|
5567
|
+
const keys = Object.keys(record);
|
|
5568
|
+
const finals = keys.map((k) => renamedKey(k, renames) ?? k);
|
|
5569
|
+
const count = /* @__PURE__ */ new Map();
|
|
5570
|
+
for (const f of finals) count.set(f, (count.get(f) ?? 0) + 1);
|
|
5571
|
+
if (finals.every((f, i) => f === keys[i] || count.get(f) > 1)) return false;
|
|
5572
|
+
const entries = keys.map((k, i) => [count.get(finals[i]) === 1 ? finals[i] : k, record[k]]);
|
|
5573
|
+
for (const k of keys) delete record[k];
|
|
5574
|
+
for (const [k, v] of entries) record[k] = v;
|
|
5575
|
+
return true;
|
|
5576
|
+
}
|
|
5577
|
+
function renameCastStrings(loc, renames) {
|
|
5578
|
+
return loc.strings ? moveCastKeys(loc.strings, renames) : false;
|
|
5579
|
+
}
|
|
5580
|
+
|
|
5144
5581
|
// src/suggestions.ts
|
|
5145
|
-
import { readFileSync as
|
|
5146
|
-
import { canonicalStringify as
|
|
5147
|
-
import { walkNodes as
|
|
5582
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
5583
|
+
import { canonicalStringify as canonicalStringify12, parseSource as parseSource9 } from "@patterkit/core";
|
|
5584
|
+
import { walkNodes as walkNodes7, AUTHORING_SCHEMA as AUTHORING_SCHEMA3 } from "@patterkit/model";
|
|
5148
5585
|
function staleParts(s, liveText, line) {
|
|
5149
5586
|
const stale = [];
|
|
5150
5587
|
if (liveText !== s.baseline) stale.push("the text");
|
|
@@ -5175,7 +5612,7 @@ function indexPlaces(loaded) {
|
|
|
5175
5612
|
places.set(scene.id, { sceneId: scene.id });
|
|
5176
5613
|
for (const block of scene.blocks) {
|
|
5177
5614
|
places.set(block.id, { sceneId: scene.id });
|
|
5178
|
-
|
|
5615
|
+
walkNodes7(block.children, (node) => {
|
|
5179
5616
|
places.set(node.id, { sceneId: scene.id });
|
|
5180
5617
|
if (node.type === "group") {
|
|
5181
5618
|
if (node.prompt) places.set(node.prompt.id, { sceneId: scene.id });
|
|
@@ -5229,7 +5666,7 @@ var Working = class {
|
|
|
5229
5666
|
if (!path) return void 0;
|
|
5230
5667
|
let file = this.flows.get(path);
|
|
5231
5668
|
if (!file) {
|
|
5232
|
-
const onDisk =
|
|
5669
|
+
const onDisk = parseSource9(readFileSync13(path, "utf8"));
|
|
5233
5670
|
const scene = this.loaded.scenes.find((s) => s.id === sceneId);
|
|
5234
5671
|
file = { ...onDisk, scene: structuredClone(scene ?? onDisk.scene) };
|
|
5235
5672
|
this.flows.set(path, file);
|
|
@@ -5237,13 +5674,13 @@ var Working = class {
|
|
|
5237
5674
|
return { path, file };
|
|
5238
5675
|
}
|
|
5239
5676
|
writes() {
|
|
5240
|
-
return [...this.locs, ...this.flows, ...this.authoring].filter(([path]) => this.touched.has(path)).map(([path, file]) => ({ path, content:
|
|
5677
|
+
return [...this.locs, ...this.flows, ...this.authoring].filter(([path]) => this.touched.has(path)).map(([path, file]) => ({ path, content: canonicalStringify12(file) })).sort((a, b) => a.path.localeCompare(b.path));
|
|
5241
5678
|
}
|
|
5242
5679
|
};
|
|
5243
5680
|
function beatIn(scene, id) {
|
|
5244
5681
|
let found;
|
|
5245
5682
|
for (const block of scene.blocks) {
|
|
5246
|
-
|
|
5683
|
+
walkNodes7(block.children, (node) => {
|
|
5247
5684
|
if (found || node.type !== "snippet") return;
|
|
5248
5685
|
found = (node.beats ?? []).find((b) => b.id === id);
|
|
5249
5686
|
});
|
|
@@ -5706,11 +6143,11 @@ function readParagraphs(ps, joiner) {
|
|
|
5706
6143
|
c.walk(p, "both");
|
|
5707
6144
|
return c;
|
|
5708
6145
|
});
|
|
5709
|
-
const
|
|
6146
|
+
const join12 = (view) => parts.map((c) => c.text(view)).filter((t) => t !== "").join(joiner);
|
|
5710
6147
|
return {
|
|
5711
6148
|
text: {
|
|
5712
|
-
proposed:
|
|
5713
|
-
original:
|
|
6149
|
+
proposed: join12("proposed"),
|
|
6150
|
+
original: join12("original"),
|
|
5714
6151
|
changes: parts.flatMap((c) => c.changes),
|
|
5715
6152
|
comments: [...new Set(parts.flatMap((c) => c.comments))],
|
|
5716
6153
|
droppedFormatting: parts.reduce((n, c) => n + c.dropped, 0)
|
|
@@ -5843,7 +6280,7 @@ async function readEditableDocx(bytes) {
|
|
|
5843
6280
|
}
|
|
5844
6281
|
|
|
5845
6282
|
// src/editable-import.ts
|
|
5846
|
-
import { canonicalStringify as
|
|
6283
|
+
import { canonicalStringify as canonicalStringify13 } from "@patterkit/core";
|
|
5847
6284
|
import { AUTHORING_SCHEMA as AUTHORING_SCHEMA4, projectQualifiers as projectQualifiers4 } from "@patterkit/model";
|
|
5848
6285
|
var REFUSE_MISSING_SHARE = 0.25;
|
|
5849
6286
|
var tidy = (s) => s.replace(/[\s ]+/g, " ").trim();
|
|
@@ -6111,7 +6548,7 @@ function planEditableImport(loaded, returned, opts) {
|
|
|
6111
6548
|
const af = shardFor(c.anchor);
|
|
6112
6549
|
af.comments = [...af.comments ?? [], c];
|
|
6113
6550
|
}
|
|
6114
|
-
let writes = [...shards].map(([path, af]) => ({ path, content:
|
|
6551
|
+
let writes = [...shards].map(([path, af]) => ({ path, content: canonicalStringify13(af) }));
|
|
6115
6552
|
if (opts.direct && suggestions.length) {
|
|
6116
6553
|
const warned = new Set(problems.filter((p) => p.severity === "warning" && p.anchor).map((p) => p.anchor));
|
|
6117
6554
|
const clean = suggestions.filter((s) => (live[s.anchor] ?? "") === s.baseline && !warned.has(s.anchor));
|
|
@@ -6279,6 +6716,7 @@ export {
|
|
|
6279
6716
|
NotAPackError,
|
|
6280
6717
|
PATTER_SCOPES_FILE,
|
|
6281
6718
|
PATTER_SCOPES_OWNER,
|
|
6719
|
+
PROJECT_HANDLED,
|
|
6282
6720
|
RARE_REACH_PCT,
|
|
6283
6721
|
REFUSE_MISSING_SHARE,
|
|
6284
6722
|
SCRIPT_FONT_FILES,
|
|
@@ -6301,6 +6739,7 @@ export {
|
|
|
6301
6739
|
catalogToPo,
|
|
6302
6740
|
catalogToXlsx,
|
|
6303
6741
|
classesForChannel,
|
|
6742
|
+
cleanCastRenames,
|
|
6304
6743
|
compileLoaded,
|
|
6305
6744
|
cueLabel,
|
|
6306
6745
|
currentBundlePosture,
|
|
@@ -6328,7 +6767,10 @@ export {
|
|
|
6328
6767
|
listProjectTags,
|
|
6329
6768
|
loadProject,
|
|
6330
6769
|
loadProjectLanding,
|
|
6770
|
+
loadedExtensions,
|
|
6771
|
+
manifestPath,
|
|
6331
6772
|
markerLength,
|
|
6773
|
+
moveCastKeys,
|
|
6332
6774
|
newHandoffId,
|
|
6333
6775
|
parseHandoff,
|
|
6334
6776
|
patterScopesFile,
|
|
@@ -6337,12 +6779,14 @@ export {
|
|
|
6337
6779
|
pickAudio,
|
|
6338
6780
|
planBuild,
|
|
6339
6781
|
planEditableImport,
|
|
6782
|
+
planExtensionUpgrades,
|
|
6340
6783
|
planPins,
|
|
6341
6784
|
planReturnedWorld,
|
|
6342
6785
|
planShareScopes,
|
|
6343
6786
|
planWorldSave,
|
|
6344
6787
|
poToCatalog,
|
|
6345
6788
|
previewRegistry,
|
|
6789
|
+
projectManifests,
|
|
6346
6790
|
projectStringSources,
|
|
6347
6791
|
proposeCoverageDrivers,
|
|
6348
6792
|
qualifierNamer,
|
|
@@ -6352,6 +6796,11 @@ export {
|
|
|
6352
6796
|
readHandoff,
|
|
6353
6797
|
readHandoffs,
|
|
6354
6798
|
readMarker,
|
|
6799
|
+
readProjectManifest,
|
|
6800
|
+
renameCastInScene,
|
|
6801
|
+
renameCastKeys,
|
|
6802
|
+
renameCastStrings,
|
|
6803
|
+
renameGameDataStage,
|
|
6355
6804
|
renameStage,
|
|
6356
6805
|
renameStageInExpression,
|
|
6357
6806
|
renderCoverageText,
|
|
@@ -6394,9 +6843,11 @@ export {
|
|
|
6394
6843
|
serialiseScopesFile,
|
|
6395
6844
|
setCut,
|
|
6396
6845
|
sidecarIssues,
|
|
6846
|
+
soundManifest,
|
|
6397
6847
|
stageDialect,
|
|
6398
6848
|
stageGateCount,
|
|
6399
6849
|
staleParts,
|
|
6850
|
+
unresolvedMarkIssues,
|
|
6400
6851
|
useScriptFontFiles,
|
|
6401
6852
|
vcsConfigWrites,
|
|
6402
6853
|
voiceScriptToXlsx,
|