@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +165 -18
  2. package/dist/index.js +665 -214
  3. 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 { root, projectFile, project, scenes, locales, sceneFiles, localeFiles, authoring, authoringFiles, ...withGameScopes(root, project) };
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 = { flow: "flow", strings: "loc", authoring: "authoring", project: "project" };
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
- switch (type) {
524
- case "loc":
525
- return mergeLoc(base, ours, theirs);
526
- case "authoring":
527
- return mergeAuthoring(base, ours, theirs);
528
- case "flow":
529
- return mergeFlow(base, ours, theirs);
530
- case "project":
531
- return mergeProject(base, ours, theirs);
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, resolve7) {
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 = resolve7?.(k, b, o, t);
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, resolve7) {
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, resolve7?.(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: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
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({ project: loaded.project, scenes: compileScenes(loaded), locales: loaded.locales, gameScopes: loaded.gameScopes?.merged });
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 parseSource2 } from "@patterkit/core";
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 = parseSource2(readFileSync4(projectFile, "utf8"));
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 files = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).map((abs) => ({ abs, rel: relative2(root, abs).split(sep).join("/") })).sort((a, b) => a.rel.localeCompare(b.rel));
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
- ok: !localisation.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)
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 readdirSync3 } from "fs";
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 = readdirSync3(r.dir);
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 (!issues.length) return;
1497
- throw new Error(
1498
- `${issues.length} unresolved merge conflict(s) - resolve them and delete the .patterconflict sidecar(s) first:
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 readFileSync6, statSync as statSync3 } from "fs";
2488
- import { dirname as dirname5 } from "path";
2489
- import { canonicalStringify as canonicalStringify5, parseSource as parseSource4 } from "@patterkit/core";
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 = dirname5(findProjectFile(path));
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 = readFileSync6(file, "utf8");
2911
+ const original = readFileSync8(file, "utf8");
2912
+ const notes = upgrades.notes.get(resolve5(file));
2505
2913
  let canonical;
2506
2914
  try {
2507
- canonical = canonicalStringify5(parseSource4(original));
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 changed ? { file, changed, write: { path: file, content: canonical } } : { file, changed };
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 join7, sep as sep3 } from "path";
2908
- import { canonicalStringify as canonicalStringify6 } from "@patterkit/core";
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(sep3);
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(sep3);
3486
+ return parts.join(sep4);
3079
3487
  }
3080
3488
  }
3081
3489
  }
3082
3490
  const stem = scene === PROJECT_LOCALE_SCENE2 ? "_project" : scene;
3083
- return join7(loaded.root, layoutOf(loaded).strings, locale, `${stem}.patterloc`);
3491
+ return join8(loaded.root, layoutOf(loaded).strings, locale, `${stem}.patterloc`);
3084
3492
  }
3085
3493
  function authoringPath(loaded, scene) {
3086
- const dir = join7(loaded.root, layoutOf(loaded).authoring);
3087
- if (scene === PROJECT_LOCALE_SCENE2) return join7(dir, "_project.patterx");
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 join7(dir, `${stem}.patterx`);
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: canonicalStringify6(file) });
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: canonicalStringify6(authoring) });
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 readdirSync4, readFileSync as readFileSync7 } from "fs";
4231
+ import { existsSync as existsSync5, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
3824
4232
  import { createRequire } from "module";
3825
- import { join as join8 } from "path";
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) => join8(p, pkg)).find((d) => existsSync5(d));
3913
- const file = dir && readdirSync4(dir).find((f) => /^(licen[cs]e|ofl)/i.test(f));
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 join8(dir, file);
4323
+ return join9(dir, file);
3916
4324
  }
3917
4325
  function scriptFontLicences() {
3918
- return SCRIPT_FONT_PACKAGES.map((pkg) => ({ package: pkg, text: readFileSync7(licences[pkg] ?? scriptFontLicencePath(pkg), "utf8") }));
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 = readFileSync7(located[spec] ?? require2.resolve(spec)));
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((resolve7, reject) => {
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", () => resolve7(Buffer.concat(chunks)));
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 join9, basename as basename6, resolve as resolve5 } from "path";
4160
- import { readdirSync as readdirSync5, existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
4161
- import { newId, slug, canonicalStringify as canonicalStringify7 } from "@patterkit/core";
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 = resolve5(opts.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 = readdirSync5(dir).filter((f) => f.endsWith(".patterproj"));
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 = join9(dir, `${fileSlug}.patterproj`);
4621
+ const projectFile = join10(dir, `${fileSlug}.patterproj`);
4214
4622
  const writes = [
4215
- { path: join9(dir, "scenes", "start.patterflow"), content: canonicalStringify7(flow) },
4216
- { path: join9(dir, "loc", "en", "start.patterloc"), content: canonicalStringify7(locale) },
4217
- { path: join9(dir, ".editorconfig"), content: EDITORCONFIG },
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: canonicalStringify7(project) }
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: join9(dir, "vcs-setup.md"), content: vcsSetup(vcs, bundle) }];
4229
- if (vcs === "git") writes.push({ path: join9(dir, ".gitattributes"), content: GITATTRIBUTES });
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: join9(dir, ignore), content: ignoreContent(bundle) });
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 readFileSync8(join9(dir, name), "utf8");
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 canonicalStringify8 } from "@patterkit/core";
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: canonicalStringify8(next) });
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: canonicalStringify8(file) });
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, unparse } from "@wildwinter/expr";
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 === void 0) continue;
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 readFileSync9 } from "fs";
4898
- import { canonicalStringify as canonicalStringify9, parseSource as parseSource5 } from "@patterkit/core";
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 = parseSource5(readFileSync9(path, "utf8"));
4926
- plan.writes.push({ path, content: canonicalStringify9({ ...file, scene: next }) });
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 join10, resolve as resolve6, sep as sep4 } from "path";
4935
- import { existsSync as existsSync7, readFileSync as readFileSync10, readdirSync as readdirSync6 } from "fs";
4936
- import { parseSource as parseSource6, canonicalStringify as canonicalStringify10 } from "@patterkit/core";
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 = readdirSync6(dir).filter((n) => n.endsWith(".patterproj"));
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 = parseSource6(readFileSync10(join10(dir, n), "utf8")).project?.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 parseSource6(shards.get(rel)).project?.id;
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 = parseSource6(readFileSync10(findProjectFile(projectDir), "utf8"));
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 = readFileSync10(outPath, "utf8");
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) => parseSource6(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 parseSource6(text);
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 result = runMerge(baseObj, oursObj, theirsObj);
5084
- const mergedText = canonicalStringify10(result.merged);
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(resolve6(projectDir), projectSides.ours).gameScopes;
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: join10(local2.dir, GAME_SCOPES_FILE), error: world.error };
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(readFileSync10(outPath, "utf8")), parseHandoff(theirText));
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 { shards, writes, sidecars, other: returnedDoc.other, conflicts, warnings, provenance, ...gameScopes ? { gameScopes } : {}, ...handoffs.length ? { handoffs } : {} };
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 = resolve6(dir);
5137
- const full = resolve6(join10(dir, name));
5138
- if (full !== root && !full.startsWith(root + sep4)) {
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 readFileSync11 } from "fs";
5146
- import { canonicalStringify as canonicalStringify11, parseSource as parseSource7 } from "@patterkit/core";
5147
- import { walkNodes as walkNodes6, AUTHORING_SCHEMA as AUTHORING_SCHEMA3 } from "@patterkit/model";
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
- walkNodes6(block.children, (node) => {
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 = parseSource7(readFileSync11(path, "utf8"));
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: canonicalStringify11(file) })).sort((a, b) => a.path.localeCompare(b.path));
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
- walkNodes6(block.children, (node) => {
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 join11 = (view) => parts.map((c) => c.text(view)).filter((t) => t !== "").join(joiner);
6146
+ const join12 = (view) => parts.map((c) => c.text(view)).filter((t) => t !== "").join(joiner);
5710
6147
  return {
5711
6148
  text: {
5712
- proposed: join11("proposed"),
5713
- original: join11("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 canonicalStringify12 } from "@patterkit/core";
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: canonicalStringify12(af) }));
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,