@patterkit/cli 0.2.5 → 0.2.7

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 (2) hide show
  1. package/dist/cli.js +616 -349
  2. package/package.json +3 -3
package/dist/cli.js CHANGED
@@ -192562,6 +192562,21 @@ function checkDecls(decls, where, issues) {
192562
192562
  message: `${where}: '${decl.name}' is ${decl.type} but declares no values`
192563
192563
  });
192564
192564
  }
192565
+ if (decl.type === "quality") {
192566
+ const stages = decl.stages ?? [];
192567
+ if (stages.length < 2) {
192568
+ issues.push({
192569
+ code: "invalid-declaration",
192570
+ message: `${where}: '${decl.name}' is a quality but declares ${stages.length === 0 ? "no stages" : "only one stage"} (a ladder needs at least two)`
192571
+ });
192572
+ }
192573
+ const seenStages = /* @__PURE__ */ new Set();
192574
+ for (const s of stages) {
192575
+ if (!s.trim()) issues.push({ code: "invalid-declaration", message: `${where}: '${decl.name}' has an empty stage name` });
192576
+ else if (seenStages.has(s)) issues.push({ code: "invalid-declaration", message: `${where}: '${decl.name}' declares stage '${s}' more than once` });
192577
+ else seenStages.add(s);
192578
+ }
192579
+ }
192565
192580
  if (decl.default !== void 0 && !defaultMatchesType(decl.default, decl)) {
192566
192581
  issues.push({
192567
192582
  code: "invalid-declaration",
@@ -192582,6 +192597,8 @@ function defaultMatchesType(value, decl) {
192582
192597
  return typeof value === "string" && (decl.values?.includes(value) ?? true);
192583
192598
  case "flags":
192584
192599
  return Array.isArray(value) && value.every((v) => typeof v === "string" && (decl.values?.includes(v) ?? true));
192600
+ case "quality":
192601
+ return typeof value === "string" && (decl.stages?.includes(value) ?? true);
192585
192602
  }
192586
192603
  }
192587
192604
 
@@ -192598,6 +192615,7 @@ function readDirSafe(dir2) {
192598
192615
  function walkFiles(dir2, ext) {
192599
192616
  const out = [];
192600
192617
  for (const e of readDirSafe(dir2)) {
192618
+ if (e.name.startsWith(".")) continue;
192601
192619
  const p = join(dir2, e.name);
192602
192620
  if (e.isDirectory()) out.push(...walkFiles(p, ext));
192603
192621
  else if (e.isFile() && e.name.endsWith(ext)) out.push(p);
@@ -192731,6 +192749,347 @@ import { dirname as dirname2 } from "path";
192731
192749
  // ../ops/src/validate.ts
192732
192750
  import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
192733
192751
 
192752
+ // ../ops/src/merge.ts
192753
+ var UnsupportedMergeError = class extends Error {
192754
+ };
192755
+ var eq = (a, b) => canonicalStringify(a) === canonicalStringify(b);
192756
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
192757
+ var CONFLICT_SIDECAR = ".patterconflict";
192758
+ function sidecarIssues(sidecarPaths) {
192759
+ return sidecarPaths.map((file) => ({
192760
+ file,
192761
+ message: "unresolved merge conflict - resolve it and delete the .patterconflict sidecar before committing"
192762
+ }));
192763
+ }
192764
+ function detectMergeType(file) {
192765
+ const s = typeof file.schema === "string" ? file.schema : "";
192766
+ if (s.startsWith("patter/flow")) return "flow";
192767
+ if (s.startsWith("patter/strings")) return "loc";
192768
+ if (s.startsWith("patter/authoring")) return "authoring";
192769
+ if (s.startsWith("patter/project")) return "project";
192770
+ throw new UnsupportedMergeError(`cannot detect a Patter merge type from schema '${s}'`);
192771
+ }
192772
+ function runMerge(base, ours, theirs, opts) {
192773
+ const type = opts?.type ?? detectMergeType(ours);
192774
+ switch (type) {
192775
+ case "loc":
192776
+ return mergeLoc(base, ours, theirs);
192777
+ case "authoring":
192778
+ return mergeAuthoring(base, ours, theirs);
192779
+ case "flow":
192780
+ return mergeFlow(base, ours, theirs);
192781
+ case "project":
192782
+ return mergeProject(base, ours, theirs);
192783
+ }
192784
+ }
192785
+ function deletedKind(base, ours, theirs) {
192786
+ const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
192787
+ return deleted ? "delete-vs-edit" : "both-changed";
192788
+ }
192789
+ function merge3(base, ours, theirs, id, path, conflicts) {
192790
+ if (eq(ours, theirs)) return ours;
192791
+ if (eq(base, ours)) return theirs;
192792
+ if (eq(base, theirs)) return ours;
192793
+ conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
192794
+ return ours;
192795
+ }
192796
+ function mergeMap(base, ours, theirs, prefix, conflicts, resolve6) {
192797
+ const out = {};
192798
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192799
+ const b = base[k], o = ours[k], t = theirs[k];
192800
+ let v;
192801
+ if (eq(o, t)) v = o;
192802
+ else if (eq(b, o)) v = t;
192803
+ else if (eq(b, t)) v = o;
192804
+ else {
192805
+ const r = resolve6?.(k, b, o, t);
192806
+ if (r && r.resolved) v = r.value;
192807
+ else {
192808
+ conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
192809
+ v = o;
192810
+ }
192811
+ }
192812
+ if (v !== void 0) out[k] = v;
192813
+ }
192814
+ return out;
192815
+ }
192816
+ var asMap = (v) => isObj(v) ? v : {};
192817
+ var asArr = (v) => Array.isArray(v) ? v : [];
192818
+ function setIf(obj2, key2, val) {
192819
+ if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj2[key2] = val;
192820
+ }
192821
+ function mergeLoc(base, ours, theirs) {
192822
+ const conflicts = [];
192823
+ const merged = {};
192824
+ for (const f of ["schema", "scene", "locale", "default"]) {
192825
+ const v = merge3(base[f], ours[f], theirs[f], "", f, conflicts);
192826
+ if (v !== void 0) merged[f] = v;
192827
+ }
192828
+ merged.strings = mergeMap(asMap(base.strings), asMap(ours.strings), asMap(theirs.strings), "strings", conflicts);
192829
+ return { type: "loc", merged, conflicts, warnings: [] };
192830
+ }
192831
+ function mergeAuthoring(base, ours, theirs) {
192832
+ const conflicts = [];
192833
+ const merged = {};
192834
+ merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
192835
+ setIf(merged, "comments", mergeComments(asArr(base.comments), asArr(ours.comments), asArr(theirs.comments)));
192836
+ const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
192837
+ setIf(merged, "edits", edits);
192838
+ const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
192839
+ for (const f of ["writing", "recording", "audio"]) {
192840
+ setIf(merged, f, mergeMap(asMap(base[f]), asMap(ours[f]), asMap(theirs[f]), f, conflicts, lww));
192841
+ }
192842
+ setIf(merged, "documentation", mergeMap(asMap(base.documentation), asMap(ours.documentation), asMap(theirs.documentation), "documentation", conflicts));
192843
+ setIf(merged, "cut", mergeMap(asMap(base.cut), asMap(ours.cut), asMap(theirs.cut), "cut", conflicts, void 0));
192844
+ setIf(merged, "suggestions", mergeById(asArr(base.suggestions), asArr(ours.suggestions), asArr(theirs.suggestions), "suggestions", conflicts));
192845
+ setIf(merged, "rerecord", mergeMap(asMap(base.rerecord), asMap(ours.rerecord), asMap(theirs.rerecord), "rerecord", conflicts, void 0));
192846
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192847
+ if (k in merged || AUTHORING_HANDLED.has(k)) continue;
192848
+ const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
192849
+ if (v !== void 0) merged[k] = v;
192850
+ }
192851
+ return { type: "authoring", merged, conflicts, warnings: [] };
192852
+ }
192853
+ var AUTHORING_HANDLED = /* @__PURE__ */ new Set(["schema", "comments", "edits", "writing", "recording", "audio", "documentation", "cut", "suggestions", "rerecord"]);
192854
+ function mergeById(base, ours, theirs, path, conflicts) {
192855
+ const index2 = (arr) => {
192856
+ const m = {};
192857
+ for (const r of arr) if (isObj(r) && typeof r.id === "string") m[r.id] = r;
192858
+ return m;
192859
+ };
192860
+ const merged = mergeMap(index2(base), index2(ours), index2(theirs), path, conflicts);
192861
+ return Object.values(merged).sort((a, b) => String(asMap(a).ts ?? "").localeCompare(String(asMap(b).ts ?? "")));
192862
+ }
192863
+ function mergeComments(base, ours, theirs) {
192864
+ const byId = /* @__PURE__ */ new Map();
192865
+ for (const c2 of [...base, ...ours, ...theirs]) {
192866
+ if (isObj(c2) && typeof c2.id === "string" && !byId.has(c2.id)) byId.set(c2.id, c2);
192867
+ }
192868
+ return [...byId.values()].sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
192869
+ }
192870
+ function lastWriterWins(oursEdits, theirsEdits) {
192871
+ return (id, _b, o, t) => {
192872
+ const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
192873
+ if (typeof om === "string" && typeof tm === "string") return { resolved: true, value: om >= tm ? o : t };
192874
+ return { resolved: false, kind: "no-timestamp" };
192875
+ };
192876
+ }
192877
+ function mergeEdits(base, ours, theirs) {
192878
+ const out = {};
192879
+ for (const id of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192880
+ const o = ours[id], t = theirs[id];
192881
+ if (eq(o, t)) {
192882
+ if (o !== void 0) out[id] = o;
192883
+ continue;
192884
+ }
192885
+ if (o === void 0) {
192886
+ if (t !== void 0) out[id] = t;
192887
+ continue;
192888
+ }
192889
+ if (t === void 0) {
192890
+ out[id] = o;
192891
+ continue;
192892
+ }
192893
+ out[id] = mergeEditRecord(asMap(o), asMap(t));
192894
+ }
192895
+ return out;
192896
+ }
192897
+ function mergeEditRecord(o, t) {
192898
+ const om = typeof o.modifiedAt === "string" ? o.modifiedAt : "";
192899
+ const tm = typeof t.modifiedAt === "string" ? t.modifiedAt : "";
192900
+ const newer = om >= tm ? o : t;
192901
+ const ol = asMap(o.localisedAt), tl = asMap(t.localisedAt);
192902
+ const loc = {};
192903
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(ol), ...Object.keys(tl)])) {
192904
+ const a = typeof ol[k] === "string" ? ol[k] : "";
192905
+ const b = typeof tl[k] === "string" ? tl[k] : "";
192906
+ loc[k] = a >= b ? ol[k] ?? tl[k] : tl[k] ?? ol[k];
192907
+ }
192908
+ const merged = { ...newer };
192909
+ if (Object.keys(loc).length > 0) merged.localisedAt = loc;
192910
+ return merged;
192911
+ }
192912
+ function mergeKeyedByName(base, ours, theirs, path, conflicts) {
192913
+ const byName = (arr) => {
192914
+ const m = {};
192915
+ for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
192916
+ return m;
192917
+ };
192918
+ const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts);
192919
+ return Object.keys(merged).sort().map((name) => merged[name]);
192920
+ }
192921
+ function mergeProject(base, ours, theirs) {
192922
+ const conflicts = [];
192923
+ const merged = {};
192924
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
192925
+ if (k === "properties" || k === "cast") {
192926
+ const v = mergeKeyedByName(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
192927
+ if (v.length > 0) merged[k] = v;
192928
+ } else if (k === "gameDataFields") {
192929
+ const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
192930
+ if (Object.keys(v).length > 0) merged[k] = v;
192931
+ } else if (k === "locales") {
192932
+ merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
192933
+ } else {
192934
+ const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
192935
+ if (v !== void 0) merged[k] = v;
192936
+ }
192937
+ }
192938
+ return { type: "project", merged, conflicts, warnings: [] };
192939
+ }
192940
+ function mergeGameDataFields(b, o, t, conflicts) {
192941
+ const out = {};
192942
+ for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
192943
+ const v = mergeKeyedByName(asArr(b[kind]), asArr(o[kind]), asArr(t[kind]), `gameDataFields.${kind}`, conflicts);
192944
+ if (v.length > 0) out[kind] = v;
192945
+ }
192946
+ return out;
192947
+ }
192948
+ function mergeLocales(b, o, t, conflicts) {
192949
+ const out = {};
192950
+ const def = merge3(b.default, o.default, t.default, "", "locales.default", conflicts);
192951
+ if (def !== void 0) out.default = def;
192952
+ const all = [];
192953
+ for (const arr of [asArr(b.all), asArr(o.all), asArr(t.all)]) {
192954
+ for (const x of arr) if (typeof x === "string" && !all.includes(x)) all.push(x);
192955
+ }
192956
+ out.all = all;
192957
+ return out;
192958
+ }
192959
+ var CHILD_KEYS = ["blocks", "children", "beats"];
192960
+ function toTree(raw) {
192961
+ const childKey = CHILD_KEYS.find((k) => Array.isArray(raw[k]));
192962
+ const fields = {};
192963
+ for (const [k, v] of Object.entries(raw)) if (k !== childKey) fields[k] = v;
192964
+ const children = childKey ? raw[childKey].filter(isObj).map(toTree) : [];
192965
+ return { id: typeof raw.id === "string" ? raw.id : "", fields, childKey, children };
192966
+ }
192967
+ function fromTree(n) {
192968
+ const out = { ...n.fields };
192969
+ if (n.childKey) out[n.childKey] = n.children.map(fromTree);
192970
+ return out;
192971
+ }
192972
+ function mergeFlow(base, ours, theirs) {
192973
+ const conflicts = [];
192974
+ const warnings = [];
192975
+ const merged = {};
192976
+ merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
192977
+ const bScene = isObj(base.scene) ? toTree(base.scene) : void 0;
192978
+ const oScene = toTree(asMap(ours.scene));
192979
+ const tScene = isObj(theirs.scene) ? toTree(theirs.scene) : void 0;
192980
+ const mergedScene = mergeNode(bScene, oScene, tScene, "scene", conflicts, warnings);
192981
+ merged.scene = fromTree(mergedScene);
192982
+ checkDuplicateIds(mergedScene, conflicts);
192983
+ return { type: "flow", merged, conflicts, warnings };
192984
+ }
192985
+ function mergeNode(b, o, t, path, conflicts, warnings) {
192986
+ const fields = mergeFields(b?.fields ?? {}, o.fields, t?.fields ?? {}, o.id, path, conflicts);
192987
+ const childKey = o.childKey ?? t?.childKey ?? b?.childKey;
192988
+ const children = childKey ? mergeChildren(b?.children ?? [], o.children, t?.children ?? [], `${path}.${childKey}`, conflicts, warnings) : [];
192989
+ return { id: o.id, fields, childKey, children };
192990
+ }
192991
+ function mergeFields(b, o, t, id, path, conflicts) {
192992
+ const out = {};
192993
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
192994
+ const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
192995
+ if (v !== void 0) out[k] = v;
192996
+ }
192997
+ return out;
192998
+ }
192999
+ function mergeChildren(B, O, T, path, conflicts, warnings) {
193000
+ const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
193001
+ const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
193002
+ const merged = /* @__PURE__ */ new Map();
193003
+ const here = (id) => `${path}[${id}]`;
193004
+ for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
193005
+ const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
193006
+ const inB = Bset.has(id), inO = Oset.has(id), inT = Tset.has(id);
193007
+ if (inO && inT) {
193008
+ if (!inB && !eq(fromTree(o), fromTree(t))) {
193009
+ conflicts.push({ id, path: here(id), base: void 0, ours: fromTree(o), theirs: fromTree(t), kind: "added-both" });
193010
+ merged.set(id, o);
193011
+ } else {
193012
+ merged.set(id, mergeNode(b, o, t, here(id), conflicts, warnings));
193013
+ }
193014
+ } else if (inO && !inT) {
193015
+ if (inB && !eq(fromTree(o), fromTree(b))) {
193016
+ conflicts.push({ id, path: here(id), base: fromTree(b), ours: fromTree(o), theirs: void 0, kind: "delete-vs-edit" });
193017
+ merged.set(id, o);
193018
+ } else if (!inB) {
193019
+ merged.set(id, o);
193020
+ }
193021
+ } else if (!inO && inT) {
193022
+ if (inB && !eq(fromTree(t), fromTree(b))) {
193023
+ conflicts.push({ id, path: here(id), base: fromTree(b), ours: void 0, theirs: fromTree(t), kind: "delete-vs-edit" });
193024
+ } else if (!inB) {
193025
+ merged.set(id, t);
193026
+ }
193027
+ }
193028
+ }
193029
+ return orderChildren(merged, B, O, T, path, conflicts, warnings);
193030
+ }
193031
+ function orderChildren(merged, B, O, T, path, conflicts, warnings) {
193032
+ const Bids = B.map((n) => n.id), Oids = O.map((n) => n.id), Tids = T.map((n) => n.id);
193033
+ const Oidset = new Set(Oids);
193034
+ const common = new Set(Bids.filter((id) => merged.has(id)));
193035
+ const restrict = (ids) => ids.filter((id) => common.has(id));
193036
+ const relB = restrict(Bids), relO = restrict(Oids), relT = restrict(Tids);
193037
+ const fullO = relO.length === common.size, fullT = relT.length === common.size;
193038
+ let commonOrder;
193039
+ if (!fullO && !fullT) commonOrder = relB;
193040
+ else if (!fullO) commonOrder = relT;
193041
+ else if (!fullT) commonOrder = relO;
193042
+ else if (arrEq(relO, relB)) commonOrder = relT;
193043
+ else if (arrEq(relT, relB)) commonOrder = relO;
193044
+ else if (arrEq(relO, relT)) commonOrder = relO;
193045
+ else {
193046
+ conflicts.push({ id: "", path, base: relB, ours: relO, theirs: relT, kind: "moved" });
193047
+ commonOrder = relO;
193048
+ }
193049
+ const anchorIn = (ids, id) => {
193050
+ for (let i = ids.indexOf(id) - 1; i >= 0; i--) if (common.has(ids[i])) return ids[i];
193051
+ return null;
193052
+ };
193053
+ const byAnchor = /* @__PURE__ */ new Map();
193054
+ const group = (a) => {
193055
+ let g = byAnchor.get(a);
193056
+ if (!g) {
193057
+ g = { ours: [], theirs: [] };
193058
+ byAnchor.set(a, g);
193059
+ }
193060
+ return g;
193061
+ };
193062
+ for (const n of O) if (merged.has(n.id) && !common.has(n.id)) group(anchorIn(Oids, n.id)).ours.push(n.id);
193063
+ for (const n of T) if (merged.has(n.id) && !common.has(n.id) && !Oidset.has(n.id)) group(anchorIn(Tids, n.id)).theirs.push(n.id);
193064
+ const out = [];
193065
+ const emit = (anchor) => {
193066
+ const g = byAnchor.get(anchor);
193067
+ if (!g) return;
193068
+ if (g.ours.length > 0 && g.theirs.length > 0) warnings.push({ id: anchor ?? "", path, message: "both sides inserted here; kept ours-then-theirs - review the order" });
193069
+ out.push(...g.ours, ...g.theirs);
193070
+ };
193071
+ emit(null);
193072
+ for (const id of commonOrder) {
193073
+ out.push(id);
193074
+ emit(id);
193075
+ }
193076
+ return out.map((id) => merged.get(id));
193077
+ }
193078
+ function checkDuplicateIds(root2, conflicts) {
193079
+ const seen = /* @__PURE__ */ new Set(), dup = /* @__PURE__ */ new Set();
193080
+ const walk2 = (n) => {
193081
+ if (n.id) {
193082
+ if (seen.has(n.id)) dup.add(n.id);
193083
+ else seen.add(n.id);
193084
+ }
193085
+ n.children.forEach(walk2);
193086
+ };
193087
+ walk2(root2);
193088
+ for (const id of dup) conflicts.push({ id, path: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
193089
+ }
193090
+ var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
193091
+ var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
193092
+
192734
193093
  // ../../../expr/packages/expr/src/parser.ts
192735
193094
  var ParseError = class extends Error {
192736
193095
  constructor(message, pos2, source2) {
@@ -193190,6 +193549,18 @@ function evaluate(node, ctx, dialect) {
193190
193549
  return val;
193191
193550
  }
193192
193551
  case "call": {
193552
+ if (n.name === "advance" && !dialect.functions[n.name]) {
193553
+ const arg = n.args[0];
193554
+ if (n.args.length !== 1 || arg === void 0) {
193555
+ throw new EvalError2(`advance() takes exactly 1 argument, got ${n.args.length}`);
193556
+ }
193557
+ const ladder = ladderOf(arg, ctx);
193558
+ if (ladder === void 0) {
193559
+ throw new EvalError2("advance() needs a quality reference (@scope.name of a quality property)");
193560
+ }
193561
+ const current = stageIndex(rec(arg), ladder, "advance");
193562
+ return ladder[Math.min(current + 1, ladder.length - 1)];
193563
+ }
193193
193564
  const def = dialect.functions[n.name];
193194
193565
  if (!def) throw new EvalError2(`unknown function '${n.name}'`);
193195
193566
  return def.eval(n.args, { evaluate: rec, ctx });
@@ -193225,6 +193596,33 @@ function evaluate(node, ctx, dialect) {
193225
193596
  }
193226
193597
  const left = rec(n.left);
193227
193598
  const right = rec(n.right);
193599
+ const lLadder = ladderOf(n.left, ctx);
193600
+ const rLadder = ladderOf(n.right, ctx);
193601
+ const ladder = lLadder ?? rLadder;
193602
+ if (ladder !== void 0) {
193603
+ if (lLadder && rLadder && !sameLadder(lLadder, rLadder)) {
193604
+ if (n.op === ">" || n.op === ">=" || n.op === "<" || n.op === "<=") {
193605
+ throw new EvalError2(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);
193606
+ }
193607
+ }
193608
+ switch (n.op) {
193609
+ case ">":
193610
+ return stageIndex(left, ladder, ">") > stageIndex(right, ladder, ">");
193611
+ case ">=":
193612
+ return stageIndex(left, ladder, ">=") >= stageIndex(right, ladder, ">=");
193613
+ case "<":
193614
+ return stageIndex(left, ladder, "<") < stageIndex(right, ladder, "<");
193615
+ case "<=":
193616
+ return stageIndex(left, ladder, "<=") <= stageIndex(right, ladder, "<=");
193617
+ case "+":
193618
+ case "-":
193619
+ case "*":
193620
+ case "/":
193621
+ throw new EvalError2(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);
193622
+ default:
193623
+ break;
193624
+ }
193625
+ }
193228
193626
  switch (n.op) {
193229
193627
  case "==":
193230
193628
  return valueEquals(left, right);
@@ -193276,6 +193674,19 @@ function assertNumbers(l2, r, op) {
193276
193674
  throw new EvalError2(`'${op}' requires numeric operands, got ${typeof l2} and ${typeof r}`);
193277
193675
  }
193278
193676
  }
193677
+ function ladderOf(node, ctx) {
193678
+ if (node.kind !== "scopedvar" || ctx.qualities === void 0) return void 0;
193679
+ return ctx.qualities(node.scope, node.name);
193680
+ }
193681
+ function stageIndex(value, ladder, op) {
193682
+ if (typeof value !== "string") {
193683
+ throw new EvalError2(`'${op}' on a quality compares stages, got ${typeof value}`);
193684
+ }
193685
+ const i = ladder.indexOf(value);
193686
+ if (i < 0) throw new EvalError2(`"${value}" is not a stage of this quality (stages: ${ladder.join(", ")})`);
193687
+ return i;
193688
+ }
193689
+ var sameLadder = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
193279
193690
 
193280
193691
  // ../../../expr/packages/expr/src/validate.ts
193281
193692
  function validateExpressionAst(node, schema, dialect) {
@@ -193313,6 +193724,24 @@ function walkValidate(node, schema, dialect, path, issues) {
193313
193724
  return;
193314
193725
  }
193315
193726
  case "call": {
193727
+ if (node.name === "advance" && !dialect.functions[node.name]) {
193728
+ if (node.args.length !== 1) {
193729
+ issues.push({ path, kind: "wrong-arg-count", severity: "error", message: `advance() takes exactly 1 argument, got ${node.args.length}` });
193730
+ }
193731
+ const arg = node.args[0];
193732
+ if (arg) {
193733
+ walkValidate(arg, schema, dialect, [...path, "args", 0], issues);
193734
+ if (stagesOf(arg, schema) === void 0 && typeOf(arg, schema, dialect) !== "unknown") {
193735
+ issues.push({
193736
+ path: [...path, "args", 0],
193737
+ kind: "wrong-arg-type",
193738
+ severity: "error",
193739
+ message: "advance() needs a quality reference (@scope.name of a quality property)"
193740
+ });
193741
+ }
193742
+ }
193743
+ return;
193744
+ }
193316
193745
  const def = dialect.functions[node.name];
193317
193746
  if (!def) {
193318
193747
  issues.push({ path, kind: "unknown-function", severity: "error", message: `unknown function '${node.name}'` });
@@ -193400,11 +193829,17 @@ function inferredFromPropertyType(t) {
193400
193829
  if (t === "number") return "number";
193401
193830
  if (t === "string" || t === "enum") return "string";
193402
193831
  if (t === "flags") return "flags";
193832
+ if (t === "quality") return "quality";
193403
193833
  return "unknown";
193404
193834
  }
193405
193835
  function inferredFromReturn(t) {
193406
193836
  return t;
193407
193837
  }
193838
+ function stagesOf(node, schema) {
193839
+ if (node.kind !== "scopedvar") return void 0;
193840
+ const meta = schema.properties.get(node.scope)?.get(node.name);
193841
+ return meta?.type === "quality" ? meta.stages : void 0;
193842
+ }
193408
193843
  function typeOf(node, schema, dialect) {
193409
193844
  switch (node.kind) {
193410
193845
  case "bool":
@@ -193420,6 +193855,7 @@ function typeOf(node, schema, dialect) {
193420
193855
  return meta ? inferredFromPropertyType(meta.type) : "unknown";
193421
193856
  }
193422
193857
  case "call": {
193858
+ if (node.name === "advance") return "quality";
193423
193859
  const def = dialect.functions[node.name];
193424
193860
  return def ? inferredFromReturn(def.returnType) : "unknown";
193425
193861
  }
@@ -193450,7 +193886,54 @@ function describeType(t) {
193450
193886
  function checkBinaryOperandTypes(op, left, right, schema, dialect, path, issues) {
193451
193887
  const lt = typeOf(left, schema, dialect);
193452
193888
  const rt = typeOf(right, schema, dialect);
193453
- if (op === "+" || op === "-" || op === "*" || op === "/" || op === ">" || op === "<" || op === ">=" || op === "<=") {
193889
+ const ordering = op === ">" || op === "<" || op === ">=" || op === "<=";
193890
+ if (lt === "quality" || rt === "quality") {
193891
+ if (ordering || op === "==" || op === "!=") {
193892
+ const ls = stagesOf(left, schema);
193893
+ const rs = stagesOf(right, schema);
193894
+ if (ls && rs) {
193895
+ if (ls.join("\0") !== rs.join("\0")) {
193896
+ issues.push({ path, kind: "operand-type-mismatch", severity: "error", message: `'${op}' compares two different qualities, whose stages are unrelated` });
193897
+ }
193898
+ return;
193899
+ }
193900
+ const ladder = ls ?? rs;
193901
+ const other = ls ? right : left;
193902
+ const otherSide = ls ? "right" : "left";
193903
+ if (other.kind === "string") {
193904
+ if (ladder && !ladder.includes(other.value)) {
193905
+ issues.push({
193906
+ path: [...path, otherSide],
193907
+ kind: "unknown-stage",
193908
+ severity: "error",
193909
+ message: `'${other.value}' is not a stage of this quality - expected one of: ${ladder.join(", ")}`,
193910
+ reference: other.value
193911
+ });
193912
+ }
193913
+ return;
193914
+ }
193915
+ const ot = ls ? rt : lt;
193916
+ if (ot !== "unknown" && ot !== "quality") {
193917
+ issues.push({
193918
+ path: [...path, otherSide],
193919
+ kind: "operand-type-mismatch",
193920
+ severity: "error",
193921
+ message: `'${op}' on a quality compares stages, got ${describeType(ot)} on the ${otherSide}`
193922
+ });
193923
+ }
193924
+ return;
193925
+ }
193926
+ if (op === "+" || op === "-" || op === "*" || op === "/") {
193927
+ issues.push({
193928
+ path,
193929
+ kind: "operand-type-mismatch",
193930
+ severity: "error",
193931
+ message: `'${op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`
193932
+ });
193933
+ return;
193934
+ }
193935
+ }
193936
+ if (op === "+" || op === "-" || op === "*" || op === "/" || ordering) {
193454
193937
  if (lt !== "unknown" && lt !== "number")
193455
193938
  issues.push({ path: [...path, "left"], kind: "operand-type-mismatch", severity: "error", message: `'${op}' requires a number on the left, got ${describeType(lt)}` });
193456
193939
  if (rt !== "unknown" && rt !== "number")
@@ -193676,6 +194159,7 @@ function hostScopesToSpec(reg) {
193676
194159
  name: d.name,
193677
194160
  type: d.type,
193678
194161
  ...d.values ? { values: d.values } : {},
194162
+ ...d.stages ? { stages: d.stages } : {},
193679
194163
  ...d.default !== void 0 ? { default: d.default } : {},
193680
194164
  ...d.writable === false ? { writable: false } : {}
193681
194165
  }))
@@ -193691,7 +194175,7 @@ function buildSchema(project, sceneProps = [], foreign) {
193691
194175
  m = /* @__PURE__ */ new Map();
193692
194176
  properties.set(scope, m);
193693
194177
  }
193694
- m.set(decl.name.toLowerCase(), { type: decl.type, enumValues: decl.values });
194178
+ m.set(decl.name.toLowerCase(), { type: decl.type, enumValues: decl.values, stages: decl.stages });
193695
194179
  };
193696
194180
  for (const decl of project.properties ?? []) put("patter", decl);
193697
194181
  for (const decl of sceneProps) put("scene", decl);
@@ -193700,7 +194184,7 @@ function buildSchema(project, sceneProps = [], foreign) {
193700
194184
  if (known.has(scope.token)) continue;
193701
194185
  if (!scope.declarations?.length) continue;
193702
194186
  const m = /* @__PURE__ */ new Map();
193703
- for (const d of scope.declarations) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values });
194187
+ for (const d of scope.declarations) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values, stages: d.stages });
193704
194188
  properties.set(scope.token, m);
193705
194189
  }
193706
194190
  return { properties };
@@ -194153,10 +194637,8 @@ function runValidate(loaded) {
194153
194637
  const interpolation = validateInterpolation({ project, scenes, locales }, { foreignScopes });
194154
194638
  const hygiene = checkHygiene([loaded.projectFile, ...Object.values(loaded.sceneFiles), ...loaded.localeFiles, ...loaded.authoringFiles]);
194155
194639
  const staleBundles = checkBundles(loaded);
194156
- const unresolvedMerges = walkFiles(loaded.root, ".patterconflict").map((file) => ({
194157
- file,
194158
- message: "unresolved merge conflict - resolve it and delete the .patterconflict sidecar before committing"
194159
- }));
194640
+ const unresolvedMerges = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
194641
+ const orphans = orphanShards(loaded);
194160
194642
  return {
194161
194643
  structural,
194162
194644
  conditions,
@@ -194164,9 +194646,32 @@ function runValidate(loaded) {
194164
194646
  hygiene,
194165
194647
  staleBundles,
194166
194648
  unresolvedMerges,
194167
- ok: structural.length === 0 && conditions.length === 0 && interpolation.length === 0 && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0
194649
+ orphans,
194650
+ ok: structural.length === 0 && conditions.length === 0 && interpolation.length === 0 && hygiene.length === 0 && staleBundles.length === 0 && unresolvedMerges.length === 0 && orphans.length === 0
194168
194651
  };
194169
194652
  }
194653
+ function orphanShards(loaded) {
194654
+ const collected = /* @__PURE__ */ new Set([
194655
+ loaded.projectFile,
194656
+ ...Object.values(loaded.sceneFiles),
194657
+ ...loaded.localeFiles,
194658
+ ...loaded.authoringFiles
194659
+ ]);
194660
+ const kind = {
194661
+ ".patterflow": "scene",
194662
+ ".patterloc": "locale",
194663
+ ".patterx": "authoring",
194664
+ ".patterproj": "project"
194665
+ };
194666
+ const out = [];
194667
+ for (const [ext, what] of Object.entries(kind)) {
194668
+ for (const file of walkFiles(loaded.root, ext)) {
194669
+ if (collected.has(file)) continue;
194670
+ out.push({ file, message: `a ${what} shard outside the project's layout - nothing loads it, so its content is not in the project (move it under the configured folder, or delete it)` });
194671
+ }
194672
+ }
194673
+ return out;
194674
+ }
194170
194675
  function checkBundles(loaded) {
194171
194676
  const issues = [];
194172
194677
  const bundles = walkFiles(loaded.root, ".patterc");
@@ -194246,7 +194751,16 @@ function compileScenes(loaded) {
194246
194751
  blocks: s.blocks.map((b) => ({ ...b, children: b.children.map(clean3) }))
194247
194752
  }));
194248
194753
  }
194754
+ function refuseUnresolvedMerge(loaded) {
194755
+ const issues = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
194756
+ if (!issues.length) return;
194757
+ throw new Error(
194758
+ `${issues.length} unresolved merge conflict(s) - resolve them and delete the .patterconflict sidecar(s) first:
194759
+ ` + issues.map((i) => ` ${i.file}`).join("\n")
194760
+ );
194761
+ }
194249
194762
  function runExport(loaded) {
194763
+ refuseUnresolvedMerge(loaded);
194250
194764
  const { project, locales } = loaded;
194251
194765
  const full = exportBundle({ project, scenes: compileScenes(loaded), locales });
194252
194766
  const loc = project.export?.localisation;
@@ -194255,6 +194769,7 @@ function runExport(loaded) {
194255
194769
  return { ...full, strings, localisation: { mode: "ids", ...loc.sourceDebug ? { sourceDebug: true } : {} } };
194256
194770
  }
194257
194771
  function runExportFull(loaded) {
194772
+ refuseUnresolvedMerge(loaded);
194258
194773
  const { project, locales } = loaded;
194259
194774
  return exportBundle({ project, scenes: compileScenes(loaded), locales });
194260
194775
  }
@@ -194266,7 +194781,7 @@ function bundleOutputPath(loaded) {
194266
194781
  }
194267
194782
 
194268
194783
  // ../ops/src/playable-runtime.ts
194269
- var PLAYABLE_RUNTIME_JS = '"use strict";var Patterplay=(()=>{var A=Object.defineProperty;var he=Object.getOwnPropertyDescriptor;var me=Object.getOwnPropertyNames;var Se=Object.prototype.hasOwnProperty;var ye=(n,e)=>{for(var t in e)A(n,t,{get:e[t],enumerable:!0})},ve=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of me(e))!Se.call(n,s)&&s!==t&&A(n,s,{get:()=>e[s],enumerable:!(r=he(e,s))||r.enumerable});return n};var be=n=>ve(A({},"__esModule",{value:!0}),n);var Ge={};ye(Ge,{Engine:()=>O,Flow:()=>w,buildTagIndex:()=>B,describeBundle:()=>pe,effectiveGameData:()=>ue,gameDataFields:()=>de,gameDataValue:()=>_});function S(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:S(n[2])};case"bin":return{kind:"binary",op:n[1],left:S(n[2]),right:S(n[3])};case"call":{let e=n.slice(2).map(S);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var p=class extends Error{constructor(e){super(e),this.name="EvalError"}};function C(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=e.scopes[i.scope];if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):o[i.name];if(a===void 0){if(r.get(i.scope)==="throw")throw new p(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{let o=t.functions[i.name];if(!o)throw new p(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new p("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 p(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new p(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let c=s(i.left);if(typeof c!="boolean")throw new p(`\'and\' requires boolean operands, left is ${typeof c}`);if(!c)return!1;let l=s(i.right);if(typeof l!="boolean")throw new p(`\'and\' requires boolean operands, right is ${typeof l}`);return l}if(i.op==="or"){let c=s(i.left);if(typeof c!="boolean")throw new p(`\'or\' requires boolean operands, left is ${typeof c}`);if(c)return!0;let l=s(i.right);if(typeof l!="boolean")throw new p(`\'or\' requires boolean operands, right is ${typeof l}`);return l}let o=s(i.left),a=s(i.right);switch(i.op){case"==":return J(o,a);case"!=":return!J(o,a);case">":return y(o,a,">"),o>a;case">=":return y(o,a,">="),o>=a;case"<":return y(o,a,"<"),o<a;case"<=":return y(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new p(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return y(o,a,"-"),o-a;case"*":return y(o,a,"*"),o*a;case"/":if(y(o,a,"/"),a===0)throw new p("division by zero");return o/a}}}};return s(n)}function J(n,e){if(Array.isArray(n)||Array.isArray(e)){if(!Array.isArray(n)||!Array.isArray(e)||n.length!==e.length)return!1;for(let t=0;t<n.length;t++)if(n[t]!==e[t])return!1;return!0}return n===e}function y(n,e,t){if(typeof n!="number"||typeof e!="number")throw new p(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}var we={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},xe=[we];function z(n,e,t){let r=t?.countingCalls??xe;return E(n,t?.want??!0,e,r)}function E(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=E(n.left,e,t,r),i=E(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 E(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 G=class n{values={};decls=new Map;subscribers=new Set;auditors=new Set;norm;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),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??Y(t))}}get(e){return this.values[this.norm(e)]}set(e,t,r){let s=this.norm(e);if(this.decls.get(s)?.writable===!1)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])=>X(t,this.get(e),void 0,e))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm});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 X(n,e,t,r){return{name:r??n.name.toLowerCase(),type:n.type,value:e,default:n.default??Y(n),...n.values!==void 0?{values:n.values}:{},writable:t??n.writable??!0}}var F=1,x=class{scopes=new Map;defineOwned(e,t){return this.mountOwned(e,new G(t))}mountOwned(e,t){return this.assertFree(e),this.scopes.set(e,{kind:"owned",bag:t}),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){this.assertFree(e);let i=new Map;for(let o of r)i.set(o.name.toLowerCase(),o);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:i,scopeWritable:s}),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(t.toLowerCase())}set(e,t,r){let s=this.scopes.get(e);if(!s)throw new Error(`unknown scope \'@${e}\'`);if(s.kind==="owned"){try{s.bag.set(t,r)}catch{throw new Error(`\'@${e}.${t}\' is read-only`)}return}let i=t.toLowerCase();if(!this.foreignWritable(s,i))throw new Error(`\'@${e}.${t}\' is read-only`);s.resolver.set(i,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)if(r.kind==="owned")for(let s of r.bag.rows())e.push({scope:t,...s});else for(let s of r.decls.values())e.push({scope:t,...X(s,r.resolver.get(s.name.toLowerCase()),this.foreignWritable(r,s.name.toLowerCase()))});return e}toEvalContext(e){let t={};for(let[r,s]of this.scopes)t[r]=s.kind==="owned"?s.bag.values:s.resolver;return{scopes:t,host:e}}toSchema(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];if(s.length===0)continue;let i=new Map;for(let o of s)i.set(o.name.toLowerCase(),{type:o.type,enumValues:o.values});e.set(t,i)}return{properties:e}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());return e}load(e){for(let[t,r]of Object.entries(e)){let s=this.scopes.get(t);s?.kind==="owned"&&s.bag.load(r)}}saveFragment(){return{version:F,scopes:this.save()}}loadFragment(e){if(e.version!==F)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${F})`);this.load(e.scopes)}assertFree(e){if(this.scopes.has(e))throw new Error(`scope \'@${e}\' is already registered`)}};function Y(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[]}}function k(n){return n.ctx.host??{}}var T={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new p("random(a, b) requires exactly 2 arguments");let t=k(e).nextRandom;if(!t)throw new p("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 p("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new p("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:Q("check_flags"),eval(n,e){let t=Z(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new p("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:Q("set_flags"),eval(n,e){let t=[...Z(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new p("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:I("visits"),eval:(n,e)=>k(e).visits?.(D(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:I("seen"),eval:(n,e)=>(k(e).visits?.(D(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:I("patter_visits"),eval:(n,e)=>k(e).patterVisits?.(D(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:I("patter_seen"),eval:(n,e)=>(k(e).patterVisits?.(D(n,e,"patter_seen"))??0)>0}}};function V(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 D(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new p(`${t}(id) requires a string node id`);return r}function I(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 Z(n,e,t){if(!n)throw new p(`${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 p(`${t}() first argument must be a flags property`)}function Q(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 ke=/^@[A-Za-z0-9_.]+$/;function*Ce(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:ke.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 Ee(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function De(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function Ie(n){let e="",t=!1;for(let r of n){if(De(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function ee(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?Ie(r):n}function te(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of Ce(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?"":Ee(s)}return t}function b(n,e){for(let t of n){e(t);let r=t.children;r&&b(r,e)}}function Re(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function g(n){let e=n.gameId?.trim();return e||Re(n.name)}function M(n){return`cast:${n}`}var j={open:"[",close:"]"},re="SFX";function R(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function B(n){let e=new Map,t=(r,s)=>{let i=R([...s,...r.tags??[]]);if(e.set(r.id,i),r.type==="group")for(let o of r.children)t(o,i);else for(let o of r.beats??[])e.set(o.id,R([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=R(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=R([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var ne=new WeakMap,O=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;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);this.defaultSeed=(t.seed??2654435769)>>>0;let u=new Map,f=new Map,H=new Map;for(let[d,m]of Object.entries(e.scenes)){this.sceneGameIdToId.set(g(m),d);let v=new Map;for(let h of m.blocks)f.set(h.id,{sceneId:d}),H.set(h.id,h),v.set(g(h),h.id),b(h.children,K=>u.set(K.id,K));this.blockGameIdToId.set(d,v)}let q=e.properties??[],P=q.filter(d=>d.shared??!0).map(ie),ge=q.filter(d=>!(d.shared??!0)).map(ie),fe=new Set(P.map(d=>d.name.toLowerCase())),N=new x().defineOwned("patter",P),U=new Set;if(t.world){let d=e.scopeRegistry?.scopes.find(v=>v.token==="world"),m=(d?.declarations??[]).map(oe);N.defineForeign("world",t.world,m,d?.writable??!0),U.add("world")}for(let d of e.scopeRegistry?.scopes??[]){if(U.has(d.token))continue;let m=(d.declarations??[]).map(oe);N.defineForeign(d.token,Pe(d.declarations??[]),m,d.writable??!0)}let W=new Map;for(let[d,m]of Object.entries(e.scenes)){let v=new Set((m.sceneProps??[]).filter(h=>h.shared??!1).map(h=>h.name.toLowerCase()));W.set(d,v)}this.host={bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,nodeIndex:u,blockIndex:f,blockById:H,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:B(e),shared:N,patterSharedDecls:P,patterLocalDecls:ge,patterSharedNames:fe,sceneSharedNames:W,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??j).open,captionClose:(e.closedCaptions??j).close,captionCharacter:e.closedCaptions?.character||re,refSplitCache:new Map}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}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){let t=this.saveGame(),r=i=>(i.setLocale(this.currentLocale),i.setClosedCaptions(this.host.captionsOn),i),s=new n(e,this.creationOptions);try{return s.loadGame(t),r(s)}catch{let i=new n(e,this.creationOptions);for(let[o,a]of Object.entries(t.flows)){let c=a.cursor.currentSceneId;try{i.openFlow(o,c!==null?{scene:c}:{})}catch{}}return r(i)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){let r=this.resolveSceneRef(t.scene),s=this.resolveBlockRef(r,t.block);this.flowsById.get(e)?.close();let i=new w(e,this.host,t.seed??this.defaultSeed);return this.flowsById.set(e,i),i.start(r,s),i}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}resolveSceneRef(e){if(e!=null)return this.host.bundle.scenes[e]?e:this.sceneGameIdToId.get(e)??e}resolveBlockRef(e,t){if(t!=null){if(this.host.blockById.has(t))return t;if(e!=null){let r=this.blockGameIdToId.get(e)?.get(t);if(r)return r}return t}}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?g(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?g(t):void 0}tagsForBeat(e){return this.host.tagIndex.get(e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?this.host.tagIndex.get(t):void 0)??[]}tagsForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t);return(s!=null?this.host.tagIndex.get(s):void 0)??[]}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)se(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 se(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...g(e)?{gameId:g(e)}:{},name:e.name,...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...g(t)?{gameId:g(t)}:{},name:t.name,...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)b(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=this.host.tagIndex.get(e.id),r={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){r.character=e.character;let s=this.host.defaultStrings[M(e.character)]??this.host.castDisplay.get(e.character);s!==void 0&&(r.characterName=s)}e.direction!==void 0&&(r.direction=e.direction)}if(e.kind==="line"||e.kind==="text"){let s=this.host.defaultStrings[e.id];s!==void 0&&(r.text=s)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=this.host.tagIndex.get(e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.shared.reseedOwned("patter",this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear(),this.host.stageBags.clear()}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e);this.host.shared.set(r,s,t)}listProperties(){return this.host.patterSharedDecls.map(e=>({ref:`@${e.name}`,type:e.type,values:e.values,value:this.getProperty(`@${e.name}`),default:Be(e)}))}splitShared(e){let t=this.host.refSplitCache.get(e);if(t||(t=V(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),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 this.host.shared.save()}load(e){this.host.shared.load(e)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:2,shared:this.host.shared.save(),sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:ce(this.host.sharedSelectors),stageBags:Object.fromEntries([...this.host.stageBags].map(([t,r])=>[t,{...r}])),flows:e}}loadGame(e){if(e.version!==2)throw new Error(`unsupported save version: ${e.version}`);this.host.shared.load(e.shared),this.host.sharedVisits.clear();for(let[t,r]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(t,r);this.host.sharedSelectors.clear();for(let[t,r]of le(e.sharedSelectors))this.host.sharedSelectors.set(t,r);this.host.stageBags.clear();for(let[t,r]of Object.entries(e.stageBags??{}))this.host.stageBags.set(t,{...r});this.flowsById.clear();for(let[t,r]of Object.entries(e.flows)){let s=new w(t,this.host,this.defaultSeed);s.restore(r),this.flowsById.set(t,s)}}},w=class{id;host;local;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPromptBeat=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.shared.get("patter",e):this.local.get("patter",e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.shared.set("patter",e,t):this.local.set("patter",e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;return t===null?void 0:(this.host.sceneSharedNames.get(t)?.has(e)?this.host.stageBags.get(t):this.sceneBags.get(t))?.[e]},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.host.sceneSharedNames.get(r)?.has(e)?this.host.stageBags.get(r):this.sceneBags.get(r);s&&(s[e]=t)}};evalCtx;constructor(e,t,r){this.id=e,this.host=t,this.rngState=r>>>0,this.local=this.freshLocal();let s={...t.shared.toEvalContext().scopes};s.patter=this.patterResolver,s.scene=this.sceneResolver,this.evalCtx={scopes:s,host:{nextRandom:this.rng,visits:i=>this.visitCounts.get(i)??0,patterVisits:i=>this.host.sharedVisits.get(i)??0}}}start(e,t){if(this.sceneBags.clear(),this.local=this.freshLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,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.start(e,t)}goto(e,t){if(this.closed)return!1;if(e==="END")return this.started=!0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=this.host.sceneGameIdToId.get(e)??(this.host.bundle.scenes[e]?e:void 0);if(r===void 0)return!1;let s;return t!==void 0&&(s=this.host.blockGameIdToId.get(r)?.get(t)??(this.host.blockIndex.get(t)?.sceneId===r?t:void 0),s===void 0)?!1:this.started?(this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??r,"jump"),this.settle(),!0):(this.start(r,s),!0)}close(){this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null}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.pendingPromptBeat){let e=this.pendingPromptBeat;return this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.beatResult(e)}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);let s=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(s);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}for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}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(i=>i.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);let s=t.byId.get(e);this.pendingChoice=null,this.pendingPromptBeat=this.host.replayPromptOnChoose?this.promptBeatOf(s)??null:null,this.pendingPromptOwnerId=this.pendingPromptBeat?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.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitRef(e);if(r==="patter")this.patterResolver.set(s,t);else if(r==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);this.sceneResolver.set(s,t)}else this.host.shared.set(r,s,t)}snapshot(){return{scopes:this.local.save(),sceneBags:Object.fromEntries([...this.sceneBags].map(([e,t])=>[e,{...t}])),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,selectors:ce(this.selectors)}}}restore(e){this.rngState=e.rngState>>>0,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.sceneBags=new Map(Object.entries(e.sceneBags??{}).map(([r,s])=>[r,{...s}])),this.local=this.freshLocal(),this.local.load(e.scopes),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=le(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);o&&(r.set(i.id,o),s.push({...i}))}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:r})}if(this.pendingPromptBeat=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId);this.pendingPromptBeat=r?this.promptBeatOf(r)??null:null,this.pendingPromptBeat||(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)}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),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.length>0){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.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(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.filter(s=>this.eligible(s));if(t.length===0)return null;let r=this.selectorState(e);switch(e.selector){case"branch":return t[0];case"sequence":{let s=e.options?.order??"sequential",i=e.options?.exhaust??"once";return s==="shuffle"?this.pickShuffle(t,i,r):s==="specificity"?this.pickSpecificity(t,i,r):this.pickSequential(t,i,r)}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(f=>f.id);if(r.bag===void 0&&(r.bag=o()),r.bag.length===0){if(t==="once")return null;if(i){let f=e[s-1];return r.last=f.id,f}r.bag=o()}let a=r.bag,c=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,l=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&l>=c&&l++;let u=a[l];return a.splice(l,1),r.last=u,e.find(f=>f.id===u)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(u=>u.id));let l=new Set(r.bag);if(s=e.filter(u=>l.has(u.id)),s.length===0)return t==="stick"&&r.last!==void 0?e.find(u=>u.id===r.last)??null:null}let i=-1,a=s.map(l=>{let u=this.specScore(l);return u>i&&(i=u),{c:l,s:u}}).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(f=>f.id===r.last):-1,u=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&u>=l&&u++,c=a[u]}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):0}matchedSpec(e,t){return z(e,s=>ae(C(s,this.evalCtx,T)),{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=t.get(e.id);return r||(r={},t.set(e.id,r)),r}runEffects(e){for(let t of e??[])this.setProperty(t.target,this.evalExpr(t.value))}eligible(e){return e.condition?ae(this.evalExpr(e.condition)):!0}evalExpr(e){return C(this.conditionAst(e),this.evalCtx,T)}conditionAst(e){let t=ne.get(e);return t||(t=S(e.ast),ne.set(e,t)),t}enter(e){this.visitCounts.set(e,(this.visitCounts.get(e)??0)+1),this.host.sharedVisits.set(e,(this.host.sharedVisits.get(e)??0)+1)}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=this.rngState+1831565813|0;this.rngState=e;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};beatResult(e){let t=this.host.tagIndex.get(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)),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,gameData:e.gameData,...r}}}}interpolate(e){return te(e,t=>this.getProperty(t))}stripCaptions(e){return ee(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));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}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 b(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=M(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){let t=this.host.refSplitCache.get(e);return t||(t=V(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t}freshLocal(){return new x().defineOwned("patter",this.host.patterLocalDecls)}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;if(!this.sceneBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)||(r[i]=L(s))}this.sceneBags.set(e.id,r)}if(!this.host.stageBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)&&(r[i]=L(s))}this.host.stageBags.set(e.id,r)}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);i&&(i[s]=L(r))}}};function se(n,e){b(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 ce(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 le(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 ie(n){return{name:n.name,type:n.type,values:n.values,default:n.default}}function Be(n){if(n.default!==void 0)return n.default;switch(n.type){case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";default:return!1}}function oe(n){return{name:n.name,type:n.type,values:n.values,default:n.default,writable:n.writable}}function Oe(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??""}}function Pe(n){let e=r=>r.toLowerCase(),t=new Map;for(let r of n)t.set(e(r.name),Oe(r));return{get:r=>t.get(e(r)),set:(r,s)=>{t.set(e(r),s)}}}function L(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??""}}function ae(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Ne=(n,e)=>n.shared??e;function $(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:Ne(n,e)}}function Ae(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function Fe(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 pe(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=g(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:g(c),name:c.name}))});for(let c of o.blocks)Fe(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>$(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=>$(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Ae)}));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=>$(o,!0)),scene:r},gameData:i,counts:e}}function de(n,e){return n.gameDataFields?.[e]??[]}function _(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function ue(n,e){let t={};for(let r of n){let s=_(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 be(Ge);})();\n//# sourceMappingURL=patterplay.min.js.map';
194784
+ var PLAYABLE_RUNTIME_JS = '"use strict";var Patterplay=(()=>{var G=Object.defineProperty;var Se=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var ve=Object.prototype.hasOwnProperty;var be=(n,e)=>{for(var t in e)G(n,t,{get:e[t],enumerable:!0})},we=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ye(e))!ve.call(n,s)&&s!==t&&G(n,s,{get:()=>e[s],enumerable:!(r=Se(e,s))||r.enumerable});return n};var xe=n=>we(G({},"__esModule",{value:!0}),n);var Me={};be(Me,{Engine:()=>N,Flow:()=>k,buildTagIndex:()=>P,describeBundle:()=>ue,effectiveGameData:()=>fe,gameDataFields:()=>ge,gameDataValue:()=>U});function v(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:v(n[2])};case"bin":return{kind:"binary",op:n[1],left:v(n[2]),right:v(n[3])};case"call":{let e=n.slice(2).map(v);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var l=class extends Error{constructor(e){super(e),this.name="EvalError"}};function D(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=e.scopes[i.scope];if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):o[i.name];if(a===void 0){if(r.get(i.scope)==="throw")throw new l(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!t.functions[i.name]){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new l(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=T(a,e);if(c===void 0)throw new l("advance() needs a quality reference (@scope.name of a quality property)");let d=S(s(a),c,"advance");return c[Math.min(d+1,c.length-1)]}let o=t.functions[i.name];if(!o)throw new l(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new l("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 l(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new l(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let g=s(i.left);if(typeof g!="boolean")throw new l(`\'and\' requires boolean operands, left is ${typeof g}`);if(!g)return!1;let h=s(i.right);if(typeof h!="boolean")throw new l(`\'and\' requires boolean operands, right is ${typeof h}`);return h}if(i.op==="or"){let g=s(i.left);if(typeof g!="boolean")throw new l(`\'or\' requires boolean operands, left is ${typeof g}`);if(g)return!0;let h=s(i.right);if(typeof h!="boolean")throw new l(`\'or\' requires boolean operands, right is ${typeof h}`);return h}let o=s(i.left),a=s(i.right),c=T(i.left,e),d=T(i.right,e),p=c??d;if(p!==void 0){if(c&&d&&!ke(c,d)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new l(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return S(o,p,">")>S(a,p,">");case">=":return S(o,p,">=")>=S(a,p,">=");case"<":return S(o,p,"<")<S(a,p,"<");case"<=":return S(o,p,"<=")<=S(a,p,"<=");case"+":case"-":case"*":case"/":throw new l(`\'${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 X(o,a);case"!=":return!X(o,a);case">":return b(o,a,">"),o>a;case">=":return b(o,a,">="),o>=a;case"<":return b(o,a,"<"),o<a;case"<=":return b(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new l(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return b(o,a,"-"),o-a;case"*":return b(o,a,"*"),o*a;case"/":if(b(o,a,"/"),a===0)throw new l("division by zero");return o/a}}}};return s(n)}function X(n,e){if(Array.isArray(n)||Array.isArray(e)){if(!Array.isArray(n)||!Array.isArray(e)||n.length!==e.length)return!1;for(let t=0;t<n.length;t++)if(n[t]!==e[t])return!1;return!0}return n===e}function b(n,e,t){if(typeof n!="number"||typeof e!="number")throw new l(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}function T(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function S(n,e,t){if(typeof n!="string")throw new l(`\'${t}\' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new l(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var ke=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);var Ce={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},Ee=[Ce];function Y(n,e,t){let r=t?.countingCalls??Ee;return I(n,t?.want??!0,e,r)}function I(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=I(n.left,e,t,r),i=I(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 I(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 M=class n{values={};decls=new Map;subscribers=new Set;auditors=new Set;norm;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),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??Q(t))}}get(e){return this.values[this.norm(e)]}set(e,t,r){let s=this.norm(e);if(this.decls.get(s)?.writable===!1)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])=>Z(t,this.get(e),void 0,e))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm});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 Z(n,e,t,r){return{name:r??n.name.toLowerCase(),type:n.type,value:e,default:n.default??Q(n),...n.values!==void 0?{values:n.values}:{},writable:t??n.writable??!0}}var V=1,C=class{scopes=new Map;defineOwned(e,t){return this.mountOwned(e,new M(t))}mountOwned(e,t){return this.assertFree(e),this.scopes.set(e,{kind:"owned",bag:t}),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){this.assertFree(e);let i=new Map;for(let o of r)i.set(o.name.toLowerCase(),o);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:i,scopeWritable:s}),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(t.toLowerCase())}set(e,t,r){let s=this.scopes.get(e);if(!s)throw new Error(`unknown scope \'@${e}\'`);if(s.kind==="owned"){try{s.bag.set(t,r)}catch{throw new Error(`\'@${e}.${t}\' is read-only`)}return}let i=t.toLowerCase();if(!this.foreignWritable(s,i))throw new Error(`\'@${e}.${t}\' is read-only`);s.resolver.set(i,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)if(r.kind==="owned")for(let s of r.bag.rows())e.push({scope:t,...s});else for(let s of r.decls.values())e.push({scope:t,...Z(s,r.resolver.get(s.name.toLowerCase()),this.foreignWritable(r,s.name.toLowerCase()))});return e}toEvalContext(e){let t={};for(let[s,i]of this.scopes)t[s]=i.kind==="owned"?i.bag.values:i.resolver;let r=this.qualityLadders();return r.size===0?{scopes:t,host:e}:{scopes:t,host:e,qualities:(s,i)=>r.get(s)?.get(i.toLowerCase())}}qualityLadders(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];for(let i of s){if(i.type!=="quality"||i.stages===void 0)continue;let o=e.get(t);o||(o=new Map,e.set(t,o)),o.set(i.name.toLowerCase(),i.stages)}}return e}toSchema(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];if(s.length===0)continue;let i=new Map;for(let o of s)i.set(o.name.toLowerCase(),{type:o.type,enumValues:o.values,...o.stages!==void 0?{stages:o.stages}:{}});e.set(t,i)}return{properties:e}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());return e}load(e){for(let[t,r]of Object.entries(e)){let s=this.scopes.get(t);s?.kind==="owned"&&s.bag.load(r)}}saveFragment(){return{version:V,scopes:this.save()}}loadFragment(e){if(e.version!==V)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${V})`);this.load(e.scopes)}assertFree(e){if(this.scopes.has(e))throw new Error(`scope \'@${e}\' is already registered`)}};function Q(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]??""}}function E(n){return n.ctx.host??{}}var L={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new l("random(a, b) requires exactly 2 arguments");let t=E(e).nextRandom;if(!t)throw new l("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 l("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new l("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:te("check_flags"),eval(n,e){let t=ee(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new l("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:te("set_flags"),eval(n,e){let t=[...ee(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new l("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:B("visits"),eval:(n,e)=>E(e).visits?.(R(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:B("seen"),eval:(n,e)=>(E(e).visits?.(R(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:B("patter_visits"),eval:(n,e)=>E(e).patterVisits?.(R(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:B("patter_seen"),eval:(n,e)=>(E(e).patterVisits?.(R(n,e,"patter_seen"))??0)>0}}};function j(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 R(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new l(`${t}(id) requires a string node id`);return r}function B(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 ee(n,e,t){if(!n)throw new l(`${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 l(`${t}() first argument must be a flags property`)}function te(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 De=/^@[A-Za-z0-9_.]+$/;function*Ie(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:De.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 Re(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function Be(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function Oe(n){let e="",t=!1;for(let r of n){if(Be(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function re(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?Oe(r):n}function ne(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of Ie(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?"":Re(s)}return t}function x(n,e){for(let t of n){e(t);let r=t.children;r&&x(r,e)}}function Pe(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function f(n){let e=n.gameId?.trim();return e||Pe(n.name)}function $(n){return`cast:${n}`}var _={open:"[",close:"]"},se="SFX";function O(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function P(n){let e=new Map,t=(r,s)=>{let i=O([...s,...r.tags??[]]);if(e.set(r.id,i),r.type==="group")for(let o of r.children)t(o,i);else for(let o of r.beats??[])e.set(o.id,O([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=O(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=O([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var ie=new WeakMap,N=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;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 d=new Map;for(let u of e.cast??[])u.displayName&&d.set(u.name,u.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let p=new Map,g=new Map,h=new Map;for(let[u,y]of Object.entries(e.scenes)){this.sceneGameIdToId.set(f(y),u);let w=new Map;for(let m of y.blocks)g.set(m.id,{sceneId:u}),h.set(m.id,m),w.set(f(m),m.id),x(m.children,J=>p.set(J.id,J));this.blockGameIdToId.set(u,w)}let W=e.properties??[],A=W.filter(u=>u.shared??!0).map(ae),he=W.filter(u=>!(u.shared??!0)).map(ae),me=new Set(A.map(u=>u.name.toLowerCase())),F=new C().defineOwned("patter",A),K=new Set;if(t.world){let u=e.scopeRegistry?.scopes.find(w=>w.token==="world"),y=(u?.declarations??[]).map(ce);F.defineForeign("world",t.world,y,u?.writable??!0),K.add("world")}for(let u of e.scopeRegistry?.scopes??[]){if(K.has(u.token))continue;let y=(u.declarations??[]).map(ce);F.defineForeign(u.token,Fe(u.declarations??[]),y,u.writable??!0)}let z=new Map;for(let[u,y]of Object.entries(e.scenes)){let w=new Set((y.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));z.set(u,w)}this.host={bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:d,nodeIndex:p,blockIndex:g,blockById:h,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:P(e),shared:F,patterSharedDecls:A,patterLocalDecls:he,patterSharedNames:me,sceneSharedNames:z,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??_).open,captionClose:(e.closedCaptions??_).close,captionCharacter:e.closedCaptions?.character||se,refSplitCache:new Map}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}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){let t=this.saveGame(),r=i=>(i.setLocale(this.currentLocale),i.setClosedCaptions(this.host.captionsOn),i),s=new n(e,this.creationOptions);try{return s.loadGame(t),r(s)}catch{let i=new n(e,this.creationOptions);for(let[o,a]of Object.entries(t.flows)){let c=a.cursor.currentSceneId;try{i.openFlow(o,c!==null?{scene:c}:{})}catch{}}return r(i)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){let r=this.resolveSceneRef(t.scene),s=this.resolveBlockRef(r,t.block);this.flowsById.get(e)?.close();let i=new k(e,this.host,t.seed??this.defaultSeed);return this.flowsById.set(e,i),i.start(r,s),i}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}resolveSceneRef(e){if(e!=null)return this.host.bundle.scenes[e]?e:this.sceneGameIdToId.get(e)??e}resolveBlockRef(e,t){if(t!=null){if(this.host.blockById.has(t))return t;if(e!=null){let r=this.blockGameIdToId.get(e)?.get(t);if(r)return r}return t}}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?f(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?f(t):void 0}tagsForBeat(e){return this.host.tagIndex.get(e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?this.host.tagIndex.get(t):void 0)??[]}tagsForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t);return(s!=null?this.host.tagIndex.get(s):void 0)??[]}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)oe(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 oe(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...f(e)?{gameId:f(e)}:{},name:e.name,...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...f(t)?{gameId:f(t)}:{},name:t.name,...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)x(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=this.host.tagIndex.get(e.id),r={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){r.character=e.character;let s=this.host.defaultStrings[$(e.character)]??this.host.castDisplay.get(e.character);s!==void 0&&(r.characterName=s)}e.direction!==void 0&&(r.direction=e.direction)}if(e.kind==="line"||e.kind==="text"){let s=this.host.defaultStrings[e.id];s!==void 0&&(r.text=s)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=this.host.tagIndex.get(e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.shared.reseedOwned("patter",this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear(),this.host.stageBags.clear()}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e);this.host.shared.set(r,s,t)}listProperties(){return this.host.patterSharedDecls.map(e=>({ref:`@${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:Ne(e)}))}splitShared(e){let t=this.host.refSplitCache.get(e);if(t||(t=j(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),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 this.host.shared.save()}load(e){this.host.shared.load(e)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:2,shared:this.host.shared.save(),sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:pe(this.host.sharedSelectors),stageBags:Object.fromEntries([...this.host.stageBags].map(([t,r])=>[t,{...r}])),flows:e}}loadGame(e){if(e.version!==2)throw new Error(`unsupported save version: ${e.version}`);this.host.shared.load(e.shared),this.host.sharedVisits.clear();for(let[t,r]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(t,r);this.host.sharedSelectors.clear();for(let[t,r]of de(e.sharedSelectors))this.host.sharedSelectors.set(t,r);this.host.stageBags.clear();for(let[t,r]of Object.entries(e.stageBags??{}))this.host.stageBags.set(t,{...r});this.flowsById.clear();for(let[t,r]of Object.entries(e.flows)){let s=new k(t,this.host,this.defaultSeed);s.restore(r),this.flowsById.set(t,s)}}},k=class{id;host;local;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPromptBeat=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.shared.get("patter",e):this.local.get("patter",e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.shared.set("patter",e,t):this.local.set("patter",e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;return t===null?void 0:(this.host.sceneSharedNames.get(t)?.has(e)?this.host.stageBags.get(t):this.sceneBags.get(t))?.[e]},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.host.sceneSharedNames.get(r)?.has(e)?this.host.stageBags.get(r):this.sceneBags.get(r);s&&(s[e]=t)}};evalCtx;constructor(e,t,r){this.id=e,this.host=t,this.rngState=r>>>0,this.local=this.freshLocal();let s={...t.shared.toEvalContext().scopes};s.patter=this.patterResolver,s.scene=this.sceneResolver,this.evalCtx={scopes:s,host:{nextRandom:this.rng,visits:i=>this.visitCounts.get(i)??0,patterVisits:i=>this.host.sharedVisits.get(i)??0},qualities:(i,o)=>this.stagesFor(i,o)}}stagesFor(e,t){let r=t.toLowerCase(),s=i=>i?.find(o=>o.name.toLowerCase()===r&&o.type==="quality")?.stages;if(e==="patter")return s(this.host.patterSharedDecls)??s(this.host.patterLocalDecls);if(e==="scene"){let i=this.currentSceneId!=null?this.host.bundle.scenes[this.currentSceneId]:void 0;return s(i?.sceneProps)}return s(this.host.bundle.scopeRegistry?.scopes.find(i=>i.token===e)?.declarations)}start(e,t){if(this.sceneBags.clear(),this.local=this.freshLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,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.start(e,t)}goto(e,t){if(this.closed)return!1;if(e==="END")return this.started=!0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=this.host.sceneGameIdToId.get(e)??(this.host.bundle.scenes[e]?e:void 0);if(r===void 0)return!1;let s;return t!==void 0&&(s=this.host.blockGameIdToId.get(r)?.get(t)??(this.host.blockIndex.get(t)?.sceneId===r?t:void 0),s===void 0)?!1:this.started?(this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??r,"jump"),this.settle(),!0):(this.start(r,s),!0)}close(){this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null}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.pendingPromptBeat){let e=this.pendingPromptBeat;return this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.beatResult(e)}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);let s=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(s);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}for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}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(i=>i.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);let s=t.byId.get(e);this.pendingChoice=null,this.pendingPromptBeat=this.host.replayPromptOnChoose?this.promptBeatOf(s)??null:null,this.pendingPromptOwnerId=this.pendingPromptBeat?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.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitRef(e);if(r==="patter")this.patterResolver.set(s,t);else if(r==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);this.sceneResolver.set(s,t)}else this.host.shared.set(r,s,t)}snapshot(){return{scopes:this.local.save(),sceneBags:Object.fromEntries([...this.sceneBags].map(([e,t])=>[e,{...t}])),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,selectors:pe(this.selectors)}}}restore(e){this.rngState=e.rngState>>>0,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.sceneBags=new Map(Object.entries(e.sceneBags??{}).map(([r,s])=>[r,{...s}])),this.local=this.freshLocal(),this.local.load(e.scopes),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=de(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);o&&(r.set(i.id,o),s.push({...i}))}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:r})}if(this.pendingPromptBeat=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId);this.pendingPromptBeat=r?this.promptBeatOf(r)??null:null,this.pendingPromptBeat||(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)}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),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.length>0){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.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(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.filter(s=>this.eligible(s));if(t.length===0)return null;let r=this.selectorState(e);switch(e.selector){case"branch":return t[0];case"sequence":{let s=e.options?.order??"sequential",i=e.options?.exhaust??"once";return s==="shuffle"?this.pickShuffle(t,i,r):s==="specificity"?this.pickSpecificity(t,i,r):this.pickSequential(t,i,r)}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);if(r.bag===void 0&&(r.bag=o()),r.bag.length===0){if(t==="once")return null;if(i){let g=e[s-1];return r.last=g.id,g}r.bag=o()}let a=r.bag,c=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,d=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&d>=c&&d++;let p=a[d];return a.splice(d,1),r.last=p,e.find(g=>g.id===p)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(p=>p.id));let d=new Set(r.bag);if(s=e.filter(p=>d.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(d=>{let p=this.specScore(d);return p>i&&(i=p),{c:d,s:p}}).filter(d=>d.s===i).map(d=>d.c),c;if(a.length===1)c=a[0];else{let d=r.last!==void 0?a.findIndex(g=>g.id===r.last):-1,p=Math.floor(this.rng()*(d>=0?a.length-1:a.length));d>=0&&p>=d&&p++,c=a[p]}return t!=="repeat"&&(r.bag=r.bag.filter(d=>d!==c.id)),r.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0):0}matchedSpec(e,t){return Y(e,s=>le(D(s,this.evalCtx,L)),{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=t.get(e.id);return r||(r={},t.set(e.id,r)),r}runEffects(e){for(let t of e??[])this.setProperty(t.target,this.evalExpr(t.value))}eligible(e){return e.condition?le(this.evalExpr(e.condition)):!0}evalExpr(e){return D(this.conditionAst(e),this.evalCtx,L)}conditionAst(e){let t=ie.get(e);return t||(t=v(e.ast),ie.set(e,t)),t}enter(e){this.visitCounts.set(e,(this.visitCounts.get(e)??0)+1),this.host.sharedVisits.set(e,(this.host.sharedVisits.get(e)??0)+1)}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=this.rngState+1831565813|0;this.rngState=e;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};beatResult(e){let t=this.host.tagIndex.get(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)),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,gameData:e.gameData,...r}}}}interpolate(e){return ne(e,t=>this.getProperty(t))}stripCaptions(e){return re(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));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}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 x(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=$(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){let t=this.host.refSplitCache.get(e);return t||(t=j(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t}freshLocal(){return new C().defineOwned("patter",this.host.patterLocalDecls)}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;if(!this.sceneBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)||(r[i]=q(s))}this.sceneBags.set(e.id,r)}if(!this.host.stageBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)&&(r[i]=q(s))}this.host.stageBags.set(e.id,r)}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);i&&(i[s]=q(r))}}};function oe(n,e){x(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 pe(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 de(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 ae(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Ne(n){if(n.default!==void 0)return n.default;switch(n.type){case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??"";default:return!1}}function ce(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function Ae(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??""}}function Fe(n){let e=r=>r.toLowerCase(),t=new Map;for(let r of n)t.set(e(r.name),Ae(r));return{get:r=>t.get(e(r)),set:(r,s)=>{t.set(e(r),s)}}}function q(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??""}}function le(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Ge=(n,e)=>n.shared??e;function H(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:Ge(n,e)}}function Te(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function Ve(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 ue(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=f(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:f(c),name:c.name}))});for(let c of o.blocks)Ve(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>H(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=>H(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Te)}));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=>H(o,!0)),scene:r},gameData:i,counts:e}}function ge(n,e){return n.gameDataFields?.[e]??[]}function U(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function fe(n,e){let t={};for(let r of n){let s=U(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 xe(Me);})();\n//# sourceMappingURL=patterplay.min.js.map';
194270
194785
 
194271
194786
  // ../ops/src/export-html.ts
194272
194787
  var esc = (s) => s.replace(/[&<>]/g, (c2) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c2]);
@@ -194713,7 +195228,29 @@ var ScopeRegistry = class {
194713
195228
  for (const [token2, e] of this.scopes) {
194714
195229
  scopes[token2] = e.kind === "owned" ? e.bag.values : e.resolver;
194715
195230
  }
194716
- return { scopes, host: host2 };
195231
+ const qualities = this.qualityLadders();
195232
+ return qualities.size === 0 ? { scopes, host: host2 } : {
195233
+ scopes,
195234
+ host: host2,
195235
+ qualities: (scope, name) => qualities.get(scope)?.get(name.toLowerCase())
195236
+ };
195237
+ }
195238
+ /** Every quality declaration's ladder, keyed scope token then name. */
195239
+ qualityLadders() {
195240
+ const out = /* @__PURE__ */ new Map();
195241
+ for (const [token2, e] of this.scopes) {
195242
+ const decls = e.kind === "owned" ? e.bag.declarations() : [...e.decls.values()];
195243
+ for (const d of decls) {
195244
+ if (d.type !== "quality" || d.stages === void 0) continue;
195245
+ let m = out.get(token2);
195246
+ if (!m) {
195247
+ m = /* @__PURE__ */ new Map();
195248
+ out.set(token2, m);
195249
+ }
195250
+ m.set(d.name.toLowerCase(), d.stages);
195251
+ }
195252
+ }
195253
+ return out;
194717
195254
  }
194718
195255
  /**
194719
195256
  * Build the `ExpressionSchema` expr's validator consumes. Scopes with no
@@ -194726,7 +195263,11 @@ var ScopeRegistry = class {
194726
195263
  const decls = e.kind === "owned" ? e.bag.declarations() : [...e.decls.values()];
194727
195264
  if (decls.length === 0) continue;
194728
195265
  const m = /* @__PURE__ */ new Map();
194729
- for (const d of decls) m.set(d.name.toLowerCase(), { type: d.type, enumValues: d.values });
195266
+ for (const d of decls) m.set(d.name.toLowerCase(), {
195267
+ type: d.type,
195268
+ enumValues: d.values,
195269
+ ...d.stages !== void 0 ? { stages: d.stages } : {}
195270
+ });
194730
195271
  properties.set(token2, m);
194731
195272
  }
194732
195273
  return { properties };
@@ -194778,6 +195319,9 @@ function defaultFor(d) {
194778
195319
  return d.values?.[0] ?? "";
194779
195320
  case "flags":
194780
195321
  return [];
195322
+ // A quality starts at the first rung of its ladder.
195323
+ case "quality":
195324
+ return d.stages?.[0] ?? "";
194781
195325
  }
194782
195326
  }
194783
195327
 
@@ -195269,6 +195813,7 @@ var Engine = class _Engine {
195269
195813
  ref: `@${d.name}`,
195270
195814
  type: d.type,
195271
195815
  values: d.values,
195816
+ stages: d.stages,
195272
195817
  value: this.getProperty(`@${d.name}`),
195273
195818
  default: declDefault(d)
195274
195819
  }));
@@ -195401,8 +195946,27 @@ var Flow = class {
195401
195946
  nextRandom: this.rng,
195402
195947
  visits: (id2) => this.visitCounts.get(id2) ?? 0,
195403
195948
  patterVisits: (id2) => this.host.sharedVisits.get(id2) ?? 0
195404
- }
195405
- };
195949
+ },
195950
+ // The quality channel (expr 0.4.0): hands the evaluator a property's stage ladder, which is what
195951
+ // makes ordering compare by position and advance() step. Wired by hand because this context takes
195952
+ // only the registry's SCOPES (the patter/scene resolvers here are the flow's own merged views),
195953
+ // and because @scene declarations belong to whichever scene the flow is in RIGHT NOW.
195954
+ qualities: (scope, name) => this.stagesFor(scope, name)
195955
+ };
195956
+ }
195957
+ /** The stage ladder of `@scope.name` when it is a declared quality, else undefined. Names compare
195958
+ * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
195959
+ stagesFor(scope, name) {
195960
+ const key2 = name.toLowerCase();
195961
+ const fromDecls = (decls) => decls?.find((d) => d.name.toLowerCase() === key2 && d.type === "quality")?.stages;
195962
+ if (scope === "patter") {
195963
+ return fromDecls(this.host.patterSharedDecls) ?? fromDecls(this.host.patterLocalDecls);
195964
+ }
195965
+ if (scope === "scene") {
195966
+ const scene = this.currentSceneId != null ? this.host.bundle.scenes[this.currentSceneId] : void 0;
195967
+ return fromDecls(scene?.sceneProps);
195968
+ }
195969
+ return fromDecls(this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations);
195406
195970
  }
195407
195971
  // -- Host API -------------------------------------------------------------
195408
195972
  /** Begin this flow at a scene (and optionally a specific block within it). */
@@ -196165,7 +196729,7 @@ function deserialiseSelectors(rec) {
196165
196729
  return map2;
196166
196730
  }
196167
196731
  function toDecl(decl) {
196168
- return { name: decl.name, type: decl.type, values: decl.values, default: decl.default };
196732
+ return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };
196169
196733
  }
196170
196734
  function declDefault(d) {
196171
196735
  if (d.default !== void 0) return d.default;
@@ -196178,12 +196742,14 @@ function declDefault(d) {
196178
196742
  return [];
196179
196743
  case "enum":
196180
196744
  return d.values?.[0] ?? "";
196745
+ case "quality":
196746
+ return d.stages?.[0] ?? "";
196181
196747
  default:
196182
196748
  return false;
196183
196749
  }
196184
196750
  }
196185
196751
  function toForeignDecl(decl) {
196186
- return { name: decl.name, type: decl.type, values: decl.values, default: decl.default, writable: decl.writable };
196752
+ return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default, writable: decl.writable };
196187
196753
  }
196188
196754
  function hostScopeDefault(decl) {
196189
196755
  if (decl.default !== void 0) return decl.default;
@@ -196198,6 +196764,8 @@ function hostScopeDefault(decl) {
196198
196764
  return [];
196199
196765
  case "enum":
196200
196766
  return decl.values?.[0] ?? "";
196767
+ case "quality":
196768
+ return decl.stages?.[0] ?? "";
196201
196769
  }
196202
196770
  }
196203
196771
  function selfBackedResolver(decls) {
@@ -196224,6 +196792,8 @@ function sceneDefault(decl) {
196224
196792
  return [];
196225
196793
  case "enum":
196226
196794
  return decl.values?.[0] ?? "";
196795
+ case "quality":
196796
+ return decl.stages?.[0] ?? "";
196227
196797
  }
196228
196798
  }
196229
196799
  function truthy(v) {
@@ -196259,7 +196829,7 @@ function stringsByLocale(loaded) {
196259
196829
  }
196260
196830
  return byLocale;
196261
196831
  }
196262
- function mergeAuthoring(loaded) {
196832
+ function mergeAuthoring2(loaded) {
196263
196833
  const writing = /* @__PURE__ */ new Map();
196264
196834
  const recording = /* @__PURE__ */ new Map();
196265
196835
  const cut = /* @__PURE__ */ new Set();
@@ -196431,7 +197001,7 @@ function proposeCoverageDrivers(loaded) {
196431
197001
  const { written, proposals } = analyzeHostScopes(bundle, hostTokens);
196432
197002
  const declByRef = /* @__PURE__ */ new Map();
196433
197003
  for (const s of loaded.project.scopeRegistry?.scopes ?? []) {
196434
- for (const d of s.declarations ?? []) declByRef.set(`@${s.token}.${d.name}`, { type: d.type, values: d.values });
197004
+ for (const d of s.declarations ?? []) declByRef.set(`@${s.token}.${d.name}`, { type: d.type, values: d.values, stages: d.stages });
196435
197005
  }
196436
197006
  const drivers = [];
196437
197007
  for (const [ref, pool] of proposals) {
@@ -196441,6 +197011,7 @@ function proposeCoverageDrivers(loaded) {
196441
197011
  if (values.length === 0 && decl) {
196442
197012
  if (decl.type === "boolean") values = [true, false];
196443
197013
  else if (decl.type === "enum" && decl.values) values = [...decl.values];
197014
+ else if (decl.type === "quality" && decl.stages) values = [...decl.stages];
196444
197015
  }
196445
197016
  if (values.length === 0) continue;
196446
197017
  drivers.push({ ref, kind: "recurring", cadence: "sometimes", values: sortValues(values) });
@@ -196677,7 +197248,7 @@ function runReport(loaded, recordingOverride) {
196677
197248
  const thresholdIdx = estimatingOn ? writingIndex.get(est?.thresholdStatus ?? "") ?? 0 : -1;
196678
197249
  const defaultLines = est?.defaultLines ?? 0;
196679
197250
  const tagMap = new Map((est?.tagEstimates ?? []).map((t) => [t.tag, t.lines]));
196680
- const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } = mergeAuthoring(loaded);
197251
+ const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } = mergeAuthoring2(loaded);
196681
197252
  const recordingBase = recordingOverride ?? manualRecordingOf;
196682
197253
  const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
196683
197254
  const byLocale = stringsByLocale(loaded);
@@ -197034,7 +197605,7 @@ function classesForChannel(classes, channel) {
197034
197605
  function resolveDocumentation(loaded, channel) {
197035
197606
  const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
197036
197607
  const allowed = classesForChannel(classes, channel);
197037
- const docsOf = mergeAuthoring(loaded).documentation;
197608
+ const docsOf = mergeAuthoring2(loaded).documentation;
197038
197609
  const own = (id) => (docsOf.get(id) ?? []).filter((l2) => l2.type !== void 0 && allowed.has(l2.type));
197039
197610
  const out = /* @__PURE__ */ new Map();
197040
197611
  const visit = (id, inherited) => {
@@ -197070,7 +197641,7 @@ function extractLoc(loaded, opts = {}) {
197070
197641
  const targetLocale = isTemplate ? void 0 : opts.locale;
197071
197642
  const source2 = tableFor(loaded, defaultLocale);
197072
197643
  const target = targetLocale ? tableFor(loaded, targetLocale) : {};
197073
- const edits = mergeAuthoring(loaded).edits;
197644
+ const edits = mergeAuthoring2(loaded).edits;
197074
197645
  const docs = resolveDocumentation(loaded, "loc");
197075
197646
  const commentsOf = (id) => (docs.get(id) ?? []).map((d) => d.text);
197076
197647
  const staleFor = (id, translation) => {
@@ -197410,7 +197981,7 @@ function runVoiceScript(loaded, opts = {}) {
197410
197981
  const stub = writingLadder[0];
197411
197982
  const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
197412
197983
  const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
197413
- const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring(loaded);
197984
+ const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring2(loaded);
197414
197985
  const recordingBase = opts.recordingOverride ?? manualRecordingOf;
197415
197986
  const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
197416
197987
  const source2 = sourceStrings(loaded);
@@ -197606,7 +198177,7 @@ function sequenceLabel(node) {
197606
198177
  function runScriptDoc(loaded) {
197607
198178
  const { project } = loaded;
197608
198179
  const source2 = sourceStrings(loaded);
197609
- const { cut } = mergeAuthoring(loaded);
198180
+ const { cut } = mergeAuthoring2(loaded);
197610
198181
  const sceneOf = /* @__PURE__ */ new Map();
197611
198182
  const blockTrail = /* @__PURE__ */ new Map();
197612
198183
  const blockName = /* @__PURE__ */ new Map();
@@ -224067,11 +224638,11 @@ var $94d7a73bd2edfc9a$export$2e2bcd8739ae039 = class {
224067
224638
  * Ignores features that have already been applied.
224068
224639
  */
224069
224640
  _addFeatures(features, global3) {
224070
- let stageIndex = this.stages.length - 1;
224071
- let stage = this.stages[stageIndex];
224641
+ let stageIndex2 = this.stages.length - 1;
224642
+ let stage = this.stages[stageIndex2];
224072
224643
  for (let feature of features) if (this.allFeatures[feature] == null) {
224073
224644
  stage.push(feature);
224074
- this.allFeatures[feature] = stageIndex;
224645
+ this.allFeatures[feature] = stageIndex2;
224075
224646
  if (global3) this.globalFeatures[feature] = true;
224076
224647
  }
224077
224648
  }
@@ -237967,6 +238538,13 @@ async function runPack(startPath) {
237967
238538
  const projectFile = findProjectFile(startPath);
237968
238539
  const root2 = dirname4(projectFile);
237969
238540
  const project = parseSource(readFileSync4(projectFile, "utf8"));
238541
+ const unresolved = sidecarIssues(walkFiles(root2, CONFLICT_SIDECAR));
238542
+ if (unresolved.length) {
238543
+ throw new Error(
238544
+ `${unresolved.length} unresolved merge conflict(s) - resolve them before packing, or the recipient gets your side of a disagreement with no sign of it:
238545
+ ` + unresolved.map((i) => ` ${i.file}`).join("\n")
238546
+ );
238547
+ }
237970
238548
  const files = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root2, ext)).map((abs) => ({ abs, rel: relative(root2, abs).split(sep2).join("/") })).sort((a, b) => a.rel.localeCompare(b.rel));
237971
238549
  const manifest = {
237972
238550
  schema: "patter/document@0",
@@ -237983,325 +238561,6 @@ async function runPack(startPath) {
237983
238561
  var import_jszip2 = __toESM(require_lib3(), 1);
237984
238562
  import { join as join5, normalize, isAbsolute as isAbsolute2, resolve as resolve4, sep as sep3 } from "path";
237985
238563
  import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
237986
-
237987
- // ../ops/src/merge.ts
237988
- var UnsupportedMergeError = class extends Error {
237989
- };
237990
- var eq = (a, b) => canonicalStringify(a) === canonicalStringify(b);
237991
- var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
237992
- function detectMergeType(file) {
237993
- const s = typeof file.schema === "string" ? file.schema : "";
237994
- if (s.startsWith("patter/flow")) return "flow";
237995
- if (s.startsWith("patter/strings")) return "loc";
237996
- if (s.startsWith("patter/authoring")) return "authoring";
237997
- if (s.startsWith("patter/project")) return "project";
237998
- throw new UnsupportedMergeError(`cannot detect a Patter merge type from schema '${s}'`);
237999
- }
238000
- function runMerge(base, ours, theirs, opts) {
238001
- const type = opts?.type ?? detectMergeType(ours);
238002
- switch (type) {
238003
- case "loc":
238004
- return mergeLoc(base, ours, theirs);
238005
- case "authoring":
238006
- return mergeAuthoring2(base, ours, theirs);
238007
- case "flow":
238008
- return mergeFlow(base, ours, theirs);
238009
- case "project":
238010
- return mergeProject(base, ours, theirs);
238011
- }
238012
- }
238013
- function deletedKind(base, ours, theirs) {
238014
- const deleted = base !== void 0 && (ours === void 0 || theirs === void 0);
238015
- return deleted ? "delete-vs-edit" : "both-changed";
238016
- }
238017
- function merge3(base, ours, theirs, id, path, conflicts) {
238018
- if (eq(ours, theirs)) return ours;
238019
- if (eq(base, ours)) return theirs;
238020
- if (eq(base, theirs)) return ours;
238021
- conflicts.push({ id, path, base, ours, theirs, kind: deletedKind(base, ours, theirs) });
238022
- return ours;
238023
- }
238024
- function mergeMap(base, ours, theirs, prefix, conflicts, resolve6) {
238025
- const out = {};
238026
- for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
238027
- const b = base[k], o = ours[k], t = theirs[k];
238028
- let v;
238029
- if (eq(o, t)) v = o;
238030
- else if (eq(b, o)) v = t;
238031
- else if (eq(b, t)) v = o;
238032
- else {
238033
- const r = resolve6?.(k, b, o, t);
238034
- if (r && r.resolved) v = r.value;
238035
- else {
238036
- conflicts.push({ id: k, path: `${prefix}.${k}`, base: b, ours: o, theirs: t, kind: r ? r.kind : deletedKind(b, o, t) });
238037
- v = o;
238038
- }
238039
- }
238040
- if (v !== void 0) out[k] = v;
238041
- }
238042
- return out;
238043
- }
238044
- var asMap = (v) => isObj(v) ? v : {};
238045
- var asArr = (v) => Array.isArray(v) ? v : [];
238046
- function setIf(obj2, key2, val) {
238047
- if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj2[key2] = val;
238048
- }
238049
- function mergeLoc(base, ours, theirs) {
238050
- const conflicts = [];
238051
- const merged = {};
238052
- for (const f of ["schema", "scene", "locale", "default"]) {
238053
- const v = merge3(base[f], ours[f], theirs[f], "", f, conflicts);
238054
- if (v !== void 0) merged[f] = v;
238055
- }
238056
- merged.strings = mergeMap(asMap(base.strings), asMap(ours.strings), asMap(theirs.strings), "strings", conflicts);
238057
- return { type: "loc", merged, conflicts, warnings: [] };
238058
- }
238059
- function mergeAuthoring2(base, ours, theirs) {
238060
- const conflicts = [];
238061
- const merged = {};
238062
- merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
238063
- setIf(merged, "comments", mergeComments(asArr(base.comments), asArr(ours.comments), asArr(theirs.comments)));
238064
- const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
238065
- setIf(merged, "edits", edits);
238066
- const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
238067
- for (const f of ["writing", "recording", "audio"]) {
238068
- setIf(merged, f, mergeMap(asMap(base[f]), asMap(ours[f]), asMap(theirs[f]), f, conflicts, lww));
238069
- }
238070
- setIf(merged, "documentation", mergeMap(asMap(base.documentation), asMap(ours.documentation), asMap(theirs.documentation), "documentation", conflicts));
238071
- setIf(merged, "cut", mergeMap(asMap(base.cut), asMap(ours.cut), asMap(theirs.cut), "cut", conflicts, void 0));
238072
- return { type: "authoring", merged, conflicts, warnings: [] };
238073
- }
238074
- function mergeComments(base, ours, theirs) {
238075
- const byId = /* @__PURE__ */ new Map();
238076
- for (const c2 of [...base, ...ours, ...theirs]) {
238077
- if (isObj(c2) && typeof c2.id === "string" && !byId.has(c2.id)) byId.set(c2.id, c2);
238078
- }
238079
- return [...byId.values()].sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
238080
- }
238081
- function lastWriterWins(oursEdits, theirsEdits) {
238082
- return (id, _b, o, t) => {
238083
- const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
238084
- if (typeof om === "string" && typeof tm === "string") return { resolved: true, value: om >= tm ? o : t };
238085
- return { resolved: false, kind: "no-timestamp" };
238086
- };
238087
- }
238088
- function mergeEdits(base, ours, theirs) {
238089
- const out = {};
238090
- for (const id of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
238091
- const o = ours[id], t = theirs[id];
238092
- if (eq(o, t)) {
238093
- if (o !== void 0) out[id] = o;
238094
- continue;
238095
- }
238096
- if (o === void 0) {
238097
- if (t !== void 0) out[id] = t;
238098
- continue;
238099
- }
238100
- if (t === void 0) {
238101
- out[id] = o;
238102
- continue;
238103
- }
238104
- out[id] = mergeEditRecord(asMap(o), asMap(t));
238105
- }
238106
- return out;
238107
- }
238108
- function mergeEditRecord(o, t) {
238109
- const om = typeof o.modifiedAt === "string" ? o.modifiedAt : "";
238110
- const tm = typeof t.modifiedAt === "string" ? t.modifiedAt : "";
238111
- const newer = om >= tm ? o : t;
238112
- const ol = asMap(o.localisedAt), tl = asMap(t.localisedAt);
238113
- const loc = {};
238114
- for (const k of /* @__PURE__ */ new Set([...Object.keys(ol), ...Object.keys(tl)])) {
238115
- const a = typeof ol[k] === "string" ? ol[k] : "";
238116
- const b = typeof tl[k] === "string" ? tl[k] : "";
238117
- loc[k] = a >= b ? ol[k] ?? tl[k] : tl[k] ?? ol[k];
238118
- }
238119
- const merged = { ...newer };
238120
- if (Object.keys(loc).length > 0) merged.localisedAt = loc;
238121
- return merged;
238122
- }
238123
- function mergeKeyedByName(base, ours, theirs, path, conflicts) {
238124
- const byName = (arr) => {
238125
- const m = {};
238126
- for (const p of arr) if (isObj(p) && typeof p.name === "string") m[p.name] = p;
238127
- return m;
238128
- };
238129
- const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts);
238130
- return Object.keys(merged).sort().map((name) => merged[name]);
238131
- }
238132
- function mergeProject(base, ours, theirs) {
238133
- const conflicts = [];
238134
- const merged = {};
238135
- for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
238136
- if (k === "properties" || k === "cast") {
238137
- const v = mergeKeyedByName(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
238138
- if (v.length > 0) merged[k] = v;
238139
- } else if (k === "gameDataFields") {
238140
- const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
238141
- if (Object.keys(v).length > 0) merged[k] = v;
238142
- } else if (k === "locales") {
238143
- merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
238144
- } else {
238145
- const v = merge3(base[k], ours[k], theirs[k], "", k, conflicts);
238146
- if (v !== void 0) merged[k] = v;
238147
- }
238148
- }
238149
- return { type: "project", merged, conflicts, warnings: [] };
238150
- }
238151
- function mergeGameDataFields(b, o, t, conflicts) {
238152
- const out = {};
238153
- for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
238154
- const v = mergeKeyedByName(asArr(b[kind]), asArr(o[kind]), asArr(t[kind]), `gameDataFields.${kind}`, conflicts);
238155
- if (v.length > 0) out[kind] = v;
238156
- }
238157
- return out;
238158
- }
238159
- function mergeLocales(b, o, t, conflicts) {
238160
- const out = {};
238161
- const def = merge3(b.default, o.default, t.default, "", "locales.default", conflicts);
238162
- if (def !== void 0) out.default = def;
238163
- const all = [];
238164
- for (const arr of [asArr(b.all), asArr(o.all), asArr(t.all)]) {
238165
- for (const x of arr) if (typeof x === "string" && !all.includes(x)) all.push(x);
238166
- }
238167
- out.all = all;
238168
- return out;
238169
- }
238170
- var CHILD_KEYS = ["blocks", "children", "beats"];
238171
- function toTree(raw) {
238172
- const childKey = CHILD_KEYS.find((k) => Array.isArray(raw[k]));
238173
- const fields = {};
238174
- for (const [k, v] of Object.entries(raw)) if (k !== childKey) fields[k] = v;
238175
- const children = childKey ? raw[childKey].filter(isObj).map(toTree) : [];
238176
- return { id: typeof raw.id === "string" ? raw.id : "", fields, childKey, children };
238177
- }
238178
- function fromTree(n) {
238179
- const out = { ...n.fields };
238180
- if (n.childKey) out[n.childKey] = n.children.map(fromTree);
238181
- return out;
238182
- }
238183
- function mergeFlow(base, ours, theirs) {
238184
- const conflicts = [];
238185
- const warnings = [];
238186
- const merged = {};
238187
- merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
238188
- const bScene = isObj(base.scene) ? toTree(base.scene) : void 0;
238189
- const oScene = toTree(asMap(ours.scene));
238190
- const tScene = isObj(theirs.scene) ? toTree(theirs.scene) : void 0;
238191
- const mergedScene = mergeNode(bScene, oScene, tScene, "scene", conflicts, warnings);
238192
- merged.scene = fromTree(mergedScene);
238193
- checkDuplicateIds(mergedScene, conflicts);
238194
- return { type: "flow", merged, conflicts, warnings };
238195
- }
238196
- function mergeNode(b, o, t, path, conflicts, warnings) {
238197
- const fields = mergeFields(b?.fields ?? {}, o.fields, t?.fields ?? {}, o.id, path, conflicts);
238198
- const childKey = o.childKey ?? t?.childKey ?? b?.childKey;
238199
- const children = childKey ? mergeChildren(b?.children ?? [], o.children, t?.children ?? [], `${path}.${childKey}`, conflicts, warnings) : [];
238200
- return { id: o.id, fields, childKey, children };
238201
- }
238202
- function mergeFields(b, o, t, id, path, conflicts) {
238203
- const out = {};
238204
- for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
238205
- const v = merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
238206
- if (v !== void 0) out[k] = v;
238207
- }
238208
- return out;
238209
- }
238210
- function mergeChildren(B, O, T, path, conflicts, warnings) {
238211
- const Bm = idMap(B), Om = idMap(O), Tm = idMap(T);
238212
- const Bset = new Set(Bm.keys()), Oset = new Set(Om.keys()), Tset = new Set(Tm.keys());
238213
- const merged = /* @__PURE__ */ new Map();
238214
- const here = (id) => `${path}[${id}]`;
238215
- for (const id of /* @__PURE__ */ new Set([...Bm.keys(), ...Om.keys(), ...Tm.keys()])) {
238216
- const b = Bm.get(id), o = Om.get(id), t = Tm.get(id);
238217
- const inB = Bset.has(id), inO = Oset.has(id), inT = Tset.has(id);
238218
- if (inO && inT) {
238219
- if (!inB && !eq(fromTree(o), fromTree(t))) {
238220
- conflicts.push({ id, path: here(id), base: void 0, ours: fromTree(o), theirs: fromTree(t), kind: "added-both" });
238221
- merged.set(id, o);
238222
- } else {
238223
- merged.set(id, mergeNode(b, o, t, here(id), conflicts, warnings));
238224
- }
238225
- } else if (inO && !inT) {
238226
- if (inB && !eq(fromTree(o), fromTree(b))) {
238227
- conflicts.push({ id, path: here(id), base: fromTree(b), ours: fromTree(o), theirs: void 0, kind: "delete-vs-edit" });
238228
- merged.set(id, o);
238229
- } else if (!inB) {
238230
- merged.set(id, o);
238231
- }
238232
- } else if (!inO && inT) {
238233
- if (inB && !eq(fromTree(t), fromTree(b))) {
238234
- conflicts.push({ id, path: here(id), base: fromTree(b), ours: void 0, theirs: fromTree(t), kind: "delete-vs-edit" });
238235
- } else if (!inB) {
238236
- merged.set(id, t);
238237
- }
238238
- }
238239
- }
238240
- return orderChildren(merged, B, O, T, path, conflicts, warnings);
238241
- }
238242
- function orderChildren(merged, B, O, T, path, conflicts, warnings) {
238243
- const Bids = B.map((n) => n.id), Oids = O.map((n) => n.id), Tids = T.map((n) => n.id);
238244
- const Oidset = new Set(Oids);
238245
- const common = new Set(Bids.filter((id) => merged.has(id)));
238246
- const restrict = (ids) => ids.filter((id) => common.has(id));
238247
- const relB = restrict(Bids), relO = restrict(Oids), relT = restrict(Tids);
238248
- const fullO = relO.length === common.size, fullT = relT.length === common.size;
238249
- let commonOrder;
238250
- if (!fullO && !fullT) commonOrder = relB;
238251
- else if (!fullO) commonOrder = relT;
238252
- else if (!fullT) commonOrder = relO;
238253
- else if (arrEq(relO, relB)) commonOrder = relT;
238254
- else if (arrEq(relT, relB)) commonOrder = relO;
238255
- else if (arrEq(relO, relT)) commonOrder = relO;
238256
- else {
238257
- conflicts.push({ id: "", path, base: relB, ours: relO, theirs: relT, kind: "moved" });
238258
- commonOrder = relO;
238259
- }
238260
- const anchorIn = (ids, id) => {
238261
- for (let i = ids.indexOf(id) - 1; i >= 0; i--) if (common.has(ids[i])) return ids[i];
238262
- return null;
238263
- };
238264
- const byAnchor = /* @__PURE__ */ new Map();
238265
- const group = (a) => {
238266
- let g = byAnchor.get(a);
238267
- if (!g) {
238268
- g = { ours: [], theirs: [] };
238269
- byAnchor.set(a, g);
238270
- }
238271
- return g;
238272
- };
238273
- for (const n of O) if (merged.has(n.id) && !common.has(n.id)) group(anchorIn(Oids, n.id)).ours.push(n.id);
238274
- for (const n of T) if (merged.has(n.id) && !common.has(n.id) && !Oidset.has(n.id)) group(anchorIn(Tids, n.id)).theirs.push(n.id);
238275
- const out = [];
238276
- const emit = (anchor) => {
238277
- const g = byAnchor.get(anchor);
238278
- if (!g) return;
238279
- if (g.ours.length > 0 && g.theirs.length > 0) warnings.push({ id: anchor ?? "", path, message: "both sides inserted here; kept ours-then-theirs - review the order" });
238280
- out.push(...g.ours, ...g.theirs);
238281
- };
238282
- emit(null);
238283
- for (const id of commonOrder) {
238284
- out.push(id);
238285
- emit(id);
238286
- }
238287
- return out.map((id) => merged.get(id));
238288
- }
238289
- function checkDuplicateIds(root2, conflicts) {
238290
- const seen = /* @__PURE__ */ new Set(), dup = /* @__PURE__ */ new Set();
238291
- const walk2 = (n) => {
238292
- if (n.id) {
238293
- if (seen.has(n.id)) dup.add(n.id);
238294
- else seen.add(n.id);
238295
- }
238296
- n.children.forEach(walk2);
238297
- };
238298
- walk2(root2);
238299
- for (const id of dup) conflicts.push({ id, path: "scene", base: void 0, ours: void 0, theirs: void 0, kind: "structural" });
238300
- }
238301
- var idMap = (nodes) => new Map(nodes.filter((n) => n.id).map((n) => [n.id, n]));
238302
- var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
238303
-
238304
- // ../ops/src/unpack.ts
238305
238564
  var MANIFEST = "patter.manifest.json";
238306
238565
  var UnsafeEntryError = class extends Error {
238307
238566
  };
@@ -238364,10 +238623,17 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
238364
238623
  shards.push({ path: rel, added: true });
238365
238624
  continue;
238366
238625
  }
238367
- const oursObj = parseSource(readFileSync5(outPath, "utf8"));
238368
- const theirsObj = parseSource(theirText);
238626
+ const readSide = (side, text) => {
238627
+ try {
238628
+ return parseSource(text);
238629
+ } catch (e) {
238630
+ throw new Error(`${rel}: the ${side} copy is not readable Patter source - ${e instanceof Error ? e.message : String(e)}`);
238631
+ }
238632
+ };
238633
+ const oursObj = readSide("ours", readFileSync5(outPath, "utf8"));
238634
+ const theirsObj = readSide("theirs", theirText);
238369
238635
  const baseText = base.get(rel);
238370
- const baseObj = baseText !== void 0 ? parseSource(baseText) : {};
238636
+ const baseObj = baseText !== void 0 ? readSide("base", baseText) : {};
238371
238637
  const result = runMerge(baseObj, oursObj, theirsObj);
238372
238638
  writes.push({ path: outPath, content: canonicalStringify(result.merged) });
238373
238639
  if (result.conflicts.length > 0) {
@@ -240491,14 +240757,15 @@ async function run(cmd, positionals, flags) {
240491
240757
  }
240492
240758
  case "validate": {
240493
240759
  const loaded = loadProject(positionals[0] ?? ".");
240494
- const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, ok } = runValidate(loaded);
240760
+ const { structural, conditions, interpolation, hygiene, staleBundles, unresolvedMerges, orphans, ok } = runValidate(loaded);
240495
240761
  for (const i of structural) console.error(` [${i.code}] ${i.message}`);
240496
240762
  for (const i of conditions) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
240497
240763
  for (const i of interpolation) console.error(` [${i.field}] ${i.nodeId}: ${i.message} (${i.src})`);
240498
240764
  for (const i of hygiene) console.error(` [hygiene] ${i.file}: ${i.message}`);
240499
240765
  for (const i of staleBundles) console.error(` [stale-bundle] ${i.file}: ${i.message}`);
240500
240766
  for (const i of unresolvedMerges) console.error(` [unresolved-merge] ${i.file}: ${i.message}`);
240501
- const count = structural.length + conditions.length + interpolation.length + hygiene.length + staleBundles.length + unresolvedMerges.length;
240767
+ for (const i of orphans) console.error(` [not-in-project] ${i.file}: ${i.message}`);
240768
+ const count = structural.length + conditions.length + interpolation.length + hygiene.length + staleBundles.length + unresolvedMerges.length + orphans.length;
240502
240769
  if (ok) console.log(`ok - ${loaded.scenes.length} scene(s), no issues`);
240503
240770
  else console.error(`
240504
240771
  ${count} issue(s)`);