@patterkit/ops 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +40 -3
- package/dist/index.js +532 -448
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -546,12 +546,12 @@ function applyWrites(writes) {
|
|
|
546
546
|
|
|
547
547
|
// src/validate.ts
|
|
548
548
|
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
549
|
-
import { basename as
|
|
549
|
+
import { basename as basename5, dirname as dirname6, isAbsolute as isAbsolute2, join as join6, relative as relative3 } from "path";
|
|
550
550
|
|
|
551
551
|
// src/merge.ts
|
|
552
552
|
import { canonicalStringify as canonicalStringify2, parseSource as parseSource2 } from "@patterkit/core";
|
|
553
553
|
import { DEFAULT_QUALIFIERS } from "@patterkit/model";
|
|
554
|
-
import { markUnresolved, mergeExtensionData, unresolvedProblems, withoutMark } from "@wildwinter/toolkit/extensions";
|
|
554
|
+
import { ATOMIC, COMMENTS, SET, keyedBy, mapOf, markUnresolved, mergeExtensionData, mergeTree, objectOf, unresolvedProblems, withoutMark } from "@wildwinter/toolkit/extensions";
|
|
555
555
|
var UnsupportedMergeError = class extends Error {
|
|
556
556
|
};
|
|
557
557
|
var eq = (a, b) => canonicalStringify2(a) === canonicalStringify2(b);
|
|
@@ -642,6 +642,11 @@ var asArr = (v) => Array.isArray(v) ? v : [];
|
|
|
642
642
|
function setIf(obj, key, val) {
|
|
643
643
|
if (Array.isArray(val) ? val.length > 0 : Object.keys(val).length > 0) obj[key] = val;
|
|
644
644
|
}
|
|
645
|
+
function shared(strategy, base, ours, theirs, id, path, conflicts) {
|
|
646
|
+
const r = mergeTree(strategy, base, ours, theirs, { equal: eq });
|
|
647
|
+
for (const c of r.conflicts) conflicts.push({ ...c, id: c.id || id, path: `${path}${c.path}` });
|
|
648
|
+
return r.merged;
|
|
649
|
+
}
|
|
645
650
|
function mergeLoc(base, ours, theirs) {
|
|
646
651
|
const conflicts = [];
|
|
647
652
|
const merged = {};
|
|
@@ -656,7 +661,7 @@ function mergeAuthoring(base, ours, theirs) {
|
|
|
656
661
|
const conflicts = [];
|
|
657
662
|
const merged = {};
|
|
658
663
|
merged.schema = merge3(base.schema, ours.schema, theirs.schema, "", "schema", conflicts) ?? ours.schema;
|
|
659
|
-
setIf(merged, "comments",
|
|
664
|
+
setIf(merged, "comments", shared(COMMENTS, asArr(base.comments), asArr(ours.comments), asArr(theirs.comments), "", "comments", conflicts));
|
|
660
665
|
const edits = mergeEdits(asMap(base.edits), asMap(ours.edits), asMap(theirs.edits));
|
|
661
666
|
setIf(merged, "edits", edits);
|
|
662
667
|
const lww = lastWriterWins(asMap(ours.edits), asMap(theirs.edits));
|
|
@@ -684,52 +689,6 @@ function mergeById(base, ours, theirs, path, conflicts) {
|
|
|
684
689
|
const merged = mergeMap(index(base), index(ours), index(theirs), path, conflicts);
|
|
685
690
|
return Object.values(merged).sort((a, b) => String(asMap(a).ts ?? "").localeCompare(String(asMap(b).ts ?? "")));
|
|
686
691
|
}
|
|
687
|
-
function mergeComments(base, ours, theirs) {
|
|
688
|
-
const index = (arr) => {
|
|
689
|
-
const m = /* @__PURE__ */ new Map();
|
|
690
|
-
for (const c of arr) if (isObj(c) && typeof c.id === "string") m.set(c.id, c);
|
|
691
|
-
return m;
|
|
692
|
-
};
|
|
693
|
-
const b = index(base), o = index(ours), t = index(theirs);
|
|
694
|
-
const msgKey = (m) => `${String(m.author ?? "")}|${String(m.ts ?? "")}`;
|
|
695
|
-
const out = [];
|
|
696
|
-
for (const id of /* @__PURE__ */ new Set([...b.keys(), ...o.keys(), ...t.keys()])) {
|
|
697
|
-
const bc = b.get(id), oc = o.get(id), tc = t.get(id);
|
|
698
|
-
const sides = [oc, tc].filter((c) => c !== void 0);
|
|
699
|
-
if (sides.length === 0) continue;
|
|
700
|
-
if (![bc, oc, tc].some((c) => Array.isArray(c?.messages))) {
|
|
701
|
-
out.push(oc ?? tc);
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
const deletedBy = /* @__PURE__ */ new Set();
|
|
705
|
-
for (const c of [oc, tc]) {
|
|
706
|
-
if (c === void 0 && bc) {
|
|
707
|
-
for (const m of asArr(bc.messages)) if (isObj(m)) deletedBy.add(msgKey(m));
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
const messages = /* @__PURE__ */ new Map();
|
|
711
|
-
for (const c of [bc, ...sides]) {
|
|
712
|
-
for (const m of asArr(c?.messages)) {
|
|
713
|
-
if (!isObj(m)) continue;
|
|
714
|
-
const key = msgKey(m);
|
|
715
|
-
const seen = messages.get(key);
|
|
716
|
-
if (!seen || m.deleted === true && seen.deleted !== true) messages.set(key, m);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
for (const key of deletedBy) {
|
|
720
|
-
const m = messages.get(key);
|
|
721
|
-
if (m && m.deleted !== true) messages.set(key, { ...m, body: "", deleted: true });
|
|
722
|
-
}
|
|
723
|
-
const merged = [...messages.values()].sort((x, y) => String(x.ts ?? "").localeCompare(String(y.ts ?? "")));
|
|
724
|
-
if (merged.length === 0 || merged.every((m) => m.deleted === true)) continue;
|
|
725
|
-
const was = bc?.resolved === true;
|
|
726
|
-
const oursSays = oc ? oc.resolved === true : was, theirsSays = tc ? tc.resolved === true : was;
|
|
727
|
-
const resolved = oursSays !== was ? oursSays : theirsSays;
|
|
728
|
-
const { resolved: _r, messages: _m, ...rest } = oc ?? tc;
|
|
729
|
-
out.push({ ...rest, ...resolved ? { resolved: true } : {}, messages: merged });
|
|
730
|
-
}
|
|
731
|
-
return out.sort((x, y) => String(asArr(x.messages).map(asMap)[0]?.ts ?? x.ts ?? "").localeCompare(String(asArr(y.messages).map(asMap)[0]?.ts ?? y.ts ?? "")));
|
|
732
|
-
}
|
|
733
692
|
function lastWriterWins(oursEdits, theirsEdits) {
|
|
734
693
|
return (id, _b, o, t) => {
|
|
735
694
|
const om = asMap(oursEdits[id]).modifiedAt, tm = asMap(theirsEdits[id]).modifiedAt;
|
|
@@ -772,26 +731,11 @@ function mergeEditRecord(o, t) {
|
|
|
772
731
|
if (Object.keys(loc).length > 0) merged.localisedAt = loc;
|
|
773
732
|
return merged;
|
|
774
733
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
};
|
|
781
|
-
const merged = mergeMap(byName(base), byName(ours), byName(theirs), path, conflicts, resolve8?.(path, conflicts));
|
|
782
|
-
return Object.keys(merged).sort().map((name) => merged[name]);
|
|
783
|
-
}
|
|
784
|
-
function mergeCastMember(path, conflicts) {
|
|
785
|
-
return (name, b, o, t) => {
|
|
786
|
-
if (!isObj(o) || !isObj(t)) return { resolved: false, kind: deletedKind(b, o, t) };
|
|
787
|
-
const base = asMap(b);
|
|
788
|
-
const out = {};
|
|
789
|
-
for (const f of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(o), ...Object.keys(t)])) {
|
|
790
|
-
const v = f === "qualifierLabels" ? mergeMap(asMap(base[f]), asMap(o[f]), asMap(t[f]), `${path}.${name}.${f}`, conflicts) : merge3(base[f], o[f], t[f], name, `${path}.${name}.${f}`, conflicts);
|
|
791
|
-
if (f === "qualifierLabels" ? Object.keys(v).length > 0 : v !== void 0) out[f] = v;
|
|
792
|
-
}
|
|
793
|
-
return { resolved: true, value: out };
|
|
794
|
-
};
|
|
734
|
+
var DECLS = keyedBy("name", ATOMIC, { order: "preserve" });
|
|
735
|
+
var CAST = keyedBy("name", objectOf({ qualifierLabels: mapOf(ATOMIC) }), { order: "preserve" });
|
|
736
|
+
function mergeDecls(strategy, base, ours, theirs, path, conflicts) {
|
|
737
|
+
const out = shared(strategy, asArr(base), asArr(ours), asArr(theirs), "", path, conflicts);
|
|
738
|
+
return out.map((m) => isObj(m) && isObj(m.qualifierLabels) && Object.keys(m.qualifierLabels).length === 0 ? (({ qualifierLabels: _q, ...rest }) => rest)(m) : m);
|
|
795
739
|
}
|
|
796
740
|
var PROJECT_HANDLED = /* @__PURE__ */ new Set(["properties", "cast", "gameDataFields", "qualifiers", "locales", "extensions"]);
|
|
797
741
|
function mergeProject(base, ours, theirs) {
|
|
@@ -799,10 +743,10 @@ function mergeProject(base, ours, theirs) {
|
|
|
799
743
|
const merged = {};
|
|
800
744
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(ours), ...Object.keys(theirs)])) {
|
|
801
745
|
if (k === "properties") {
|
|
802
|
-
const v =
|
|
746
|
+
const v = mergeDecls(DECLS, base[k], ours[k], theirs[k], k, conflicts);
|
|
803
747
|
if (v.length > 0) merged[k] = v;
|
|
804
748
|
} else if (k === "cast") {
|
|
805
|
-
const v =
|
|
749
|
+
const v = mergeDecls(CAST, base[k], ours[k], theirs[k], k, conflicts);
|
|
806
750
|
if (v.length > 0) merged[k] = v;
|
|
807
751
|
} else if (k === "gameDataFields") {
|
|
808
752
|
const v = mergeGameDataFields(asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), conflicts);
|
|
@@ -850,7 +794,7 @@ function mergeProjectExtensions(b, o, t, conflicts) {
|
|
|
850
794
|
function mergeGameDataFields(b, o, t, conflicts) {
|
|
851
795
|
const out = {};
|
|
852
796
|
for (const kind of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
853
|
-
const v =
|
|
797
|
+
const v = mergeDecls(DECLS, b[kind], o[kind], t[kind], `gameDataFields.${kind}`, conflicts);
|
|
854
798
|
if (v.length > 0) out[kind] = v;
|
|
855
799
|
}
|
|
856
800
|
return out;
|
|
@@ -918,11 +862,17 @@ function mergeNode(b, o, t, path, conflicts, warnings) {
|
|
|
918
862
|
function mergeFields(b, o, t, id, path, conflicts) {
|
|
919
863
|
const out = {};
|
|
920
864
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
921
|
-
const v = k === "gameData" && [b[k], o[k], t[k]].every((x) => x === void 0 || isObj(x)) ? mergeGameData(asMap(b[k]), asMap(o[k]), asMap(t[k]), id, `${path}.${k}`, conflicts) : merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
865
|
+
const v = k === "gameData" && [b[k], o[k], t[k]].every((x) => x === void 0 || isObj(x)) ? mergeGameData(asMap(b[k]), asMap(o[k]), asMap(t[k]), id, `${path}.${k}`, conflicts) : k === "tags" && [b[k], o[k], t[k]].every((x) => x === void 0 || Array.isArray(x)) ? mergeTags(b[k], o[k], t[k], id, `${path}.${k}`, conflicts) : merge3(b[k], o[k], t[k], id, `${path}.${k}`, conflicts);
|
|
922
866
|
if (v !== void 0) out[k] = v;
|
|
923
867
|
}
|
|
924
868
|
return out;
|
|
925
869
|
}
|
|
870
|
+
function mergeTags(b, o, t, id, path, conflicts) {
|
|
871
|
+
if (eq(o, t) || eq(b, t)) return o;
|
|
872
|
+
if (eq(b, o)) return t;
|
|
873
|
+
const v = shared(SET, asArr(b), asArr(o), asArr(t), id, path, conflicts);
|
|
874
|
+
return v.length > 0 ? v : void 0;
|
|
875
|
+
}
|
|
926
876
|
function mergeGameData(b, o, t, id, path, conflicts) {
|
|
927
877
|
const out = {};
|
|
928
878
|
for (const k of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
|
|
@@ -1083,7 +1033,7 @@ function besideBundlePath(loaded, ext) {
|
|
|
1083
1033
|
}
|
|
1084
1034
|
|
|
1085
1035
|
// src/validate.ts
|
|
1086
|
-
import { EXTENSION_SHARD as EXTENSION_SHARD3, PROJECT_LOCALE_SCENE, projectLayout as
|
|
1036
|
+
import { EXTENSION_SHARD as EXTENSION_SHARD3, PROJECT_LOCALE_SCENE as PROJECT_LOCALE_SCENE2, projectLayout as projectLayout3 } from "@patterkit/model";
|
|
1087
1037
|
|
|
1088
1038
|
// src/reachability.ts
|
|
1089
1039
|
import { disjuncts, latchOf, scopedRef, terms } from "@wildwinter/expr";
|
|
@@ -1180,7 +1130,7 @@ function reachabilityIssues(loaded, compiled) {
|
|
|
1180
1130
|
const routes = writers.get(key);
|
|
1181
1131
|
if (routes === void 0 || routes.length === 0) return { need: /* @__PURE__ */ new Set(), cut: false };
|
|
1182
1132
|
inFlight.add(key);
|
|
1183
|
-
let
|
|
1133
|
+
let shared2;
|
|
1184
1134
|
let cut = false;
|
|
1185
1135
|
for (const route of routes) {
|
|
1186
1136
|
const need = /* @__PURE__ */ new Set();
|
|
@@ -1191,10 +1141,10 @@ function reachabilityIssues(loaded, compiled) {
|
|
|
1191
1141
|
cut ||= deeper.cut;
|
|
1192
1142
|
for (const k of deeper.need) need.add(k);
|
|
1193
1143
|
}
|
|
1194
|
-
|
|
1144
|
+
shared2 = shared2 === void 0 ? need : new Set([...shared2].filter((k) => need.has(k)));
|
|
1195
1145
|
}
|
|
1196
1146
|
inFlight.delete(key);
|
|
1197
|
-
const result =
|
|
1147
|
+
const result = shared2 ?? /* @__PURE__ */ new Set();
|
|
1198
1148
|
if (!cut) cache2.set(key, result);
|
|
1199
1149
|
return { need: result, cut };
|
|
1200
1150
|
};
|
|
@@ -1244,22 +1194,416 @@ function reachabilityIssues(loaded, compiled) {
|
|
|
1244
1194
|
return issues;
|
|
1245
1195
|
}
|
|
1246
1196
|
|
|
1197
|
+
// src/localisation.ts
|
|
1198
|
+
import { basename as basename3, join as join3, sep } from "path";
|
|
1199
|
+
import { canonicalStringify as canonicalStringify3 } from "@patterkit/core";
|
|
1200
|
+
import {
|
|
1201
|
+
walkNodes,
|
|
1202
|
+
textIsAction,
|
|
1203
|
+
castStringKey,
|
|
1204
|
+
qualifierStringKey,
|
|
1205
|
+
speakerLabelKey,
|
|
1206
|
+
usedQualifiers,
|
|
1207
|
+
usedSpeakerPairs,
|
|
1208
|
+
projectQualifiers,
|
|
1209
|
+
SPEAKER_PATTERN_KEY,
|
|
1210
|
+
DEFAULT_SPEAKER_PATTERN,
|
|
1211
|
+
PROJECT_LOCALE_SCENE,
|
|
1212
|
+
projectLayout as projectLayout2,
|
|
1213
|
+
STRINGS_SCHEMA,
|
|
1214
|
+
AUTHORING_SCHEMA
|
|
1215
|
+
} from "@patterkit/model";
|
|
1216
|
+
|
|
1217
|
+
// src/loaded-helpers.ts
|
|
1218
|
+
import { RERECORD_STATUS, effectiveGameId } from "@patterkit/model";
|
|
1219
|
+
function effectiveRecording(id, base, rerecord, lowest) {
|
|
1220
|
+
return rerecord.has(id) ? RERECORD_STATUS : base.get(id) ?? lowest;
|
|
1221
|
+
}
|
|
1222
|
+
function tableFor(loaded, locale) {
|
|
1223
|
+
const table = {};
|
|
1224
|
+
for (const l of loaded.locales) if (l.locale === locale) Object.assign(table, l.strings);
|
|
1225
|
+
return table;
|
|
1226
|
+
}
|
|
1227
|
+
function sourceStrings(loaded) {
|
|
1228
|
+
return tableFor(loaded, loaded.project.locales.default);
|
|
1229
|
+
}
|
|
1230
|
+
function resolveStart(loaded, override) {
|
|
1231
|
+
if (override?.scene) return { scene: override.scene, block: override.block };
|
|
1232
|
+
if (override?.block) return findBlockAnywhere(loaded, override.block);
|
|
1233
|
+
if (loaded.project.start) return { scene: loaded.project.start.scene, block: loaded.project.start.block };
|
|
1234
|
+
return {};
|
|
1235
|
+
}
|
|
1236
|
+
function findBlockAnywhere(loaded, ref) {
|
|
1237
|
+
const matches = [];
|
|
1238
|
+
for (const scene of loaded.scenes) {
|
|
1239
|
+
for (const block of scene.blocks) {
|
|
1240
|
+
if (block.id === ref) return { scene: scene.id, block: block.id };
|
|
1241
|
+
if (block.name === ref || effectiveGameId(block) === ref) matches.push({ scene, block });
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (matches.length === 0) throw new Error(`no scene has a block "${ref}"`);
|
|
1245
|
+
if (matches.length > 1) {
|
|
1246
|
+
const scenes = [...new Set(matches.map((m) => `"${m.scene.name}" (${m.scene.id})`))].join(", ");
|
|
1247
|
+
throw new Error(`more than one block is called "${ref}" (in ${scenes}): name the scene as well`);
|
|
1248
|
+
}
|
|
1249
|
+
return { scene: matches[0].scene.id, block: matches[0].block.id };
|
|
1250
|
+
}
|
|
1251
|
+
function stringsByLocale(loaded) {
|
|
1252
|
+
const byLocale = /* @__PURE__ */ new Map();
|
|
1253
|
+
for (const l of loaded.locales) {
|
|
1254
|
+
const table = byLocale.get(l.locale) ?? {};
|
|
1255
|
+
Object.assign(table, l.strings);
|
|
1256
|
+
byLocale.set(l.locale, table);
|
|
1257
|
+
}
|
|
1258
|
+
return byLocale;
|
|
1259
|
+
}
|
|
1260
|
+
function mergeAuthoring2(loaded) {
|
|
1261
|
+
const writing = /* @__PURE__ */ new Map();
|
|
1262
|
+
const recording = /* @__PURE__ */ new Map();
|
|
1263
|
+
const cut = /* @__PURE__ */ new Set();
|
|
1264
|
+
const rerecord = /* @__PURE__ */ new Set();
|
|
1265
|
+
const documentation = /* @__PURE__ */ new Map();
|
|
1266
|
+
const edits = /* @__PURE__ */ new Map();
|
|
1267
|
+
for (const a of loaded.authoring) {
|
|
1268
|
+
for (const [id, v] of Object.entries(a.writing ?? {})) writing.set(id, v);
|
|
1269
|
+
for (const [id, v] of Object.entries(a.recording ?? {})) recording.set(id, v);
|
|
1270
|
+
for (const [id, v] of Object.entries(a.cut ?? {})) if (v) cut.add(id);
|
|
1271
|
+
for (const [id, v] of Object.entries(a.rerecord ?? {})) if (v) rerecord.add(id);
|
|
1272
|
+
for (const [id, lines] of Object.entries(a.documentation ?? {})) {
|
|
1273
|
+
const cur = documentation.get(id);
|
|
1274
|
+
if (cur) cur.push(...lines);
|
|
1275
|
+
else documentation.set(id, [...lines]);
|
|
1276
|
+
}
|
|
1277
|
+
for (const [id, v] of Object.entries(a.edits ?? {})) edits.set(id, v);
|
|
1278
|
+
}
|
|
1279
|
+
return { writing, recording, cut, rerecord, documentation, edits };
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/documentation.ts
|
|
1283
|
+
import { DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
|
|
1284
|
+
function classesForChannel(classes, channel) {
|
|
1285
|
+
const out = /* @__PURE__ */ new Set();
|
|
1286
|
+
for (const c of classes) {
|
|
1287
|
+
if (c.deliver === "*" || Array.isArray(c.deliver) && c.deliver.includes(channel)) out.add(c.name);
|
|
1288
|
+
}
|
|
1289
|
+
return out;
|
|
1290
|
+
}
|
|
1291
|
+
function resolveDocumentation(loaded, channel) {
|
|
1292
|
+
const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
|
|
1293
|
+
const allowed = classesForChannel(classes, channel);
|
|
1294
|
+
const docsOf = mergeAuthoring2(loaded).documentation;
|
|
1295
|
+
const own = (id) => (docsOf.get(id) ?? []).filter((l) => l.type !== void 0 && allowed.has(l.type));
|
|
1296
|
+
const out = /* @__PURE__ */ new Map();
|
|
1297
|
+
const visit = (id, inherited) => {
|
|
1298
|
+
const mine = own(id);
|
|
1299
|
+
if (inherited.length === 0 && mine.length === 0) return inherited;
|
|
1300
|
+
const resolved = [...inherited, ...mine];
|
|
1301
|
+
out.set(id, resolved);
|
|
1302
|
+
return resolved;
|
|
1303
|
+
};
|
|
1304
|
+
const walkNode = (node, inherited) => {
|
|
1305
|
+
const passed = visit(node.id, inherited);
|
|
1306
|
+
if (node.type === "group") {
|
|
1307
|
+
if (node.prompt) visit(node.prompt.id, passed);
|
|
1308
|
+
for (const child2 of node.children ?? []) walkNode(child2, passed);
|
|
1309
|
+
} else {
|
|
1310
|
+
for (const beat of node.beats ?? []) visit(beat.id, passed);
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
for (const scene of loaded.scenes) {
|
|
1314
|
+
const sceneInh = visit(scene.id, []);
|
|
1315
|
+
for (const block of scene.blocks) {
|
|
1316
|
+
const blockInh = visit(block.id, sceneInh);
|
|
1317
|
+
for (const child2 of block.children ?? []) walkNode(child2, blockInh);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
return out;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/localisation.ts
|
|
1324
|
+
var SETTING_NAME = `"Localise text lines in dialogue scenes" (Project Settings, Language; \`localiseDialogueText\` in the project file)`;
|
|
1325
|
+
function describeLocExport(loaded, opts = {}) {
|
|
1326
|
+
const source = tableFor(loaded, loaded.project.locales.default);
|
|
1327
|
+
let lines = 0, scenes = 0;
|
|
1328
|
+
for (const ids of actionTextIds(loaded).values()) {
|
|
1329
|
+
const n = [...ids].filter((id) => source[id]).length;
|
|
1330
|
+
if (n) {
|
|
1331
|
+
lines += n;
|
|
1332
|
+
scenes++;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
const out = [];
|
|
1336
|
+
if (lines) {
|
|
1337
|
+
out.push(`Left out ${lines} text line${lines === 1 ? "" : "s"} in ${scenes} dialogue scene${scenes === 1 ? "" : "s"}: ${lines === 1 ? "it's" : "they're"} action, a direction for whoever stages the scene, because ${SETTING_NAME} is off. Turn it on to translate ${lines === 1 ? "it" : "them"}.`);
|
|
1338
|
+
}
|
|
1339
|
+
return [...out, ...describeActionTextChanges(loaded, actionTextChanges(loaded, opts))];
|
|
1340
|
+
}
|
|
1341
|
+
function describeActionTextChanges(loaded, changes) {
|
|
1342
|
+
const nameOf = new Map(loaded.scenes.map((sc) => [sc.id, sc.name]));
|
|
1343
|
+
const out = [];
|
|
1344
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1345
|
+
for (const c of changes) {
|
|
1346
|
+
const key = `${c.locale}|${c.change}`;
|
|
1347
|
+
(groups.get(key) ?? groups.set(key, []).get(key)).push(c);
|
|
1348
|
+
}
|
|
1349
|
+
for (const [key, list] of groups) {
|
|
1350
|
+
const [locale, change] = key.split("|");
|
|
1351
|
+
const n = `${list.length} text line${list.length === 1 ? "" : "s"}`;
|
|
1352
|
+
out.push(change === "dropped" ? `${n} now action, so left out of '${locale}': the next loc import drops ${list.length === 1 ? "its translation" : "their translations"} (turn on ${SETTING_NAME} to keep ${list.length === 1 ? "it" : "them"})` : `${n} no longer action, so new to '${locale}' and untranslated`);
|
|
1353
|
+
for (const c of list) out.push(` ${c.id} in '${nameOf.get(c.scene) ?? c.scene}'`);
|
|
1354
|
+
}
|
|
1355
|
+
return out;
|
|
1356
|
+
}
|
|
1357
|
+
function sceneTextIds(scene) {
|
|
1358
|
+
const out = [];
|
|
1359
|
+
for (const block of scene.blocks) {
|
|
1360
|
+
walkNodes(block.children, (node) => {
|
|
1361
|
+
if (node.type === "snippet") {
|
|
1362
|
+
for (const beat of node.beats ?? []) if (beat.kind === "text") out.push(beat.id);
|
|
1363
|
+
}
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
return out;
|
|
1367
|
+
}
|
|
1368
|
+
function actionTextIds(loaded) {
|
|
1369
|
+
const out = /* @__PURE__ */ new Map();
|
|
1370
|
+
for (const scene of loaded.scenes) if (textIsAction(loaded.project, scene)) out.set(scene.id, new Set(sceneTextIds(scene)));
|
|
1371
|
+
return out;
|
|
1372
|
+
}
|
|
1373
|
+
function actionTextChanges(loaded, opts = {}) {
|
|
1374
|
+
const defaultLocale = loaded.project.locales.default;
|
|
1375
|
+
const locales = opts.locale !== void 0 ? opts.locale === defaultLocale ? [] : [opts.locale] : loaded.project.locales.all.filter((l) => l !== defaultLocale);
|
|
1376
|
+
const source = tableFor(loaded, defaultLocale);
|
|
1377
|
+
const out = [];
|
|
1378
|
+
for (const locale of locales) {
|
|
1379
|
+
for (const scene of loaded.scenes) {
|
|
1380
|
+
const path = existingLocPath(loaded, scene.id, locale);
|
|
1381
|
+
const shard = path ? findFile(loaded.localeFiles, loaded.locales, path) : void 0;
|
|
1382
|
+
if (!shard) continue;
|
|
1383
|
+
const action = textIsAction(loaded.project, scene);
|
|
1384
|
+
for (const id of sceneTextIds(scene)) {
|
|
1385
|
+
if (action && shard.strings[id]) out.push({ id, scene: scene.id, locale, change: "dropped" });
|
|
1386
|
+
else if (!action && shard.textAction && source[id]) out.push({ id, scene: scene.id, locale, change: "added" });
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return out;
|
|
1391
|
+
}
|
|
1392
|
+
function projectStringSources(project, sourceTable) {
|
|
1393
|
+
const out = {};
|
|
1394
|
+
const put = (id, authored) => {
|
|
1395
|
+
if (authored) out[id] = sourceTable[id] ?? authored;
|
|
1396
|
+
};
|
|
1397
|
+
for (const c of project.cast ?? []) {
|
|
1398
|
+
put(castStringKey(c.name), c.displayName);
|
|
1399
|
+
for (const [q, label] of Object.entries(c.qualifierLabels ?? {})) put(speakerLabelKey(c.name, q), label);
|
|
1400
|
+
}
|
|
1401
|
+
for (const q of projectQualifiers(project)) put(qualifierStringKey(q.gameId), q.name);
|
|
1402
|
+
put(SPEAKER_PATTERN_KEY, project.speakerPattern ?? DEFAULT_SPEAKER_PATTERN);
|
|
1403
|
+
return out;
|
|
1404
|
+
}
|
|
1405
|
+
function extractLoc(loaded, opts = {}) {
|
|
1406
|
+
const defaultLocale = loaded.project.locales.default;
|
|
1407
|
+
const isTemplate = opts.locale === void 0 || opts.locale === defaultLocale;
|
|
1408
|
+
const targetLocale = isTemplate ? void 0 : opts.locale;
|
|
1409
|
+
const source = tableFor(loaded, defaultLocale);
|
|
1410
|
+
const target = targetLocale ? tableFor(loaded, targetLocale) : {};
|
|
1411
|
+
const edits = mergeAuthoring2(loaded).edits;
|
|
1412
|
+
const projectSources = projectStringSources(loaded.project, source);
|
|
1413
|
+
const docs = resolveDocumentation(loaded, "loc");
|
|
1414
|
+
const commentsOf = (id) => (docs.get(id) ?? []).map((d) => d.text);
|
|
1415
|
+
const staleFor = (id, translation) => {
|
|
1416
|
+
if (isTemplate || !translation) return false;
|
|
1417
|
+
const e = edits.get(id);
|
|
1418
|
+
const localised = e?.localisedAt?.[targetLocale];
|
|
1419
|
+
return !!(e?.modifiedAt && localised && e.modifiedAt > localised);
|
|
1420
|
+
};
|
|
1421
|
+
const genderOf = /* @__PURE__ */ new Map();
|
|
1422
|
+
for (const c of loaded.project.cast ?? []) if (c.gender) genderOf.set(c.name, c.gender);
|
|
1423
|
+
const withGender = (context) => {
|
|
1424
|
+
const g = context?.character ? genderOf.get(context.character) : void 0;
|
|
1425
|
+
return g ? { ...context, gender: g } : context;
|
|
1426
|
+
};
|
|
1427
|
+
const qualifierName = new Map(projectQualifiers(loaded.project).map((q) => [q.gameId, q.name]));
|
|
1428
|
+
const lineContext = (b) => {
|
|
1429
|
+
const q = b.kind === "line" && b.qualifier ? qualifierName.get(b.qualifier) ?? b.qualifier : void 0;
|
|
1430
|
+
return { character: b.kind === "line" ? b.character : void 0, kind: b.kind, ...q ? { qualifier: q } : {} };
|
|
1431
|
+
};
|
|
1432
|
+
const entries = [];
|
|
1433
|
+
const push = (id, scene, context) => {
|
|
1434
|
+
const src = source[id];
|
|
1435
|
+
if (src === void 0) return;
|
|
1436
|
+
const translation = isTemplate ? "" : target[id] ?? "";
|
|
1437
|
+
entries.push({ id, scene, source: src, translation, comments: commentsOf(id), context: withGender(context), stale: staleFor(id, translation) });
|
|
1438
|
+
};
|
|
1439
|
+
for (const scene of loaded.scenes) {
|
|
1440
|
+
const action = textIsAction(loaded.project, scene);
|
|
1441
|
+
for (const block of scene.blocks) {
|
|
1442
|
+
walkNodes(block.children, (node) => {
|
|
1443
|
+
if (node.type === "group") {
|
|
1444
|
+
const p = node.prompt;
|
|
1445
|
+
if (p) push(p.id, scene.id, lineContext(p));
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
for (const beat of node.beats ?? []) {
|
|
1449
|
+
if (beat.kind === "gameEvent" || action && beat.kind === "text") continue;
|
|
1450
|
+
push(beat.id, scene.id, lineContext(beat));
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
for (const c of loaded.project.cast ?? []) {
|
|
1456
|
+
if (!c.displayName) continue;
|
|
1457
|
+
const id = castStringKey(c.name);
|
|
1458
|
+
const src = projectSources[id];
|
|
1459
|
+
const translation = isTemplate ? "" : target[id] ?? "";
|
|
1460
|
+
entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: commentsOf(id), context: withGender({ character: c.name }), stale: staleFor(id, translation) });
|
|
1461
|
+
}
|
|
1462
|
+
for (const q of usedQualifiers(loaded.project, loaded.scenes)) {
|
|
1463
|
+
const id = qualifierStringKey(q.gameId);
|
|
1464
|
+
const src = projectSources[id] ?? q.name;
|
|
1465
|
+
const translation = isTemplate ? "" : target[id] ?? "";
|
|
1466
|
+
entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: commentsOf(id), context: { kind: "qualifier" }, stale: staleFor(id, translation) });
|
|
1467
|
+
}
|
|
1468
|
+
const pairs = usedSpeakerPairs(loaded.scenes);
|
|
1469
|
+
if (pairs.length > 0) {
|
|
1470
|
+
const id = SPEAKER_PATTERN_KEY;
|
|
1471
|
+
const src = projectSources[id];
|
|
1472
|
+
const translation = isTemplate ? "" : target[id] ?? "";
|
|
1473
|
+
entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: [SPEAKER_PATTERN_NOTE, ...commentsOf(id)], context: { kind: "speaker pattern" }, stale: staleFor(id, translation) });
|
|
1474
|
+
const labelsOf = new Map((loaded.project.cast ?? []).map((c) => [c.name, c.qualifierLabels]));
|
|
1475
|
+
for (const p of pairs) {
|
|
1476
|
+
const label = labelsOf.get(p.character)?.[p.qualifier];
|
|
1477
|
+
if (!label) continue;
|
|
1478
|
+
const pid = speakerLabelKey(p.character, p.qualifier);
|
|
1479
|
+
const psrc = projectSources[pid] ?? label;
|
|
1480
|
+
const ptranslation = isTemplate ? "" : target[pid] ?? "";
|
|
1481
|
+
const qualifier = qualifierName.get(p.qualifier) ?? p.qualifier;
|
|
1482
|
+
entries.push({ id: pid, scene: PROJECT_LOCALE_SCENE, source: psrc, translation: ptranslation, comments: commentsOf(pid), context: withGender({ character: p.character, kind: "speaker", qualifier }), stale: staleFor(pid, ptranslation) });
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
return { project: loaded.project.project.id, defaultLocale, locale: targetLocale, entries };
|
|
1486
|
+
}
|
|
1487
|
+
var SPEAKER_PATTERN_NOTE = "{name} and {qualifier} are filled in with the speaker's name and the qualifier, so keep both as they are. You can put them in any order.";
|
|
1488
|
+
var layoutOf = (loaded) => projectLayout2(loaded.project);
|
|
1489
|
+
function existingLocPath(loaded, scene, locale) {
|
|
1490
|
+
for (let i = 0; i < loaded.locales.length; i++) {
|
|
1491
|
+
const l = loaded.locales[i];
|
|
1492
|
+
if (l.scene === scene && l.locale === locale) return loaded.localeFiles[i] ?? null;
|
|
1493
|
+
}
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
function targetLocPath(loaded, scene, locale, defaultLocale) {
|
|
1497
|
+
const existing = existingLocPath(loaded, scene, locale);
|
|
1498
|
+
if (existing) return existing;
|
|
1499
|
+
const fromDefault = existingLocPath(loaded, scene, defaultLocale);
|
|
1500
|
+
if (fromDefault) {
|
|
1501
|
+
const parts = fromDefault.split(sep);
|
|
1502
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
1503
|
+
if (parts[i] === defaultLocale) {
|
|
1504
|
+
parts[i] = locale;
|
|
1505
|
+
return parts.join(sep);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const stem = scene === PROJECT_LOCALE_SCENE ? "_project" : scene;
|
|
1510
|
+
return join3(loaded.root, layoutOf(loaded).strings, locale, `${stem}.patterloc`);
|
|
1511
|
+
}
|
|
1512
|
+
function authoringPath(loaded, scene) {
|
|
1513
|
+
const dir = join3(loaded.root, layoutOf(loaded).authoring);
|
|
1514
|
+
if (scene === PROJECT_LOCALE_SCENE) return join3(dir, "_project.patterx");
|
|
1515
|
+
const flow = loaded.sceneFiles[scene];
|
|
1516
|
+
const stem = flow ? basename3(flow).replace(/\.patterflow$/, "") : scene;
|
|
1517
|
+
return join3(dir, `${stem}.patterx`);
|
|
1518
|
+
}
|
|
1519
|
+
var findFile = (files, items, path) => {
|
|
1520
|
+
const i = files.indexOf(path);
|
|
1521
|
+
return i >= 0 ? items[i] : void 0;
|
|
1522
|
+
};
|
|
1523
|
+
function applyLoc(loaded, catalog, opts = {}) {
|
|
1524
|
+
const locale = catalog.locale;
|
|
1525
|
+
if (!locale || locale === loaded.project.locales.default) {
|
|
1526
|
+
return { writes: [], stats: { updated: 0, files: 0, dropped: 0 } };
|
|
1527
|
+
}
|
|
1528
|
+
if (!loaded.project.locales.all.includes(locale)) {
|
|
1529
|
+
throw new Error(`'${locale}' is not one of the project's languages (${loaded.project.locales.all.join(", ")}); add it to the project first`);
|
|
1530
|
+
}
|
|
1531
|
+
const known = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id), PROJECT_LOCALE_SCENE]);
|
|
1532
|
+
const unknown = [...new Set(catalog.entries.filter((e) => e.translation.trim() && !known.has(e.scene)).map((e) => e.scene))];
|
|
1533
|
+
if (unknown.length) {
|
|
1534
|
+
throw new Error(`the file names scene(s) this project does not have: ${unknown.map((u) => `'${u}'`).join(", ")}; nothing was imported`);
|
|
1535
|
+
}
|
|
1536
|
+
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
1537
|
+
const action = actionTextIds(loaded);
|
|
1538
|
+
const bySceneText = /* @__PURE__ */ new Map();
|
|
1539
|
+
const bySceneFresh = /* @__PURE__ */ new Map();
|
|
1540
|
+
for (const e of catalog.entries) {
|
|
1541
|
+
if (!e.translation.trim()) continue;
|
|
1542
|
+
if (action.get(e.scene)?.has(e.id)) continue;
|
|
1543
|
+
(bySceneText.get(e.scene) ?? bySceneText.set(e.scene, /* @__PURE__ */ new Map()).get(e.scene)).set(e.id, e.translation);
|
|
1544
|
+
if (!e.stale) (bySceneFresh.get(e.scene) ?? bySceneFresh.set(e.scene, /* @__PURE__ */ new Set()).get(e.scene)).add(e.id);
|
|
1545
|
+
}
|
|
1546
|
+
const writes = [];
|
|
1547
|
+
let updated = 0;
|
|
1548
|
+
let dropped = 0;
|
|
1549
|
+
let files = 0;
|
|
1550
|
+
const scenes = new Set(bySceneText.keys());
|
|
1551
|
+
for (const l of loaded.locales) if (l.locale === locale && l.scene !== PROJECT_LOCALE_SCENE && known.has(l.scene)) scenes.add(l.scene);
|
|
1552
|
+
for (const scene of scenes) {
|
|
1553
|
+
const strings = bySceneText.get(scene) ?? /* @__PURE__ */ new Map();
|
|
1554
|
+
const path = targetLocPath(loaded, scene, locale, loaded.project.locales.default);
|
|
1555
|
+
const existing = findFile(loaded.localeFiles, loaded.locales, path);
|
|
1556
|
+
const merged = { ...existing?.strings ?? {} };
|
|
1557
|
+
for (const [id, text] of strings) {
|
|
1558
|
+
if (merged[id] !== text) updated++;
|
|
1559
|
+
merged[id] = text;
|
|
1560
|
+
}
|
|
1561
|
+
const actionIds = action.get(scene);
|
|
1562
|
+
for (const id of actionIds ?? []) if (id in merged) {
|
|
1563
|
+
delete merged[id];
|
|
1564
|
+
dropped++;
|
|
1565
|
+
}
|
|
1566
|
+
const file = {
|
|
1567
|
+
schema: existing?.schema ?? STRINGS_SCHEMA,
|
|
1568
|
+
scene,
|
|
1569
|
+
locale,
|
|
1570
|
+
...actionIds ? { textAction: true } : {},
|
|
1571
|
+
strings: merged
|
|
1572
|
+
};
|
|
1573
|
+
const content = canonicalStringify3(file);
|
|
1574
|
+
if (!existing || strings.size > 0 || canonicalStringify3(existing) !== content) {
|
|
1575
|
+
writes.push({ path, content });
|
|
1576
|
+
files++;
|
|
1577
|
+
}
|
|
1578
|
+
const fresh = bySceneFresh.get(scene);
|
|
1579
|
+
if (!fresh?.size) continue;
|
|
1580
|
+
const aPath = authoringPath(loaded, scene);
|
|
1581
|
+
const aExisting = findFile(loaded.authoringFiles, loaded.authoring, aPath);
|
|
1582
|
+
const authoring = aExisting ? { ...aExisting, edits: { ...aExisting.edits } } : { schema: AUTHORING_SCHEMA };
|
|
1583
|
+
const editsMap = { ...authoring.edits ?? {} };
|
|
1584
|
+
for (const id of fresh) editsMap[id] = { ...editsMap[id], localisedAt: { ...editsMap[id]?.localisedAt, [locale]: now } };
|
|
1585
|
+
authoring.edits = editsMap;
|
|
1586
|
+
writes.push({ path: aPath, content: canonicalStringify3(authoring) });
|
|
1587
|
+
}
|
|
1588
|
+
return { writes, stats: { updated, files, dropped } };
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1247
1591
|
// src/pack.ts
|
|
1248
1592
|
import JSZip from "jszip";
|
|
1249
1593
|
import { readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
|
|
1250
|
-
import { dirname as
|
|
1594
|
+
import { dirname as dirname5, join as join5, relative as relative2, sep as sep2 } from "path";
|
|
1251
1595
|
import { parseSource as parseSource3 } from "@patterkit/core";
|
|
1252
1596
|
import { EXTENSION_SHARD as EXTENSION_SHARD2, EXTENSIONS_DIR as EXTENSIONS_DIR2, MANIFEST_EXTENSION as MANIFEST_EXTENSION2 } from "@patterkit/model";
|
|
1253
1597
|
import { ARCHIVE_ENTRY_OPTS } from "@wildwinter/toolkit/archive";
|
|
1254
1598
|
|
|
1255
1599
|
// src/handoff.ts
|
|
1256
1600
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1257
|
-
import { basename as
|
|
1601
|
+
import { basename as basename4, join as join4 } from "path";
|
|
1258
1602
|
import { HANDOFF_SCHEMA } from "@patterkit/model";
|
|
1259
1603
|
var HANDOFF_DIR = "handoffs";
|
|
1260
1604
|
var HANDOFF_EXT = ".json";
|
|
1261
1605
|
function handoffPath(root, id) {
|
|
1262
|
-
return
|
|
1606
|
+
return join4(root, HANDOFF_DIR, `${id}${HANDOFF_EXT}`);
|
|
1263
1607
|
}
|
|
1264
1608
|
function serialiseHandoff(handoff) {
|
|
1265
1609
|
return JSON.stringify(handoff, null, 2) + "\n";
|
|
@@ -1276,11 +1620,11 @@ function parseHandoff(text) {
|
|
|
1276
1620
|
function readHandoffs(root) {
|
|
1277
1621
|
const handoffs = [];
|
|
1278
1622
|
const issues = [];
|
|
1279
|
-
for (const file of walkFiles(
|
|
1623
|
+
for (const file of walkFiles(join4(root, HANDOFF_DIR), HANDOFF_EXT)) {
|
|
1280
1624
|
try {
|
|
1281
1625
|
handoffs.push(parseHandoff(readFileSync3(file, "utf8")));
|
|
1282
1626
|
} catch (e) {
|
|
1283
|
-
issues.push({ file, message: `${
|
|
1627
|
+
issues.push({ file, message: `${basename4(file)}: ${e instanceof Error ? e.message : String(e)}` });
|
|
1284
1628
|
}
|
|
1285
1629
|
}
|
|
1286
1630
|
handoffs.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
@@ -1340,7 +1684,7 @@ var SHARD_EXTENSIONS = [".patterflow", ".patterloc", ".patterx", ".patterproj",
|
|
|
1340
1684
|
var ENTRY_OPTS = ARCHIVE_ENTRY_OPTS;
|
|
1341
1685
|
async function runPack(startPath) {
|
|
1342
1686
|
const projectFile = findProjectFile(startPath);
|
|
1343
|
-
const root =
|
|
1687
|
+
const root = dirname5(projectFile);
|
|
1344
1688
|
const project = parseSource3(readFileSync4(projectFile, "utf8"));
|
|
1345
1689
|
const unresolved = sidecarIssues(walkFiles(root, CONFLICT_SIDECAR));
|
|
1346
1690
|
if (unresolved.length) {
|
|
@@ -1349,7 +1693,7 @@ async function runPack(startPath) {
|
|
|
1349
1693
|
` + unresolved.map((i) => ` ${i.file}`).join("\n")
|
|
1350
1694
|
);
|
|
1351
1695
|
}
|
|
1352
|
-
const shardFiles = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).map((abs) => ({ abs, rel: relative2(root, abs).split(
|
|
1696
|
+
const shardFiles = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).map((abs) => ({ abs, rel: relative2(root, abs).split(sep2).join("/") }));
|
|
1353
1697
|
const marked = [...new Set(unresolvedMarkIssues(shardFiles.map((f) => ({ file: f.rel, text: readFileSync4(f.abs, "utf8") }))).map((i) => i.file))];
|
|
1354
1698
|
if (marked.length) {
|
|
1355
1699
|
throw new Error(
|
|
@@ -1359,7 +1703,7 @@ async function runPack(startPath) {
|
|
|
1359
1703
|
}
|
|
1360
1704
|
const files = [...shardFiles, ...extensionManifests(root)].sort((a, b) => a.rel.localeCompare(b.rel));
|
|
1361
1705
|
const scopes = gameScopesSnapshot(root, project);
|
|
1362
|
-
const handoffs = walkFiles(
|
|
1706
|
+
const handoffs = walkFiles(join5(root, HANDOFF_DIR), ".json").map((abs) => ({ rel: relative2(root, abs).split(sep2).join("/"), text: readFileSync4(abs, "utf8") })).filter((f) => {
|
|
1363
1707
|
try {
|
|
1364
1708
|
return !parseHandoff(f.text).closed;
|
|
1365
1709
|
} catch {
|
|
@@ -1383,11 +1727,11 @@ async function runPack(startPath) {
|
|
|
1383
1727
|
function extensionManifests(root) {
|
|
1384
1728
|
let names;
|
|
1385
1729
|
try {
|
|
1386
|
-
names = readdirSync3(
|
|
1730
|
+
names = readdirSync3(join5(root, EXTENSIONS_DIR2), { withFileTypes: true }).filter((e) => e.isFile() && !e.name.startsWith(".") && e.name.endsWith(MANIFEST_EXTENSION2)).map((e) => e.name);
|
|
1387
1731
|
} catch {
|
|
1388
1732
|
return [];
|
|
1389
1733
|
}
|
|
1390
|
-
return names.map((name) => ({ abs:
|
|
1734
|
+
return names.map((name) => ({ abs: join5(root, EXTENSIONS_DIR2, name), rel: `${EXTENSIONS_DIR2}/${name}` }));
|
|
1391
1735
|
}
|
|
1392
1736
|
var isManifestEntry = (name) => {
|
|
1393
1737
|
const rest = name.startsWith(`${EXTENSIONS_DIR2}/`) ? name.slice(EXTENSIONS_DIR2.length + 1) : void 0;
|
|
@@ -1467,17 +1811,17 @@ function gitattributesHint(loaded) {
|
|
|
1467
1811
|
const files = [];
|
|
1468
1812
|
let dir = loaded.root, repo = false;
|
|
1469
1813
|
for (; ; ) {
|
|
1470
|
-
const f =
|
|
1814
|
+
const f = join6(dir, ".gitattributes");
|
|
1471
1815
|
if (existsSync4(f)) files.push(f);
|
|
1472
|
-
if (existsSync4(
|
|
1816
|
+
if (existsSync4(join6(dir, ".git"))) {
|
|
1473
1817
|
repo = true;
|
|
1474
1818
|
break;
|
|
1475
1819
|
}
|
|
1476
|
-
const up =
|
|
1820
|
+
const up = dirname6(dir);
|
|
1477
1821
|
if (up === dir) break;
|
|
1478
1822
|
dir = up;
|
|
1479
1823
|
}
|
|
1480
|
-
const consulted = repo ? files : files.filter((f) =>
|
|
1824
|
+
const consulted = repo ? files : files.filter((f) => dirname6(f) === loaded.root);
|
|
1481
1825
|
const texts = consulted.map((f) => {
|
|
1482
1826
|
try {
|
|
1483
1827
|
return readFileSync5(f, "utf8");
|
|
@@ -1499,7 +1843,7 @@ function gameScopesIssues(loaded, notes) {
|
|
|
1499
1843
|
const gs = loaded.gameScopes;
|
|
1500
1844
|
if (!gs) return out;
|
|
1501
1845
|
for (const i of gs.issues) out.push({ file: i.file, severity: i.severity, message: i.message });
|
|
1502
|
-
for (const n of notes) out.push({ file:
|
|
1846
|
+
for (const n of notes) out.push({ file: join6(gs.dir, n.file), severity: "warning", message: n.message });
|
|
1503
1847
|
const stale = patterScopesStale(loaded.project, gs);
|
|
1504
1848
|
if (stale) out.push({ ...stale, severity: "warning" });
|
|
1505
1849
|
return out;
|
|
@@ -1513,7 +1857,7 @@ function unplayableScopes(loaded, compiled) {
|
|
|
1513
1857
|
}));
|
|
1514
1858
|
}
|
|
1515
1859
|
function orphanShards(loaded, tree = walkFilesByExt(loaded.root, SHARD_EXTENSIONS)) {
|
|
1516
|
-
const layout =
|
|
1860
|
+
const layout = projectLayout3(loaded.project);
|
|
1517
1861
|
const kind = {
|
|
1518
1862
|
".patterflow": { what: "scene", home: layout.flow },
|
|
1519
1863
|
".patterloc": { what: "strings", home: layout.strings },
|
|
@@ -1528,9 +1872,9 @@ function orphanShards(loaded, tree = walkFilesByExt(loaded.root, SHARD_EXTENSION
|
|
|
1528
1872
|
const out = [];
|
|
1529
1873
|
for (const [ext, { what, home }] of Object.entries(kind)) {
|
|
1530
1874
|
for (const file of tree.get(ext) ?? []) {
|
|
1531
|
-
if (home ? inside(file,
|
|
1875
|
+
if (home ? inside(file, join6(loaded.root, home)) : file === loaded.projectFile) continue;
|
|
1532
1876
|
const rel = relative3(loaded.root, file);
|
|
1533
|
-
const message = home ? `${rel} is a ${what} file outside the project's folders: nothing loads it, so none of it is in the project. Move it under ${home}, or delete it.` : `${rel} is a second project file: only ${
|
|
1877
|
+
const message = home ? `${rel} is a ${what} file outside the project's folders: nothing loads it, so none of it is in the project. Move it under ${home}, or delete it.` : `${rel} is a second project file: only ${basename5(loaded.projectFile)} is read. Delete it, or move it out of the project.`;
|
|
1534
1878
|
out.push({ file, message });
|
|
1535
1879
|
}
|
|
1536
1880
|
}
|
|
@@ -1559,7 +1903,7 @@ function checkBundles(loaded, compiled, inTree) {
|
|
|
1559
1903
|
}
|
|
1560
1904
|
function locIssues(loaded) {
|
|
1561
1905
|
const out = [];
|
|
1562
|
-
const scenes = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id),
|
|
1906
|
+
const scenes = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id), PROJECT_LOCALE_SCENE2]);
|
|
1563
1907
|
const declared = new Set(loaded.project.locales.all);
|
|
1564
1908
|
const bySlot = /* @__PURE__ */ new Map();
|
|
1565
1909
|
const byKey = /* @__PURE__ */ new Map();
|
|
@@ -1580,6 +1924,21 @@ function locIssues(loaded) {
|
|
|
1580
1924
|
if (seen.text !== text) out.push({ file, severity: "error", message: `'${key}' has different '${l.locale}' text here and in ${seen.file}; the project will not build until one is removed` });
|
|
1581
1925
|
}
|
|
1582
1926
|
});
|
|
1927
|
+
const changes = /* @__PURE__ */ new Map();
|
|
1928
|
+
for (const c of actionTextChanges(loaded)) {
|
|
1929
|
+
const key = `${c.scene}|${c.locale}`;
|
|
1930
|
+
const n = changes.get(key) ?? changes.set(key, { dropped: 0, added: 0 }).get(key);
|
|
1931
|
+
n[c.change]++;
|
|
1932
|
+
}
|
|
1933
|
+
const nameOf = new Map(loaded.scenes.map((sc) => [sc.id, sc.name]));
|
|
1934
|
+
for (const [key, n] of changes) {
|
|
1935
|
+
const [scene, locale] = key.split("|");
|
|
1936
|
+
const file = existingLocPath(loaded, scene, locale) ?? "";
|
|
1937
|
+
const name = nameOf.get(scene) ?? scene;
|
|
1938
|
+
const lines = (k) => `${k} text line${k === 1 ? "" : "s"}`;
|
|
1939
|
+
if (n.dropped) out.push({ file, severity: "warning", message: `the text in '${name}' is now action (a dialogue scene's text, which the project doesn't localise), but the '${locale}' strings still translate ${lines(n.dropped)}: the next loc import drops ${n.dropped === 1 ? "it" : "them"}` });
|
|
1940
|
+
if (n.added) out.push({ file, severity: "warning", message: `the text in '${name}' is no longer action, so ${lines(n.added)} ${n.added === 1 ? "isn't" : "aren't"} in the '${locale}' strings yet: the next loc export sends ${n.added === 1 ? "it" : "them"} for translation` });
|
|
1941
|
+
}
|
|
1583
1942
|
return out;
|
|
1584
1943
|
}
|
|
1585
1944
|
var hygieneCache = /* @__PURE__ */ new Map();
|
|
@@ -1647,11 +2006,11 @@ function checkHygiene(files) {
|
|
|
1647
2006
|
|
|
1648
2007
|
// src/export.ts
|
|
1649
2008
|
import { readFileSync as readFileSync6 } from "fs";
|
|
1650
|
-
import { canonicalStringify as
|
|
2009
|
+
import { canonicalStringify as canonicalStringify4 } from "@patterkit/core";
|
|
1651
2010
|
|
|
1652
2011
|
// src/audio-index.ts
|
|
1653
2012
|
import { readdirSync as readdirSync4 } from "fs";
|
|
1654
|
-
import { join as
|
|
2013
|
+
import { join as join7, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
|
|
1655
2014
|
import { deriveRecordingFolders, DEFAULT_RECORDING_STATUSES } from "@patterkit/model";
|
|
1656
2015
|
var AUDIO_MANIFEST_SCHEMA = "patter/audio@0";
|
|
1657
2016
|
var AUDIO_MANIFEST_FILE = "patteraudio.json";
|
|
@@ -1702,14 +2061,14 @@ function audioManifest(snapshot, projectRoot, audioRoot) {
|
|
|
1702
2061
|
const clips = {};
|
|
1703
2062
|
for (const beatId of Object.keys(snapshot).sort()) {
|
|
1704
2063
|
const entry = snapshot[beatId];
|
|
1705
|
-
clips[beatId] = { file: relative4(base, entry.path).split(
|
|
2064
|
+
clips[beatId] = { file: relative4(base, entry.path).split(sep3).join("/"), status: entry.status };
|
|
1706
2065
|
}
|
|
1707
2066
|
return JSON.stringify({ schema: AUDIO_MANIFEST_SCHEMA, clips }, null, 2) + "\n";
|
|
1708
2067
|
}
|
|
1709
2068
|
function audioManifestWrite(loaded, snapshot) {
|
|
1710
2069
|
const p = loaded.project;
|
|
1711
2070
|
if (!snapshot || !audioFoldersActive(p) || !Object.keys(snapshot).length) return void 0;
|
|
1712
|
-
return { path:
|
|
2071
|
+
return { path: join7(resolve4(loaded.root, p.audioRoot), AUDIO_MANIFEST_FILE), content: audioManifest(snapshot, loaded.root, p.audioRoot) };
|
|
1713
2072
|
}
|
|
1714
2073
|
|
|
1715
2074
|
// src/export.ts
|
|
@@ -1755,7 +2114,7 @@ function runExportFull(loaded, opts = {}) {
|
|
|
1755
2114
|
}
|
|
1756
2115
|
function planBuild(loaded, opts = {}) {
|
|
1757
2116
|
const bundle = runExport(loaded, opts);
|
|
1758
|
-
const text =
|
|
2117
|
+
const text = canonicalStringify4(bundle, { trailingComma: false });
|
|
1759
2118
|
const scopes = patterScopesWrite(loaded.project, loaded.gameScopes);
|
|
1760
2119
|
const audio = audioManifestWrite(loaded, opts.audio ?? scanAudio(loaded));
|
|
1761
2120
|
return {
|
|
@@ -1766,11 +2125,11 @@ function planBuild(loaded, opts = {}) {
|
|
|
1766
2125
|
}
|
|
1767
2126
|
|
|
1768
2127
|
// src/export-html.ts
|
|
1769
|
-
import { canonicalStringify as
|
|
2128
|
+
import { canonicalStringify as canonicalStringify5 } from "@patterkit/core";
|
|
1770
2129
|
import { DEFAULT_PAD_AFTER } from "@patterkit/model";
|
|
1771
2130
|
|
|
1772
2131
|
// src/playable-runtime.ts
|
|
1773
|
-
var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var ie=Object.defineProperty;var gt=Object.getOwnPropertyDescriptor;var ft=Object.getOwnPropertyNames;var ht=Object.prototype.hasOwnProperty;var mt=(r,e)=>{for(var t in e)ie(r,t,{get:e[t],enumerable:!0})},yt=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ft(e))!ht.call(r,s)&&s!==t&&ie(r,s,{get:()=>e[s],enumerable:!(n=gt(e,s))||n.enumerable});return r};var St=r=>yt(ie({},"__esModule",{value:!0}),r);var _t={};mt(_t,{Checkpoint:()=>U,DEFAULT_PAD_AFTER:()=>E,Engine:()=>ne,Flow:()=>O,buildTagIndex:()=>te,compareStages:()=>N,describeBundle:()=>st,effectiveGameData:()=>ot,extensionDecls:()=>at,extensionSettings:()=>ct,extensionsOf:()=>lt,gameDataFields:()=>it,gameDataValue:()=>ye,nextStage:()=>B,stageIndex:()=>P});function T(r,e){return r!==void 0&&Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0}function R(r){switch(r[0]){case"b":return{kind:"bool",value:r[1]};case"n":return{kind:"number",value:r[1]};case"s":return{kind:"string",value:r[1]};case"sv":return{kind:"scopedvar",scope:r[1],name:r[2]};case"u":return{kind:"unary",op:r[1],operand:R(r[2])};case"bin":return{kind:"binary",op:r[1],left:R(r[2]),right:R(r[3])};case"call":{let e=r.slice(2).map(R);return{kind:"call",name:r[1],args:e}}case"fd":return{kind:"flagdelta",sign:r[1],name:r[2]}}}var u=class extends Error{constructor(e){super(e),this.name="EvalError"}};function J(r,e,t){let n=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),s=i=>{switch(i.kind){case"bool":return i.value;case"number":return i.value;case"string":return i.value;case"scopedvar":{let o=T(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):T(o,i.name);if(a===void 0){if(n.get(i.scope)==="throw")throw new u(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!T(t.functions,i.name)){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new u(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=oe(a,e);if(c===void 0)throw new u("advance() needs a quality reference (@scope.name of a quality property)");let l=v(s(a),c,"advance");return c[Math.min(l+1,c.length-1)]}let o=T(t.functions,i.name);if(!o)throw new u(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new u("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=s(i.operand);if(typeof a!="boolean")throw new u(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new u(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'and\' requires boolean operands, left is ${typeof h}`);if(!h)return!1;let f=s(i.right);if(typeof f!="boolean")throw new u(`\'and\' requires boolean operands, right is ${typeof f}`);return f}if(i.op==="or"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'or\' requires boolean operands, left is ${typeof h}`);if(h)return!0;let f=s(i.right);if(typeof f!="boolean")throw new u(`\'or\' requires boolean operands, right is ${typeof f}`);return f}let o=s(i.left),a=s(i.right),c=oe(i.left,e),l=oe(i.right,e),d=c??l;if(d!==void 0){if(c&&l&&!wt(c,l)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new u(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return v(o,d,">")>v(a,d,">");case">=":return v(o,d,">=")>=v(a,d,">=");case"<":return v(o,d,"<")<v(a,d,"<");case"<=":return v(o,d,"<=")<=v(a,d,"<=");case"+":case"-":case"*":case"/":throw new u(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return Re(o,a);case"!=":return!Re(o,a);case">":return C(o,a,">"),o>a;case">=":return C(o,a,">="),o>=a;case"<":return C(o,a,"<"),o<a;case"<=":return C(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new u(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return C(o,a,"-"),o-a;case"*":return C(o,a,"*"),o*a;case"/":if(C(o,a,"/"),a===0)throw new u("division by zero");return o/a}}}};return s(r)}function Re(r,e){if(Array.isArray(r)||Array.isArray(e)){if(!Array.isArray(r)||!Array.isArray(e)||r.length!==e.length)return!1;let t=[...r].sort(),n=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==n[s])return!1;return!0}return r===e}function C(r,e,t){if(typeof r!="number"||typeof e!="number")throw new u(`\'${t}\' requires numeric operands, got ${typeof r} and ${typeof e}`)}function oe(r,e){if(!(r.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(r.scope,r.name)}function v(r,e,t){if(typeof r!="string")throw new u(`\'${t}\' on a quality compares stages, got ${typeof r}`);let n=e.indexOf(r);if(n<0)throw new u(`"${r}" is not a stage of this quality (stages: ${e.join(", ")})`);return n}var wt=(r,e)=>r.length===e.length&&r.every((t,n)=>t===e[n]);function L(r){if(Number.isNaN(r)||!Number.isFinite(r))return 0;let e=Math.trunc(r)%4294967296;return e<0?e+4294967296:e}function ae(r){let e=L(r);return{next(){e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296},state(){return e}}}var le=r=>Array.isArray(r)?r:r.stages??[],vt=r=>Array.isArray(r)?"flags":r==null?"nothing":typeof r;function P(r,e){return typeof e=="string"?le(r).indexOf(e):-1}function ce(r,e){if(typeof e!="string")throw new u(`a stage is a name, got ${vt(e)}`);let t=r.indexOf(e);if(t<0)throw new u(`"${e}" is not a stage of this quality (stages: ${r.join(", ")})`);return t}function N(r,e,t){let n=le(r),s=ce(n,e)-ce(n,t);return s<0?-1:s>0?1:0}function B(r,e){let t=le(r);return t[Math.min(ce(t,e)+1,t.length-1)]}function de(r,e,t){switch(r.type){case"boolean":return typeof e=="boolean";case"number":return typeof e=="number";case"string":case"text":case"multiline":return typeof e=="string";case"enum":return typeof e!="string"?!1:e===""&&t?.noneChosen?!0:!Array.isArray(r.values)||r.values.includes(e);case"flags":return Array.isArray(e)&&e.every(n=>typeof n=="string"&&(!Array.isArray(r.values)||r.values.includes(n)));case"quality":return typeof e=="string"&&(!Array.isArray(r.stages)||r.stages.includes(e));default:return!0}}var bt={name:"check_flags",count:r=>Math.max(1,r.args.length-1)},xt=[bt];function Ce(r,e,t){let n=t?.countingCalls??xt;return Q(r,t?.want??!0,e,n)}function Q(r,e,t,n){if(r.kind==="binary"&&(r.op==="and"||r.op==="or")){let s=Q(r.left,e,t,n),i=Q(r.right,e,t,n);return r.op==="and"===e?s>0&&i>0?s+i:0:Math.max(s,i)}if(r.kind==="unary"&&r.op==="not")return Q(r.operand,!e,t,n);if(r.kind==="call"){let s=n.find(i=>i.name===r.name);if(s){let i=s.count(r),o=t(r);return e?o?i:0:o?0:1}}return t(r)===e?1:0}var k=class r{values=Object.create(null);decls=new Map;subscribers=new Set;auditors=new Set;norm;pathPrefix;constructor(e=[],t){this.norm=t?.normalise??(n=>n.toLowerCase()),this.pathPrefix=t?.pathPrefix??"",this.seed(e)}seed(e){for(let t of e){let n=this.norm(t.name);this.decls.set(n,t),this.values[n]=structuredClone(t.default??A(t))}}get(e){return this.values[this.norm(e)]}normalise(e){return this.norm(e)}writable(e){return this.decls.get(this.norm(e))?.writable!==!1}set(e,t,n){let s=this.norm(e);if(!n?.host&&!this.writable(s))throw new Error(`\'${e}\' is read-only`);let i={name:s,prev:this.values[s],next:t,silent:n?.silent??!1,reason:n?.reason};this.values[s]=t;for(let o of[...this.auditors])o(i);if(!i.silent)for(let o of[...this.subscribers])o(i);return i}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>Ie(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new r([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e,t){let n=new Map;for(let[i,o]of Object.entries(e)){let a=this.norm(i);this.values[a]=structuredClone(o),n.set(a,o)}let s=[];if(!t?.refit)return s;for(let[i,o]of this.decls){if(!n.has(i))continue;let a=n.get(i);de(o,a)||(this.values[i]=structuredClone(A(o)),s.push({name:i,value:a}))}return s}};function Ie(r,e,t,n,s=""){let i=n??r.name.toLowerCase();return{name:i,path:s+i,type:r.type,value:e,default:r.default??A(r),...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:t??r.writable??!0}}var kt=r=>r.toLowerCase(),pe=1,z=class{scopes=new Map;parked=new Map;parkedRefit=new Set;misfits=[];rev=0;get revision(){return this.rev}defineOwned(e,t,n){let s=typeof n=="string"?{pathPrefix:n}:n??{},i=new k(t,{pathPrefix:s.pathPrefix??`${e}.`,...s.normalise?{normalise:s.normalise}:{}});return this.mountOwned(e,i,s.owner!==void 0?{owner:s.owner}:void 0)}mountOwned(e,t,n){this.assertFree(e,n?.owner),this.scopes.set(e,{kind:"owned",bag:t,...n?.owner!==void 0?{owner:n.owner}:{}}),this.rev++;let s=this.parked.get(e);return s&&(this.note(e,t.load(s,{refit:this.parkedRefit.has(e)})),this.parked.delete(e),this.parkedRefit.delete(e)),this}remove(e,t){let n=this.scopes.get(e);if(!n)throw new Error(`unknown scope \'@${e}\'`);return t?.keep&&n.kind==="owned"&&(this.parked.set(e,n.bag.save()),this.parkedRefit.delete(e)),this.scopes.delete(e),this.rev++,this}discardParked(e){if(e===void 0)this.parked.clear(),this.parkedRefit.clear();else for(let t of[...this.parked.keys()])t.startsWith(e)&&(this.parked.delete(t),this.parkedRefit.delete(t));return this}takeMisfits(e){if(!e){let n=this.misfits;return this.misfits=[],n}let t=this.misfits.filter(n=>e(n.scope));return this.misfits=this.misfits.filter(n=>!e(n.scope)),t}note(e,t){for(let n of t)this.misfits.push({scope:e,name:n.name,value:n.value})}ownedBag(e){let t=this.scopes.get(e);if(!t||t.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);return t.bag}reseedOwned(e,t){return this.ownedBag(e).reseed(t),this}defineForeign(e,t,n=[],s=!0){let i=typeof s=="boolean"?{writable:s}:s;this.assertFree(e,i.owner);let o=i.normalise??kt,a=new Map;for(let c of n)a.set(o(c.name),c);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:a,scopeWritable:i.writable??!0,norm:o,...i.owner!==void 0?{owner:i.owner}:{}}),this.rev++,this}has(e){return this.scopes.has(e)}get(e,t){let n=this.scopes.get(e);if(n)return n.kind==="owned"?n.bag.get(t):n.resolver.get(n.norm(t))}set(e,t,n,s){let i=this.scopes.get(e);if(!i)throw new Error(`unknown scope \'@${e}\'`);if(i.kind==="owned"){if(!s?.host&&!i.bag.writable(t))throw new Error(`\'@${e}.${t}\' is read-only`);i.bag.set(t,n,s?.host?{host:!0}:void 0);return}let o=i.norm(t);if(!i.resolver.set)throw new Error(`\'@${e}.${t}\' is read-only`);if(!s?.host&&!this.foreignWritable(i,o))throw new Error(`\'@${e}.${t}\' is read-only`);i.resolver.set(o,n)}foreignWritable(e,t){return e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}listProperties(){let e=[];for(let[t,n]of this.scopes){let s=n.owner!==void 0?{owner:n.owner}:{};if(n.kind==="owned")for(let i of n.bag.rows())e.push({scope:t,...s,...i});else for(let[i,o]of n.decls)e.push({scope:t,...s,...Ie(o,n.resolver.get(i),this.foreignWritable(n,i),i,`${t}.`)})}return e}toEvalContext(e,t){let n=this.view(t?.aliases),s={};for(let[o,a]of n)s[o]=a.kind==="owned"?a.bag.values:a.resolver;let i=this.qualityLadders(n);return i.size===0?{scopes:s,host:e}:{scopes:s,host:e,qualities:(o,a)=>{let c=n.get(o);return c?i.get(o)?.get(Et(c)(a)):void 0}}}view(e){let t=new Map(this.scopes);for(let[n,s]of Object.entries(e??{})){let i=this.scopes.get(s);if(!i)throw new Error(`alias \'@${n}\' names \'${s}\', which is not registered`);t.set(n,i)}return t}qualityLadders(e){let t=new Map;for(let[n,s]of e)for(let[i,o]of De(s)){if(o.type!=="quality"||o.stages===void 0)continue;let a=t.get(n);a||(a=new Map,t.set(n,a)),a.set(i,o.stages)}return t}toSchema(e){let t=new Map;for(let[n,s]of this.view(e?.aliases)){let i=De(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(n,o)}return{properties:t}}save(){let e={};for(let[t,n]of this.scopes)n.kind==="owned"&&(e[t]=n.bag.save());for(let[t,n]of this.parked)e[t]=structuredClone(n);return e}load(e,t){t?.keepParked||(this.parked.clear(),this.parkedRefit.clear());let n=t?.refit===!0;for(let[s,i]of Object.entries(e)){let o=this.scopes.get(s);o?.kind==="owned"?this.note(s,o.bag.load(i,{refit:n})):o||(this.parked.set(s,structuredClone(i)),n?this.parkedRefit.add(s):this.parkedRefit.delete(s))}}saveFragment(){return{version:pe,scopes:this.save()}}loadFragment(e){if(e.version!==pe)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${pe})`);this.load(e.scopes)}assertFree(e,t){let n=this.scopes.get(e);if(!n)return;let s=n.owner!==void 0?` by ${n.owner}`:"",i=t!==void 0?` (wanted by ${t})`:"";throw new Error(`scope \'@${e}\' is already registered${s}${i}`)}};function De(r){return r.kind==="foreign"?[...r.decls.entries()]:r.bag.declarations().map(e=>[r.bag.normalise(e.name),e])}function Et(r){return r.kind==="foreign"?r.norm:e=>r.bag.normalise(e)}function A(r){if(r.default!==void 0)return r.default;switch(r.type){case"boolean":return!1;case"number":return 0;case"string":case"text":case"multiline":return"";case"enum":return r.values?.[0]??"";case"flags":return[];case"quality":return r.stages?.[0]??"";default:return!1}}var Oe=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function j(r){return r.ctx.host??{}}var ue={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(r,e){if(r.length!==2)throw new u("random(a, b) requires exactly 2 arguments");let t=j(e).nextRandom;if(!t)throw new u("random() called without a PRNG in context");let n=e.evaluate(r[0]),s=e.evaluate(r[1]);if(typeof n!="number"||typeof s!="number")throw new u("random(a, b) arguments must be numbers");if(!Number.isInteger(n)||!Number.isInteger(s))throw new u("random(a, b) arguments must be integers");let i=Math.min(n,s),o=Math.max(n,s);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:Ne("check_flags"),eval(r,e){let t=Pe(r[0],e,"check_flags");for(let n=1;n<r.length;n++){let s=r[n];if(s.kind!=="flagdelta")throw new u("check_flags() flag args must be +flagName or -flagName");if(s.sign==="+"?!t.includes(s.name):t.includes(s.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:Ne("set_flags"),eval(r,e){let t=[...Pe(r[0],e,"set_flags")];for(let n=1;n<r.length;n++){let s=r[n];if(s.kind!=="flagdelta")throw new u("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let i=t.indexOf(s.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:X("visits"),eval:(r,e)=>j(e).visits?.(Y(r,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:X("seen"),eval:(r,e)=>(j(e).visits?.(Y(r,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:X("patter_visits"),eval:(r,e)=>j(e).patterVisits?.(Y(r,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:X("patter_seen"),eval:(r,e)=>(j(e).patterVisits?.(Y(r,e,"patter_seen"))??0)>0}}},Sn=Oe.map(r=>r.token).filter(r=>r!=="patter"&&r!=="scene");function Be(r,e){let t=r.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function Y(r,e,t){let n=e.evaluate(r[0]);if(typeof n!="string")throw new u(`${t}(id) requires a string node id`);return n}function X(r){return(e,t)=>{let n=e[0];n&&n.kind!=="string"&&t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(id): the argument must be a string id literal (a scene / block / node id)`})}}function Pe(r,e,t){if(!r)throw new u(`${t}() requires at least one argument (the flags variable)`);let n=e.evaluate(r);if(Array.isArray(n))return n;if(n===!1||n===null||n===void 0)return[];throw new u(`${t}() first argument must be a flags property`)}function Ne(r){return(e,t)=>{if(e.length===0)return;let n=e[0];if(n.kind!=="scopedvar"){t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(): first argument must be a flags property reference (@name or @scope.name)`});return}let s=t.schema.properties.get(n.scope)?.get(n.name);if(s&&s.type!=="flags"){let i=n.scope===t.defaultScope?n.name:`${n.scope}.${n.name}`;t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(): \'@${i}\' is not a flags property (got ${s.type})`});return}for(let i=1;i<e.length;i++){let o=e[i];o.kind!=="flagdelta"?t.report({path:[...t.path,"args",i],kind:"wrong-arg-type",severity:"error",message:`${r}(): argument ${i+1} must be +flagName or -flagName`}):s?.type==="flags"&&s.enumValues&&!s.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${r}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var Rt=/^@[A-Za-z0-9_.]+$/;function*Ct(r){let e="",t=0;for(;t<r.length;){let n=r[t];if(n==="{"&&r[t+1]==="{"){e+="{",t+=2;continue}if(n==="}"&&r[t+1]==="}"){e+="}",t+=2;continue}if(n==="{"){let s=r.indexOf("}",t+1);if(s!==-1){let i=r.slice(t,s+1),o=r.slice(t+1,s).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:Rt.test(o)?o:void 0},t=s+1;continue}e+=i,t=s+1;continue}}e+=n,t+=1}e&&(yield{kind:"text",value:e})}function Dt(r){return Array.isArray(r)?r.join(", "):typeof r=="boolean"?r?"true":"false":String(r)}function It(r){return r===" "||r===" "||r===`\n`||r==="\\r"||r==="\\f"||r==="\\v"}function Ot(r){let e="",t=!1;for(let n of r){if(It(n)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=n}return e}function Ae(r,e,t){if(e.length===0||r.indexOf(e)<0)return r;let n="",s=0,i=!1;for(;s<r.length;){if(r.startsWith(e,s)){let o=r.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}n+=r.slice(s);break}n+=r[s],s+=1}return i?Ot(n):r}function Fe(r,e){if(r.indexOf("{")<0)return r;let t="";for(let n of Ct(r)){if(n.kind==="text"){t+=n.value;continue}if(!n.ref){t+=n.raw;continue}let s=e(n.ref);t+=s===void 0?"":Dt(s)}return t}function D(r,e){for(let t of r){e(t);let n=t.children;n&&D(n,e)}}function Pt(r){return r.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function y(r){let e=r.gameId?.trim();return e||Pt(r.name)}var E=.6;function Z(r){return`cast:${r}`}function ee(r){return`qualifier:${r}`}function Te(r,e){return`speaker:${r}:${e}`}var Le="speaker:pattern",je="{name} ({qualifier})";function Me(r,e,t){return r.split("{name}").map(n=>n.split("{qualifier}").join(t)).join(e)}var ge={open:"[",close:"]"},Ve="SFX";var M=3;function V(r){let e=new Set,t=[];for(let n of r)e.has(n)||(e.add(n),t.push(n));return t}function te(r){let e=new Map,t=(n,s)=>{let i=V([...s,...n.tags??[]]);if(e.set(n.id,i),n.type==="group"&&n.prompt&&e.set(n.prompt.id,V([...i,...n.prompt.tags??[]])),n.type==="group")for(let o of n.children)t(o,i);else for(let o of n.beats??[])e.set(o.id,V([...i,...o.tags??[]]))};for(let n of Object.values(r.scenes)){let s=V(n.tags??[]);e.set(n.id,s);for(let i of n.blocks){let o=V([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var Ge=r=>r.kind==="line"||r.kind==="text",G=r=>typeof r=="number"&&Number.isFinite(r)?r:void 0;function qe(r){let e=new Map,t=(i,o,a)=>{let c=i.kind==="gameEvent"?void 0:G(i.padAfter),l=c??o;a&&l<0&&(l=0),e.set(i.id,c!==void 0?{own:c,resolved:l}:{resolved:l})},n=(i,o)=>{let a=G(i.padAfterDefault)??o;if(i.type==="group"){i.prompt&&t(i.prompt,a,!1);for(let d of i.children)n(d,a);return}let c=i.beats??[],l=-1;c.forEach((d,h)=>{Ge(d)&&(l=h)}),c.forEach((d,h)=>{Ge(d)&&t(d,a,h===l)})},s=G(r.padAfterDefault)??E;for(let i of Object.values(r.scenes)){let o=G(i.padAfterDefault)??s;for(let a of i.blocks){let c=G(a.padAfterDefault)??o;for(let l of a.children)n(l,c)}}return e}var $e=new WeakMap,_e=new WeakMap;function Nt(r){let e=_e.get(r);return e||(e=te(r),_e.set(r,e)),e}var He=new WeakMap;function Bt(r){let e=He.get(r);return e||(e=qe(r),He.set(r,e)),e}var Ue=new WeakMap;function At(r){let e=Ue.get(r);if(e)return e;e=new Map;for(let t of Object.values(r.scenes))for(let n of t.blocks)D(n.children,s=>{if(s.type==="group"){s.prompt&&e.set(s.prompt.id,s.prompt);return}for(let i of s.beats??[])e.set(i.id,i)});return Ue.set(r,e),e}function I(r,e){return r.tagIndex.get(e)?.slice()}var _="Patter",q=r=>r.replace(/%/g,"%25").replace(/\\//g,"%2F"),Ft=r=>r.replace(/%2F/g,"/").replace(/%25/g,"%"),We=Symbol("ownsRegistry"),w={stage:r=>`patter/scene/${q(r)}`,flow:r=>`patter/flow/${q(r)}/`,flowGlobals:r=>`patter/flow/${q(r)}/patter`,flowScene:(r,e)=>`patter/flow/${q(r)}/scene/${q(e)}`},U=class{constructor(e){this.journal=e}journal},ne=class r{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let n=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=n;let i=s[n]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let l=new Map;for(let p of e.cast??[])p.displayName&&l.set(p.name,p.displayName);let d=new Map;for(let p of e.qualifiers??[])p?.gameId&&d.set(p.gameId,p.name);let h=new Map;for(let p of e.cast??[])for(let[g,S]of Object.entries(p.qualifierLabels??{}))typeof S=="string"&&h.set(`${p.name}\\0${g}`,S);let f=e.speakerPattern??je;this.defaultSeed=(t.seed??2654435769)>>>0;let Se=new Map,we=new Map,ve=new Map;for(let[p,g]of Object.entries(e.scenes)){this.sceneGameIdToId.set(y(g),p);let S=new Map;for(let m of g.blocks)we.set(m.id,{sceneId:p}),ve.set(m.id,m),S.set(y(m),m.id),D(m.children,K=>Se.set(K.id,K));this.blockGameIdToId.set(p,S)}let be=e.properties??[],re=be.filter(p=>p.shared??!0).map(ze),dt=be.filter(p=>!(p.shared??!0)).map(ze),pt=new Set(re.map(p=>p.name.toLowerCase())),x=t.registry??new z,xe=!t.registry||t[We]===!0,ke=new k(re,{pathPrefix:"@patter."}),se=[],W=[];try{x.mountOwned("patter",ke,{owner:_}),W.push("patter");let p=new Set;for(let[g,S]of Object.entries(Ye(t))){let m=e.scopeRegistry?.scopes.find(ut=>ut.token===g),K=(m?.declarations??[]).map(tt);x.defineForeign(g,S,K,{writable:m?.writable??!0,owner:_}),W.push(g),p.add(g)}for(let g of xe?e.scopeRegistry?.scopes??[]:[]){if(p.has(g.token)||x.has(g.token))continue;let S=(g.declarations??[]).map(m=>jt(m,g.writable));x.defineOwned(g.token,S,{owner:_}),W.push(g.token),se.push(g.token)}}catch(p){for(let g of W)x.remove(g,{keep:!0});throw p}let Ee=new Map;for(let[p,g]of Object.entries(e.scenes)){let S=new Set((g.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));Ee.set(p,S)}this.host={logEnabled:t.log??!1,tracing:t.log??!1,emitEngine:(p,g,S)=>this.emitEngine(p,g,S),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,qualifierDisplay:d,speakerLabels:h,speakerPattern:f,nodeIndex:Se,blockIndex:we,blockById:ve,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:Nt(e),padIndex:Bt(e),registry:x,ownsRegistry:xe,patterBag:ke,hostScopes:se,patterSharedDecls:re,patterLocalDecls:dt,patterSharedNames:pt,sceneSharedNames:Ee,sharedVisits:new Map,qualityLadders:new Map,sharedSelectors:new Map,stageBags:new Map,journal:null,customRng:t.rng,onDryChoice:t.onDryChoice,onError:t.onError??(p=>console.warn(`Patterplay: ${p.kind} on ${p.node} in flow \'${p.flow}\' failed, played through: ${p.message}`)),reportRefits:()=>{let p=x.takeMisfits(g=>g==="patter"||g.startsWith("patter/")||se.includes(g));if(p.length!==0)if(t.onRefit)t.onRefit(p);else for(let g of p)console.warn(`Patterplay: the save\'s ${g.scope}.${g.name} was ${JSON.stringify(g.value)}, which no longer fits its declaration; it is back at its default`)},replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??ge).open,captionClose:(e.closedCaptions??ge).close,captionCharacter:e.closedCaptions?.character||Ve,refSplitCache:new Map,refSplitRevision:x.revision},this.host.reportRefits()}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),n=c=>(c.setLocale(this.currentLocale),c.setClosedCaptions(this.host.captionsOn),c),s=()=>new r(e,{...this.creationOptions,registry:this.host.registry,[We]:this.host.ownsRegistry}),i=new Set(["patter",...this.host.hostScopes]),o=this.host.ownsRegistry?void 0:Object.fromEntries(Object.entries(this.host.registry.save()).filter(([c])=>i.has(c)||c.startsWith("patter/")));this.release(!0),o&&this.host.registry.load(o,{keepParked:!0,refit:!0});let a=s();try{return a.loadGame(t),n(a)}catch{a.release(!0);let c=this.host.ownsRegistry?t.registry:o;c&&this.host.registry.load(c,{keepParked:!0,refit:!0});let l=s();for(let[d,h]of Object.entries(t.flows)){let f=h.cursor.currentSceneId;try{l.openFlow(d,f!==null?{scene:f}:{})}catch{}}return n(l)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){this.assertExternalScopes();let{sceneId:n,blockId:s}=this.resolveOpenAddress(t.scene,t.block),i=this.flowsById.get(e),o=this.host.journal;if(o&&i&&!i.isClosed)throw new Error(`openFlow would replace the open flow \'${e}\', which can\'t be undone while a checkpoint is open`);i?.close();let a=new O(e,this.host,t.seed??this.defaultSeed);return o&&(o.flows.add(a),o.opened.add(a),o.undo.push(()=>{a.close(),i?this.flowsById.set(e,i):this.flowsById.delete(e)})),this.flowsById.set(e,a),a.begin(n,s),a}runFlow(e,t,n){let s=this.flowsById.get(e);if(!s)return this.openFlow(e,{scene:t,block:n}).advanceToStop().played;if(!s.goto(t,n))throw new Error(`runFlow: address not found: ${t}${n===void 0?"":` / ${n}`}`);return s.advanceToStop().played}assertExternalScopes(){for(let e of this.host.bundle.externalScopes??[])if(!this.host.registry.has(e))throw new Error(`this content names @${e}, which no engine on this registry registered: give every engine the game\'s one registry`)}resolveOpenAddress(e,t){if(e!==void 0){let n=fe(this.host,e);if(n===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:n};let s=he(this.host,n,t);if(s===void 0)throw new Error(`unknown block: ${t}`);return{sceneId:n,blockId:s}}if(t!==void 0){if(!this.host.blockIndex.has(t))throw new Error(`unknown block: ${t}`);return{blockId:t}}if(Object.keys(this.host.bundle.scenes).length===0)throw new Error("no scenes in bundle");return{}}resolveSceneRef(e){if(e!=null)return fe(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:he(this.host,e,t)}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?y(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?y(t):void 0}tagsForBeat(e){return I(this.host,e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?I(this.host,t):void 0)??[]}tagsForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t);return(s!=null?I(this.host,s):void 0)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.gameData}}gameDataForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(n!=null?this.host.blockById.get(n):void 0)?.gameData}}extensionsForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.extensions}}extensionsForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(n!=null?this.host.blockById.get(n):void 0)?.extensions}}getCast(){let e=[];for(let t of this.host.bundle.cast??[])t?.name&&e.push(t.name);return e}castForScene(e){let t=this.resolveSceneRef(e),n=t!=null?this.host.bundle.scenes[t]:void 0;if(!n)return[];let s=new Set;for(let i of n.blocks)Ke(i.children,s);return[...s]}castForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t),i=s!=null?this.host.blockById.get(s):void 0;if(!i)return[];let o=new Set;return Ke(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...y(e)?{gameId:y(e)}:{},name:e.name,...Je(e.gameData),...b(e.extensions),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...y(t)?{gameId:y(t)}:{},name:t.name,...Je(t.gameData),...b(t.extensions),...this.tagsField(t.id),children:t.children.map(n=>this.outlineNode(n))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let n of t.blocks)D(n.children,s=>{if(s.type==="snippet")for(let i of s.beats??[])e.push({sceneId:t.id,blockId:n.id,snippetId:s.id,beat:this.beatInfo(i)})});return e}outlineNode(e){return e.type==="group"?{type:"group",id:e.id,...this.tagsField(e.id),...e.selector?{selector:e.selector}:{},...e.prompt?{prompt:this.beatInfo(e.prompt)}:{},children:e.children.map(t=>this.outlineNode(t))}:{type:"snippet",id:e.id,...this.tagsField(e.id),beats:(e.beats??[]).map(t=>this.beatInfo(t)),...e.jump?{jumpTo:e.jump.to,...e.jump.mode?{jumpMode:e.jump.mode}:{}}:{}}}beatInfo(e){let t=I(this.host,e.id),n={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){n.character=e.character;let i=this.host.defaultStrings[Z(e.character)]??this.host.castDisplay.get(e.character);i!==void 0&&(n.characterName=i)}if(e.direction!==void 0&&(n.direction=e.direction),e.qualifier!==void 0){n.qualifier=e.qualifier;let i=this.host.defaultStrings[ee(e.qualifier)]??this.host.qualifierDisplay.get(e.qualifier);i!==void 0&&(n.qualifierName=i)}let s=$(this.host,e.character,e.qualifier,!0);s!==void 0&&(n.speakerLabel=s)}if(e.kind==="line"||e.kind==="text"){let s=this.host.padIndex.get(e.id);n.padAfter=s?.resolved??E,s?.own!==void 0&&(n.ownPadAfter=s.own);let i=this.host.defaultStrings[e.id];i!==void 0&&(n.text=i)}return e.gameData&&Object.keys(e.gameData).length&&(n.gameData=e.gameData),e.extensions&&Object.keys(e.extensions).length&&(n.extensions=e.extensions),t&&t.length&&(n.tags=t),n}tagsField(e){let t=I(this.host,e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}listBags(){let e=[{bag:this.host.patterBag}];for(let[t,n]of this.host.stageBags)e.push({bag:n,pathPrefix:`@scene:${t}.`});return e}checkpoint(){if(this.host.journal)throw new Error("a checkpoint is already open: roll it back or commit it first");let e={undo:[],flows:new Set,opened:new Set,selectors:new Set,flowSelectors:new Map};return this.host.journal=e,new U(e)}rollback(e){this.endCheckpoint(e);let t=e.journal.undo;for(let n=t.length-1;n>=0;n--)t[n]();r.forget(e.journal)}commit(e){this.endCheckpoint(e),r.forget(e.journal)}get inCheckpoint(){return this.host.journal!==null}endCheckpoint(e){if(this.host.journal!==e.journal)throw new Error("that checkpoint is not the open one");this.host.journal=null}static forget(e){e.undo=[],e.flows.clear(),e.opened.clear(),e.selectors.clear(),e.flowSelectors.clear()}refuseInCheckpoint(e){if(this.host.journal)throw new Error(`${e} can\'t be undone, so it isn\'t allowed while a checkpoint is open`)}captureFlow(e){let t=this.flowsById.get(e);if(!t)throw new Error(`no open flow \'${e}\' to capture`);return{version:M,...t.snapshot(),...Vt(e,this.host.registry.save())}}restoreFlow(e,t){this.refuseInCheckpoint("restoreFlow");let n=t.version;if(n!==2&&n!==M)throw new Error(`unsupported flow state version: ${String(n)}`);let s=t.cursor;if(typeof s!="object"||s===null)throw new Error("malformed flow state: no cursor");if(this.flowsById.has(e))throw new Error(`a flow \'${e}\' is already open; close it before restoring another under that id`);this.assertExternalScopes();let i=w.flow(e);this.host.registry.discardParked(i),this.host.registry.load(rt(e,t),{keepParked:!0,refit:!0});let o=new O(e,this.host,this.defaultSeed);try{o.restore(t)}catch(a){throw o.close(),this.host.registry.discardParked(i),a}return this.flowsById.set(e,o),this.host.reportRefits(),o}closeFlow(e){this.refuseInCheckpoint("closeFlow"),this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){this.refuseInCheckpoint("reset");for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.patterBag.reseed(this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear();for(let e of this.host.stageBags.keys())this.host.registry.remove(w.stage(e));this.host.stageBags.clear(),this.host.registry.discardParked("patter/")}release(e){for(let n of this.flowsById.values())n.releaseBags(e),n.close();this.flowsById.clear();let t=this.host.registry;for(let n of this.host.stageBags.keys())t.remove(w.stage(n),{keep:e});this.host.stageBags.clear();for(let n of["patter",...this.host.hostScopes])t.has(n)&&t.remove(n,{keep:e});for(let n of Object.keys(Ye(this.creationOptions)))t.has(n)&&t.remove(n)}getProperty(e){let{scope:t,name:n}=this.splitShared(e);return this.host.registry.get(t,n)}setProperty(e,t){let{scope:n,name:s}=this.splitShared(e),i=this.host.journal?this.host.registry.get(n,s):void 0;this.host.registry.set(n,s,t,{host:!0}),this.host.journal&&i!==void 0&&this.host.journal.undo.push(()=>this.host.registry.set(n,s,i,{host:!0}))}stageIndex(e,t){return P(this.ladderOf(e),t)}compareStages(e,t,n){return N(this.ladderOf(e),t,n)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitShared(e),s=Lt(this.host,t,n);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}log(){return this.engineLog}clearLog(){this.engineLog.length=0}onTrace(e){return this.engineTraceHandlers.add(e),this.host.tracing=!0,()=>{this.engineTraceHandlers.delete(e),this.host.tracing=this.host.logEnabled||this.engineTraceHandlers.size>0}}emitEngine(e,t,n){for(let s of this.engineTraceHandlers)s(e,t);this.host.logEnabled&&this.engineLog.push({...t,flow:e,seq:this.engineSeq++,...n?{scene:n}:{}})}getVisitCounts(){return Object.fromEntries(this.host.sharedVisits)}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:A(e),writable:e.writable??!0}))}splitShared(e){let t=nt(this.host,e);if(t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return{patter:this.host.patterBag.save()}}load(e){e.patter&&this.host.patterBag.load(e.patter)}saveGame(){let e={};for(let[t,n]of this.flowsById)e[t]=n.snapshot();return{version:M,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:Ze(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==M)throw new Error(`unsupported save version: ${String(t)}`);let n=e.flows;if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("malformed save: no flows");this.assertExternalScopes();let s=this.host.registry;for(let[o,a]of this.flowsById)a.releaseBags(o in e.flows),a.close();this.flowsById.clear();for(let o of this.host.stageBags.keys())s.remove(w.stage(o),{keep:!0});this.host.stageBags.clear();let i=e.version===2?Mt(e):e.registry;i&&(this.host.ownsRegistry?s.load(i,{refit:!0}):s.load(i,{keepParked:!0,refit:!0})),this.host.sharedVisits.clear();for(let[o,a]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(o,a);this.host.sharedSelectors.clear();for(let[o,a]of et(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new O(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}this.host.reportRefits()}},O=class{id;host;local;registered=new Set;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPrompt=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.patterBag.get(e):this.local.get(e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.registry.set("patter",e,t):this.local.set(e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;if(t!==null)return this.sceneBagFor(t,e)?.get(e)},set:(e,t)=>{let n=this.currentSceneId;if(n===null)return;let s=this.sceneBagFor(n,e);s&&s.set(e,t)}};evalCtx;ctxRevision=-1;registryQualities;flowLog=[];flowSeq=0;constructor(e,t,n){this.id=e,this.host=t,this.rngState=L(n),this.local=this.newLocal(),this.evalCtx={scopes:{},host:{nextRandom:this.rng,visits:s=>this.visitCounts.get(s)??0,patterVisits:s=>this.host.sharedVisits.get(s)??0},qualities:(s,i)=>this.stagesFor(s,i)}}context(){let e=this.host.registry;if(e.revision!==this.ctxRevision){let t=e.toEvalContext(),n=this.evalCtx.scopes;for(let s of Object.keys(n))delete n[s];Object.assign(n,t.scopes),n.patter=this.patterResolver,n.scene=this.sceneResolver,this.registryQualities=t.qualities,this.ctxRevision=e.revision}return this.evalCtx}stagesFor(e,t){let n=t.toLowerCase();if(e==="patter")return H(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(n);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:H(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(n)}return this.registryQualities?.(e,t)??H(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}begin(e,t){if(this.host.journal&&!this.host.journal.opened.has(this))throw new Error("a flow can\'t be started or reset while a checkpoint is open");if(this.releaseBags(!1),this.host.registry.discardParked(w.flow(this.id)),this.mountLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.clearPending(),this.started=!0,t){let n=this.host.blockIndex.get(t);if(!n)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(n.sceneId),this.stack=[{sceneId:n.sceneId,containerId:t,index:0}],this.enter(t)}else{let n=e??Object.keys(this.host.bundle.scenes)[0],s=n?this.host.bundle.scenes[n]:void 0;if(!s)throw new Error(n?`unknown scene: ${n}`:"no scenes in bundle");this.enterSceneSetup(n);let i=s.blocks[0];i&&(this.stack=[{sceneId:n,containerId:i.id,index:0}],this.enter(i.id))}this.settle()}reset(e,t){this.begin(e,t)}start(e,t){this.reset(e,t)}goto(e,t){if(this.closed)return!1;if(this.touch(),e==="END")return this.started=!0,this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let n=fe(this.host,e);if(n===void 0)return!1;let s;return t!==void 0&&(s=he(this.host,n,t),s===void 0)?!1:this.started?(this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??n,"jump"),this.settle(),!0):(this.begin(n,s),!0)}close(){this.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.clearPending()}clearPending(){this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started:n,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:d}=this,h=this.stack.map(f=>({...f}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:n,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:d,stack:h})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.touch(),this.pendingPrompt){let e=this.pendingPrompt;return this.pendingPrompt=null,this.pendingPromptOwnerId=null,e.shown?this.promptResult(e.beat,e.shown):this.beatResult(e.beat)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit,this.activeSnippet.id);let i=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(i);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let n=this.childrenOf(t.containerId);if(!n){this.stack.pop();continue}let s=t.index;for(;t.index<n.length&&!this.eligible(n[t.index]);)t.index++;if(this.host.tracing&&t.index!==s&&this.emit({type:"select",group:t.containerId,selector:"run",children:n.slice(s,t.index+1).map((i,o)=>({id:i.id,eligible:s+o===t.index})),picked:n[t.index]?.id??null}),t.index>=n.length){this.stack.pop();continue}this.enterChild(n[t.index++])}}listBags(){let e=[{bag:this.local,pathPrefix:`${this.id}/@patter.`}];for(let[t,n]of this.sceneBags)e.push({bag:n,pathPrefix:`${this.id}/@scene:${t}.`});return e}log(){return this.flowLog}clearLog(){this.flowLog.length=0}emit(e){let t=this.currentSceneId??void 0;this.host.emitEngine(this.id,e,t),this.host.logEnabled&&this.flowLog.push({...e,seq:this.flowSeq++,...t?{scene:t}:{}})}getVisitCounts(){return Object.fromEntries(this.visitCounts)}stepFor(e){let t=At(this.host.bundle).get(e);return t?this.beatResult(t):void 0}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let n=t.options.find(o=>o.id===e);if(!n)throw new Error(`unknown choice option: ${e}`);if(!n.eligible)throw new Error(`choice option is not eligible: ${e}`);this.touch();let s=t.byId.get(e);this.host.tracing&&this.emit({type:"chose",group:t.groupId,option:e}),this.pendingChoice=null;let i=this.host.replayPromptOnChoose?this.authoredPromptOf(s):void 0;this.pendingPrompt=i&&n.prompt?{beat:i,shown:{...n.prompt}}:null,this.pendingPromptOwnerId=this.pendingPrompt?s.id:null,this.enterChild(s)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:n}=this.splitRef(e);return t==="patter"?this.patterResolver.get(n):t==="scene"?this.sceneResolver.get(n):this.host.registry.get(t,n)}setProperty(e,t){this.writeProperty(e,t,!0)}stageIndex(e,t){return P(this.ladderOf(e),t)}compareStages(e,t,n){return N(this.ladderOf(e),t,n)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitRef(e);this.context();let s=this.stagesFor(t,n);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}writeProperty(e,t,n){let{scope:s,name:i}=this.splitRef(e),o=this.host.journal;if(s==="patter"){if(o){let a=this.host.patterSharedNames.has(i),c=a?this.host.patterBag.get(i):this.local.get(i),l=this.local;c!==void 0&&o.undo.push(a?()=>this.host.registry.set("patter",i,c):()=>l.set(i,c))}this.patterResolver.set(i,t)}else if(s==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);if(o){let a=this.sceneBagFor(this.currentSceneId,i),c=a?.get(i);a&&c!==void 0&&o.undo.push(()=>a.set(i,c))}this.sceneResolver.set(i,t)}else{let a=o?this.host.registry.get(s,i):void 0;this.host.registry.set(s,i,t,n?{host:!0}:void 0),o&&a!==void 0&&o.undo.push(()=>this.host.registry.set(s,i,a,{host:!0}))}}snapshot(){return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(({extensions:e,...t})=>({...t}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:Tt(this.pendingPrompt.shown)}:{},selectors:Ze(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");this.restoreCursor(e),this.releaseBags(!0),this.mountLocal();for(let t of new Set([this.currentSceneId,...this.stack.map(n=>n.sceneId)]))t!==null&&this.host.bundle.scenes[t]&&this.ensureSceneBags(t)}restoreCursor(e){this.rngState=L(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(n=>{let{nextId:s,...i}=n;if(s!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===s)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.activeSnippet=null,t.activeSnippetId!==null){let n=this.host.nodeIndex.get(t.activeSnippetId);n&&n.type==="snippet"&&(this.activeSnippet=n)}if(this.selectors=et(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let n=new Map,s=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);if(!o)continue;n.set(i.id,o);let{prompt:a,extensions:c,...l}=i,d=a?.kind==="line"&&a.speakerLabel===void 0?$(this.host,a.character,a.qualifier):void 0;s.push({...l,...a?{prompt:{...a,padAfter:a.padAfter??this.promptPadOf(o),...d!==void 0?{speakerLabel:d}:{}}}:{},...b(o.extensions)})}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:n})}if(this.pendingPrompt=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let n=this.host.nodeIndex.get(this.pendingPromptOwnerId),s=n?this.authoredPromptOf(n):void 0,i=t.pendingPrompt,o=i?.kind==="line"&&i.speakerLabel===void 0?$(this.host,i.character,i.qualifier):void 0;s?this.pendingPrompt={beat:s,...i?{shown:{...i,padAfter:i.padAfter??this.padOf(s.id),...o!==void 0?{speakerLabel:o}:{}}}:{}}:this.pendingPromptOwnerId=null}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry,t.id)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let n=this.selectChild(e);n&&this.enterChild(n)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let n=this.host.nodeIndex.get(e);if(n&&n.type==="group")return n.children}beginSnippet(e){this.runEffects(e.onEnter,e.id),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],n=new Map,s=[];for(let o of e.children){if(o.fallback===!0){s.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),c=o.secretUntilEligible===!0;!a&&c||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData,...b(o.extensions)}),n.set(o.id,o))}if(t.some(o=>o.eligible)){this.host.tracing&&this.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),this.pendingChoice={groupId:e.id,options:t,byId:n};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.tracing&&this.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.host.tracing&&this.emit({type:"jump",to:e,mode:t}),e==="END"){this.flowEnded=!0,this.stack=[];return}let n,s,i=this.host.bundle.scenes[e];if(i){this.enterSceneSetup(e);let a=i.blocks[0];if(!a){t==="jump"&&(this.stack=[]);return}n=e,s=a.id}else{let a=this.host.blockIndex.get(e);if(!a)throw new Error(`jump target not found: ${e}`);a.sceneId!==this.currentSceneId&&this.enterSceneSetup(a.sceneId),n=a.sceneId,s=e}this.enter(s);let o={sceneId:n,containerId:s,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.map(c=>({id:c.id,eligible:this.eligible(c)})),n=e.children.filter((c,l)=>t[l].eligible),s=e.options?.order??"sequential",i=e.options?.exhaust??"once",o=c=>(this.host.tracing&&this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(n.length===0)return o(null);let a=this.selectorState(e);switch(e.selector){case"branch":return o(n[0]);case"sequence":return o(s==="shuffle"?this.pickShuffle(n,i,a):s==="specificity"?this.pickSpecificity(n,i,a):this.pickSequential(n,i,a));default:return null}}pickSequential(e,t,n){let s=e.length,i=n.seq??0;return n.seq=i+1,t==="repeat"?e[i%s]:i<s?e[i]:t==="stick"?e[s-1]:null}pickShuffle(e,t,n){let s=e.length,i=t==="stick",o=()=>(i?e.slice(0,s-1):e).map(f=>f.id),a=new Set(e.map(f=>f.id));if(n.bag===void 0&&(n.bag=o()),!n.bag.some(f=>a.has(f))){if(t==="once")return null;if(i){let f=e[s-1];return n.last=f.id,f}if(n.bag=o(),n.bag.length===0)return null}let c=n.bag.filter(f=>a.has(f)),l=n.last!==void 0&&c.length>1?c.indexOf(n.last):-1,d=Math.floor(this.rng()*(l>=0?c.length-1:c.length));l>=0&&d>=l&&d++;let h=c[d];return n.bag.splice(n.bag.indexOf(h),1),n.last=h,e.find(f=>f.id===h)}pickSpecificity(e,t,n){let s=e;if(t!=="repeat"){n.bag===void 0&&(n.bag=e.map(d=>d.id));let l=new Set(n.bag);if(s=e.filter(d=>l.has(d.id)),s.length===0)return t==="stick"&&n.last!==void 0?e.find(d=>d.id===n.last)??null:null}let i=-1,a=s.map(l=>{let d=this.specScore(l);return d>i&&(i=d),{c:l,s:d}}).filter(l=>l.s===i).map(l=>l.c),c;if(a.length===1)c=a[0];else{let l=n.last!==void 0?a.findIndex(h=>h.id===n.last):-1,d=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&d>=l&&d++,c=a[d]}return t!=="repeat"&&(n.bag=n.bag.filter(l=>l!==c.id)),n.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0,e):0}matchedSpec(e,t,n){return Ce(e,i=>{try{return Xe(J(i,this.context(),ue))}catch(o){return this.reportError("best-match",n.id,n.condition,o),!1}},{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,n=this.host.journal,s;if(n&&(s=e.shared?n.selectors:n.flowSelectors.get(this),s||(s=new Set,n.flowSelectors.set(this,s))),n&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;n.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e,t){for(let n of e??[]){let s;try{s=this.evalExpr(n.value);let i=this.host.tracing?this.getProperty(n.target):void 0;this.writeProperty(n.target,s,!1),this.host.tracing&&this.emit({type:"write",target:n.target,value:s,...i!==void 0?{prev:i}:{}})}catch(i){this.reportError("effect",t,n.value,i)}}}eligible(e){if(!e.condition)return!0;try{return Xe(this.evalExpr(e.condition))}catch(t){return this.reportError("condition",e.id,e.condition,t),!1}}reportError(e,t,n,s){let i=s instanceof Error?s.message:String(s),o=n?.src;this.host.onError({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i}),this.host.tracing&&this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return J(this.conditionAst(e),this.context(),ue)}conditionAst(e){let t=$e.get(e);return t||(t=R(e.ast),$e.set(e,t)),t}enter(e){let t=this.visitCounts,n=t.get(e);t.set(e,(n??0)+1);let s=this.host.sharedVisits,i=s.get(e);s.set(e,(i??0)+1),this.host.journal&&this.host.journal.undo.push(()=>{n===void 0?t.delete(e):t.set(e,n),i===void 0?s.delete(e):s.set(e,i)})}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=ae(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=I(this.host,e.id),n=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...b(e.extensions),...n};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...n};case"line":{let s=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?s:this.interpolate(s)),c=i&&a.length===0;return{type:"line",id:e.id,text:a,character:c?void 0:e.character,characterName:c?void 0:this.resolveCharacterName(e.character),direction:c?void 0:e.direction,qualifier:c?void 0:e.qualifier,qualifierName:c?void 0:this.resolveQualifierName(e.qualifier),speakerLabel:c?void 0:$(this.host,e.character,e.qualifier),padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...n}}}}interpolate(e){return Fe(e,t=>this.getProperty(t))}stripCaptions(e){return Ae(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let n=this.interpolate(this.resolveString(t.id)),s=this.padOf(t.id);return t.kind==="line"?{kind:"line",text:this.captionLine(n),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction,qualifier:t.qualifier,qualifierName:this.resolveQualifierName(t.qualifier),speakerLabel:$(this.host,t.character,t.qualifier),padAfter:s}:{kind:"text",text:n,padAfter:s}}authoredPromptOf(e){return e.type==="group"?e.prompt:void 0}promptResult(e,t){let n=I(this.host,e.id),s=n&&n.length?{tags:n}:{};return t.kind==="text"?{type:"text",id:e.id,text:t.text,padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...s}:{type:"line",id:e.id,text:t.text,character:t.character,characterName:t.characterName,direction:t.direction,qualifier:t.qualifier,qualifierName:t.qualifierName,speakerLabel:t.speakerLabel,padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...s}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(n=>n.kind==="line"||n.kind==="text")}firstTextSnippetIn(e){let t;return D(e,n=>{!t&&n.type==="snippet"&&(n.beats??[]).some(s=>s.kind==="line"||s.kind==="text")&&(t=n)}),t}resolveString(e){if(this.host.emitIds)return e;let t=this.host.strings[e];if(t!==void 0)return t;let n=this.host.defaultStrings[e];return n!==void 0?`<Untranslated: ${e}> ${n}`:e}resolveCharacterName(e){if(e===void 0||this.host.emitIds)return;let t=Z(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}promptPadOf(e){let t=this.promptBeatOf(e);return t?this.padOf(t.id):E}padOf(e){return this.host.padIndex.get(e)?.resolved??E}resolveQualifierName(e){if(e===void 0||this.host.emitIds)return;let t=ee(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.qualifierDisplay.get(e)}splitRef(e){return nt(this.host,e)}newLocal(){return new k(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=w.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:_}),this.registered.add(e),this.host.reportRefits()}releaseBags(e){for(let t of this.registered)this.host.registry.remove(t,{keep:e});this.registered.clear(),this.sceneBags.clear()}sceneBagFor(e,t){if(this.host.bundle.scenes[e])return this.ensureSceneBags(e),this.host.sceneSharedNames.get(e)?.has(t.toLowerCase())?this.host.stageBags.get(e):this.sceneBags.get(e)}ensureSceneBags(e){let t=this.host.sceneSharedNames.get(e)??new Set,n=this.host.bundle.scenes[e]?.sceneProps??[],s=this.host.journal,i=!this.sceneBags.has(e)||!this.host.stageBags.has(e);if(!this.sceneBags.has(e)){let o=new k(n.filter(l=>!t.has(l.name.toLowerCase())),{pathPrefix:"@scene."}),a=w.flowScene(this.id,e),c=Qe(this.host.registry,a,o,s!==null);this.registered.add(a),this.sceneBags.set(e,o),s&&s.undo.push(()=>{this.sceneBags.get(e)===o&&(this.host.registry.remove(a,c?{keep:!0}:void 0),this.registered.delete(a),this.sceneBags.delete(e))})}if(!this.host.stageBags.has(e)){let o=new k(n.filter(c=>t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),a=Qe(this.host.registry,w.stage(e),o,s!==null);this.host.stageBags.set(e,o),s&&s.undo.push(()=>{this.host.stageBags.get(e)===o&&(this.host.registry.remove(w.stage(e),a?{keep:!0}:void 0),this.host.stageBags.delete(e))})}i&&this.host.reportRefits()}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;this.ensureSceneBags(e.id);for(let n of e.sceneProps??[]){if(!n.temporary)continue;let s=n.name.toLowerCase(),i=t.has(s)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);if(!i)continue;let o=this.host.journal?i.get(s):void 0;o!==void 0&&this.host.journal.undo.push(()=>i.set(s,o)),i.set(s,A(n))}}};function Ke(r,e){D(r,t=>{if(t.type==="group"){t.prompt?.kind==="line"&&t.prompt.character&&e.add(t.prompt.character);return}for(let n of t.beats??[])n.kind==="line"&&n.character&&e.add(n.character)})}function $(r,e,t,n=!1){if(!e||r.emitIds)return;let s=c=>(n?void 0:r.strings[c])??r.defaultStrings[c],i=s(Z(e))??r.castDisplay.get(e)??e;if(t===void 0)return i;let o=r.speakerLabels.get(`${e}\\0${t}`);if(o!==void 0)return s(Te(e,t))??o;let a=s(ee(t))??r.qualifierDisplay.get(t)??t;return Me(s(Le)??r.speakerPattern,i,a)}function Je(r){return r&&Object.keys(r).length?{gameData:r}:{}}function b(r){return r&&Object.keys(r).length?{extensions:r}:{}}function Tt(r){let e={kind:r.kind,text:r.text};return r.character!==void 0&&(e.character=r.character),r.characterName!==void 0&&(e.characterName=r.characterName),r.direction!==void 0&&(e.direction=r.direction),r.qualifier!==void 0&&(e.qualifier=r.qualifier),r.qualifierName!==void 0&&(e.qualifierName=r.qualifierName),r.speakerLabel!==void 0&&(e.speakerLabel=r.speakerLabel),typeof r.padAfter=="number"&&(e.padAfter=r.padAfter),e}function Ze(r){let e={};for(let[t,n]of r){let s={};n.seq!==void 0&&(s.seq=n.seq),n.bag&&(s.bag=[...n.bag]),n.last!==void 0&&(s.last=n.last),e[t]=s}return e}function et(r){let e=new Map;for(let[t,n]of Object.entries(r??{})){let s={};n.seq!==void 0&&(s.seq=n.seq),n.bag&&(s.bag=[...n.bag]),n.last!==void 0&&(s.last=n.last),e.set(t,s)}return e}function Qe(r,e,t,n){let s=n?JSON.stringify(t.save()):"";return r.mountOwned(e,t,{owner:_}),n&&JSON.stringify(t.save())!==s}function ze(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default}}function Ye(r){return{...r.world?{world:r.world}:{},...r.hostScopes}}function H(r,e,t){let n=r.qualityLadders.get(e);if(!n){n=new Map;for(let s of t()??[]){let i=s.name.toLowerCase();s.type==="quality"&&s.stages&&!n.has(i)&&n.set(i,s.stages)}r.qualityLadders.set(e,n)}return n}function Lt(r,e,t){let n=t.toLowerCase();return e==="patter"?H(r,"patter",()=>[...r.patterSharedDecls,...r.patterLocalDecls]).get(n):r.registry.toEvalContext().qualities?.(e,t)??H(r,`host/${e}`,()=>r.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}function tt(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default,writable:r.writable}}function jt(r,e){let t=tt(r),n=r.writable??e;return n===void 0?t:{...t,writable:n}}function fe(r,e){return r.sceneGameIdToId.get(e)??(r.bundle.scenes[e]?e:void 0)}function he(r,e,t){return r.blockGameIdToId.get(e)?.get(t)??(r.blockIndex.get(t)?.sceneId===e?t:void 0)}function nt(r,e){r.refSplitRevision!==r.registry.revision&&(r.refSplitCache.clear(),r.refSplitRevision=r.registry.revision);let t=r.refSplitCache.get(e);return t||(t=Be(e,n=>n==="scene"||r.registry.has(n)||(r.bundle.externalScopes?.includes(n)??!1)),r.refSplitCache.set(e,t)),t}function Mt(r){let e={};r.shared?.patter&&(e.patter=r.shared.patter);for(let[t,n]of Object.entries(r.stageBags??{}))e[w.stage(t)]=n;for(let[t,n]of Object.entries(r.flows??{}))Object.assign(e,rt(t,n));return e}function rt(r,e){let t={};e.scopes?.patter&&(t[w.flowGlobals(r)]=e.scopes.patter);for(let[n,s]of Object.entries(e.sceneBags??{}))t[w.flowScene(r,n)]=s;return t}function Vt(r,e){let t={scopes:{},sceneBags:{}},n=w.flow(r);for(let[s,i]of Object.entries(e)){if(!s.startsWith(n))continue;let o=s.slice(n.length);o==="patter"?t.scopes.patter=i:o.startsWith("scene/")&&(t.sceneBags[Ft(o.slice(6))]=i)}return t}function Xe(r){return typeof r=="boolean"?r:typeof r=="number"?r!==0:typeof r=="string"?r!=="":r.length>0}var Gt=(r,e)=>r.shared??e;function me(r,e){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.default!==void 0?{default:r.default}:{},shared:Gt(r,e)}}function qt(r){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.values?{values:[...r.values]}:{},...r.purpose?{purpose:r.purpose}:{}}}function $t(r,e){e.blocks++;let t=[...r.children];for(;t.length;){let n=t.pop();if(n.type==="group"){e.groups++,n.prompt&&e.prompts++,t.push(...n.children);continue}e.snippets++;for(let s of n.beats??[])e.beats++,s.kind==="gameEvent"&&e.gameEvents++}}function st(r){let e={scenes:0,blocks:0,groups:0,snippets:0,beats:0,prompts:0,gameEvents:0,cast:r.cast?.length??0},t=[],n=[];for(let o of Object.values(r.scenes)){e.scenes++;let a=y(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:y(c),name:c.name}))});for(let c of o.blocks)$t(c,e);o.sceneProps?.length&&n.push({gameId:a,properties:o.sceneProps.map(c=>me(c,!1))})}let s=(r.scopeRegistry?.scopes??[]).map(o=>({token:o.token,writable:o.writable??!0,opaque:o.declarations===void 0,properties:(o.declarations??[]).map(a=>me(a,!0))})),i=Object.entries(r.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(qt)}));return{identity:{schema:r.schema,project:r.content.project,...r.content.version!==void 0?{version:r.content.version}:{},...r.content.hash!==void 0?{hash:r.content.hash}:{},...r.content.structureHash!==void 0?{structureHash:r.content.structureHash}:{},voiced:r.voiced,defaultLocale:r.locales.default,locales:[...r.locales.included],localisation:r.localisation?.mode??"embedded",sourceDebug:r.localisation?.sourceDebug??!1},addresses:t,hostScopes:s,properties:{patter:(r.properties??[]).map(o=>me(o,!0)),scene:n},gameData:i,counts:e}}var F=(r,e)=>Object.prototype.hasOwnProperty.call(r,e);function it(r,e){return r.gameDataFields?.[e]??[]}function ye(r,e,t){return e&&F(e,t)?e[t]:r.find(n=>n.name===t)?.default}function ot(r,e){let t={};for(let n of r){let s=ye(r,e,n.name);s!==void 0&&(t[n.name]=s)}for(let[n,s]of Object.entries(e??{}))F(t,n)||(t[n]=s);return t}function at(r,e,t){let n=r.extensions!==void 0&&F(r.extensions,e)?r.extensions[e]:void 0;return n!==void 0&&F(n.fields,t)?n.fields[t]??[]:[]}function ct(r,e){return{...(r.extensions!==void 0&&F(r.extensions,e)?r.extensions[e]:void 0)?.settings??{}}}function lt(r,e,t=""){let n=o=>({...o?.extensions??{}}),s=Object.values(r.scenes??{}),i=o=>(F(r.scenes??{},o)?r.scenes[o]:void 0)??s.find(a=>y(a)===o);switch(e){case"project":{let o={};for(let[a,c]of Object.entries(r.extensions??{}))c.project!==void 0&&(o[a]=c.project);return o}case"scene":return n(i(t));case"block":{let o=t.indexOf("/");if(o<0)return{};let a=i(t.slice(0,o))?.blocks??[],c=t.slice(o+1);return n(a.find(l=>l.id===c)??a.find(l=>y(l)===c))}case"snippet":case"option":case"line":case"text":case"gameEvent":{let o,a=(c,l)=>{for(let d of c??[]){if(o!==void 0)return;if(d.type==="group"){if(l&&d.id===t&&e==="option"){o=d;return}if(l&&d.prompt?.id===t&&d.prompt.kind===e){o=d.prompt;return}a(d.children??[],d.selector==="choice");continue}if(d.id===t&&e==="snippet"){o=d;return}let h=(d.beats??[]).find(f=>f.id===t);if(h!==void 0){h.kind===e&&(o=h);return}}};for(let c of s)for(let l of c.blocks??[])o===void 0&&a(l.children??[],!1);return n(o)}default:return{}}}return St(_t);})();\n';
|
|
2132
|
+
var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var oe=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var mt=Object.getOwnPropertyNames;var yt=Object.prototype.hasOwnProperty;var St=(r,e)=>{for(var t in e)oe(r,t,{get:e[t],enumerable:!0})},wt=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of mt(e))!yt.call(r,s)&&s!==t&&oe(r,s,{get:()=>e[s],enumerable:!(n=ht(e,s))||n.enumerable});return r};var vt=r=>wt(oe({},"__esModule",{value:!0}),r);var Ut={};St(Ut,{Checkpoint:()=>U,DEFAULT_PAD_AFTER:()=>E,Engine:()=>re,Flow:()=>O,buildTagIndex:()=>ne,compareStages:()=>N,describeBundle:()=>ot,effectiveGameData:()=>ct,extensionDecls:()=>lt,extensionSettings:()=>dt,extensionsOf:()=>pt,gameDataFields:()=>at,gameDataValue:()=>we,nextStage:()=>B,resolveSceneRef:()=>Z,sceneAddressIndex:()=>fe,stageIndex:()=>P});function T(r,e){return r!==void 0&&Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0}function R(r){switch(r[0]){case"b":return{kind:"bool",value:r[1]};case"n":return{kind:"number",value:r[1]};case"s":return{kind:"string",value:r[1]};case"sv":return{kind:"scopedvar",scope:r[1],name:r[2]};case"u":return{kind:"unary",op:r[1],operand:R(r[2])};case"bin":return{kind:"binary",op:r[1],left:R(r[2]),right:R(r[3])};case"call":{let e=r.slice(2).map(R);return{kind:"call",name:r[1],args:e}}case"fd":return{kind:"flagdelta",sign:r[1],name:r[2]}}}var u=class extends Error{constructor(e){super(e),this.name="EvalError"}};function J(r,e,t){let n=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),s=i=>{switch(i.kind){case"bool":return i.value;case"number":return i.value;case"string":return i.value;case"scopedvar":{let o=T(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):T(o,i.name);if(a===void 0){if(n.get(i.scope)==="throw")throw new u(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!T(t.functions,i.name)){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new u(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=ae(a,e);if(c===void 0)throw new u("advance() needs a quality reference (@scope.name of a quality property)");let l=v(s(a),c,"advance");return c[Math.min(l+1,c.length-1)]}let o=T(t.functions,i.name);if(!o)throw new u(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new u("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=s(i.operand);if(typeof a!="boolean")throw new u(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new u(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'and\' requires boolean operands, left is ${typeof h}`);if(!h)return!1;let f=s(i.right);if(typeof f!="boolean")throw new u(`\'and\' requires boolean operands, right is ${typeof f}`);return f}if(i.op==="or"){let h=s(i.left);if(typeof h!="boolean")throw new u(`\'or\' requires boolean operands, left is ${typeof h}`);if(h)return!0;let f=s(i.right);if(typeof f!="boolean")throw new u(`\'or\' requires boolean operands, right is ${typeof f}`);return f}let o=s(i.left),a=s(i.right),c=ae(i.left,e),l=ae(i.right,e),d=c??l;if(d!==void 0){if(c&&l&&!bt(c,l)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new u(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return v(o,d,">")>v(a,d,">");case">=":return v(o,d,">=")>=v(a,d,">=");case"<":return v(o,d,"<")<v(a,d,"<");case"<=":return v(o,d,"<=")<=v(a,d,"<=");case"+":case"-":case"*":case"/":throw new u(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return Ce(o,a);case"!=":return!Ce(o,a);case">":return D(o,a,">"),o>a;case">=":return D(o,a,">="),o>=a;case"<":return D(o,a,"<"),o<a;case"<=":return D(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new u(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return D(o,a,"-"),o-a;case"*":return D(o,a,"*"),o*a;case"/":if(D(o,a,"/"),a===0)throw new u("division by zero");return o/a}}}};return s(r)}function Ce(r,e){if(Array.isArray(r)||Array.isArray(e)){if(!Array.isArray(r)||!Array.isArray(e)||r.length!==e.length)return!1;let t=[...r].sort(),n=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==n[s])return!1;return!0}return r===e}function D(r,e,t){if(typeof r!="number"||typeof e!="number")throw new u(`\'${t}\' requires numeric operands, got ${typeof r} and ${typeof e}`)}function ae(r,e){if(!(r.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(r.scope,r.name)}function v(r,e,t){if(typeof r!="string")throw new u(`\'${t}\' on a quality compares stages, got ${typeof r}`);let n=e.indexOf(r);if(n<0)throw new u(`"${r}" is not a stage of this quality (stages: ${e.join(", ")})`);return n}var bt=(r,e)=>r.length===e.length&&r.every((t,n)=>t===e[n]);function L(r){if(Number.isNaN(r)||!Number.isFinite(r))return 0;let e=Math.trunc(r)%4294967296;return e<0?e+4294967296:e}function ce(r){let e=L(r);return{next(){e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296},state(){return e}}}var de=r=>Array.isArray(r)?r:r.stages??[],xt=r=>Array.isArray(r)?"flags":r==null?"nothing":typeof r;function P(r,e){return typeof e=="string"?de(r).indexOf(e):-1}function le(r,e){if(typeof e!="string")throw new u(`a stage is a name, got ${xt(e)}`);let t=r.indexOf(e);if(t<0)throw new u(`"${e}" is not a stage of this quality (stages: ${r.join(", ")})`);return t}function N(r,e,t){let n=de(r),s=le(n,e)-le(n,t);return s<0?-1:s>0?1:0}function B(r,e){let t=de(r);return t[Math.min(le(t,e)+1,t.length-1)]}function pe(r,e,t){switch(r.type){case"boolean":return typeof e=="boolean";case"number":return typeof e=="number";case"string":case"text":case"multiline":return typeof e=="string";case"enum":return typeof e!="string"?!1:e===""&&t?.noneChosen?!0:!Array.isArray(r.values)||r.values.includes(e);case"flags":return Array.isArray(e)&&e.every(n=>typeof n=="string"&&(!Array.isArray(r.values)||r.values.includes(n)));case"quality":return typeof e=="string"&&(!Array.isArray(r.stages)||r.stages.includes(e));default:return!0}}var kt={name:"check_flags",count:r=>Math.max(1,r.args.length-1)},Et=[kt];function Ie(r,e,t){let n=t?.countingCalls??Et;return Q(r,t?.want??!0,e,n)}function Q(r,e,t,n){if(r.kind==="binary"&&(r.op==="and"||r.op==="or")){let s=Q(r.left,e,t,n),i=Q(r.right,e,t,n);return r.op==="and"===e?s>0&&i>0?s+i:0:Math.max(s,i)}if(r.kind==="unary"&&r.op==="not")return Q(r.operand,!e,t,n);if(r.kind==="call"){let s=n.find(i=>i.name===r.name);if(s){let i=s.count(r),o=t(r);return e?o?i:0:o?0:1}}return t(r)===e?1:0}var k=class r{values=Object.create(null);decls=new Map;subscribers=new Set;auditors=new Set;norm;pathPrefix;constructor(e=[],t){this.norm=t?.normalise??(n=>n.toLowerCase()),this.pathPrefix=t?.pathPrefix??"",this.seed(e)}seed(e){for(let t of e){let n=this.norm(t.name);this.decls.set(n,t),this.values[n]=structuredClone(t.default??A(t))}}get(e){return this.values[this.norm(e)]}normalise(e){return this.norm(e)}writable(e){return this.decls.get(this.norm(e))?.writable!==!1}set(e,t,n){let s=this.norm(e);if(!n?.host&&!this.writable(s))throw new Error(`\'${e}\' is read-only`);let i={name:s,prev:this.values[s],next:t,silent:n?.silent??!1,reason:n?.reason};this.values[s]=t;for(let o of[...this.auditors])o(i);if(!i.silent)for(let o of[...this.subscribers])o(i);return i}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>Pe(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new r([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e,t){let n=new Map;for(let[i,o]of Object.entries(e)){let a=this.norm(i);this.values[a]=structuredClone(o),n.set(a,o)}let s=[];if(!t?.refit)return s;for(let[i,o]of this.decls){if(!n.has(i))continue;let a=n.get(i);pe(o,a)||(this.values[i]=structuredClone(A(o)),s.push({name:i,value:a}))}return s}};function Pe(r,e,t,n,s=""){let i=n??r.name.toLowerCase();return{name:i,path:s+i,type:r.type,value:e,default:r.default??A(r),...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:t??r.writable??!0}}var Rt=r=>r.toLowerCase(),ue=1,z=class{scopes=new Map;parked=new Map;parkedRefit=new Set;misfits=[];rev=0;get revision(){return this.rev}defineOwned(e,t,n){let s=typeof n=="string"?{pathPrefix:n}:n??{},i=new k(t,{pathPrefix:s.pathPrefix??`${e}.`,...s.normalise?{normalise:s.normalise}:{}});return this.mountOwned(e,i,s.owner!==void 0?{owner:s.owner}:void 0)}mountOwned(e,t,n){this.assertFree(e,n?.owner),this.scopes.set(e,{kind:"owned",bag:t,...n?.owner!==void 0?{owner:n.owner}:{}}),this.rev++;let s=this.parked.get(e);return s&&(this.note(e,t.load(s,{refit:this.parkedRefit.has(e)})),this.parked.delete(e),this.parkedRefit.delete(e)),this}remove(e,t){let n=this.scopes.get(e);if(!n)throw new Error(`unknown scope \'@${e}\'`);return t?.keep&&n.kind==="owned"&&(this.parked.set(e,n.bag.save()),this.parkedRefit.delete(e)),this.scopes.delete(e),this.rev++,this}discardParked(e){if(e===void 0)this.parked.clear(),this.parkedRefit.clear();else for(let t of[...this.parked.keys()])t.startsWith(e)&&(this.parked.delete(t),this.parkedRefit.delete(t));return this}takeMisfits(e){if(!e){let n=this.misfits;return this.misfits=[],n}let t=this.misfits.filter(n=>e(n.scope));return this.misfits=this.misfits.filter(n=>!e(n.scope)),t}note(e,t){for(let n of t)this.misfits.push({scope:e,name:n.name,value:n.value})}ownedBag(e){let t=this.scopes.get(e);if(!t||t.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);return t.bag}reseedOwned(e,t){return this.ownedBag(e).reseed(t),this}defineForeign(e,t,n=[],s=!0){let i=typeof s=="boolean"?{writable:s}:s;this.assertFree(e,i.owner);let o=i.normalise??Rt,a=new Map;for(let c of n)a.set(o(c.name),c);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:a,scopeWritable:i.writable??!0,norm:o,...i.owner!==void 0?{owner:i.owner}:{}}),this.rev++,this}has(e){return this.scopes.has(e)}get(e,t){let n=this.scopes.get(e);if(n)return n.kind==="owned"?n.bag.get(t):n.resolver.get(n.norm(t))}set(e,t,n,s){let i=this.scopes.get(e);if(!i)throw new Error(`unknown scope \'@${e}\'`);if(i.kind==="owned"){if(!s?.host&&!i.bag.writable(t))throw new Error(`\'@${e}.${t}\' is read-only`);i.bag.set(t,n,s?.host?{host:!0}:void 0);return}let o=i.norm(t);if(!i.resolver.set)throw new Error(`\'@${e}.${t}\' is read-only`);if(!s?.host&&!this.foreignWritable(i,o))throw new Error(`\'@${e}.${t}\' is read-only`);i.resolver.set(o,n)}foreignWritable(e,t){return e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}listProperties(){let e=[];for(let[t,n]of this.scopes){let s=n.owner!==void 0?{owner:n.owner}:{};if(n.kind==="owned")for(let i of n.bag.rows())e.push({scope:t,...s,...i});else for(let[i,o]of n.decls)e.push({scope:t,...s,...Pe(o,n.resolver.get(i),this.foreignWritable(n,i),i,`${t}.`)})}return e}toEvalContext(e,t){let n=this.view(t?.aliases),s={};for(let[o,a]of n)s[o]=a.kind==="owned"?a.bag.values:a.resolver;let i=this.qualityLadders(n);return i.size===0?{scopes:s,host:e}:{scopes:s,host:e,qualities:(o,a)=>{let c=n.get(o);return c?i.get(o)?.get(Dt(c)(a)):void 0}}}view(e){let t=new Map(this.scopes);for(let[n,s]of Object.entries(e??{})){let i=this.scopes.get(s);if(!i)throw new Error(`alias \'@${n}\' names \'${s}\', which is not registered`);t.set(n,i)}return t}qualityLadders(e){let t=new Map;for(let[n,s]of e)for(let[i,o]of Oe(s)){if(o.type!=="quality"||o.stages===void 0)continue;let a=t.get(n);a||(a=new Map,t.set(n,a)),a.set(i,o.stages)}return t}toSchema(e){let t=new Map;for(let[n,s]of this.view(e?.aliases)){let i=Oe(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(n,o)}return{properties:t}}save(){let e={};for(let[t,n]of this.scopes)n.kind==="owned"&&(e[t]=n.bag.save());for(let[t,n]of this.parked)e[t]=structuredClone(n);return e}load(e,t){t?.keepParked||(this.parked.clear(),this.parkedRefit.clear());let n=t?.refit===!0;for(let[s,i]of Object.entries(e)){let o=this.scopes.get(s);o?.kind==="owned"?this.note(s,o.bag.load(i,{refit:n})):o||(this.parked.set(s,structuredClone(i)),n?this.parkedRefit.add(s):this.parkedRefit.delete(s))}}saveFragment(){return{version:ue,scopes:this.save()}}loadFragment(e){if(e.version!==ue)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${ue})`);this.load(e.scopes)}assertFree(e,t){let n=this.scopes.get(e);if(!n)return;let s=n.owner!==void 0?` by ${n.owner}`:"",i=t!==void 0?` (wanted by ${t})`:"";throw new Error(`scope \'@${e}\' is already registered${s}${i}`)}};function Oe(r){return r.kind==="foreign"?[...r.decls.entries()]:r.bag.declarations().map(e=>[r.bag.normalise(e.name),e])}function Dt(r){return r.kind==="foreign"?r.norm:e=>r.bag.normalise(e)}function A(r){if(r.default!==void 0)return r.default;switch(r.type){case"boolean":return!1;case"number":return 0;case"string":case"text":case"multiline":return"";case"enum":return r.values?.[0]??"";case"flags":return[];case"quality":return r.stages?.[0]??"";default:return!1}}var Ne=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function j(r){return r.ctx.host??{}}var ge={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(r,e){if(r.length!==2)throw new u("random(a, b) requires exactly 2 arguments");let t=j(e).nextRandom;if(!t)throw new u("random() called without a PRNG in context");let n=e.evaluate(r[0]),s=e.evaluate(r[1]);if(typeof n!="number"||typeof s!="number")throw new u("random(a, b) arguments must be numbers");if(!Number.isInteger(n)||!Number.isInteger(s))throw new u("random(a, b) arguments must be integers");let i=Math.min(n,s),o=Math.max(n,s);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:Ae("check_flags"),eval(r,e){let t=Be(r[0],e,"check_flags");for(let n=1;n<r.length;n++){let s=r[n];if(s.kind!=="flagdelta")throw new u("check_flags() flag args must be +flagName or -flagName");if(s.sign==="+"?!t.includes(s.name):t.includes(s.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:Ae("set_flags"),eval(r,e){let t=[...Be(r[0],e,"set_flags")];for(let n=1;n<r.length;n++){let s=r[n];if(s.kind!=="flagdelta")throw new u("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let i=t.indexOf(s.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:X("visits"),eval:(r,e)=>j(e).visits?.(Y(r,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:X("seen"),eval:(r,e)=>(j(e).visits?.(Y(r,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:X("patter_visits"),eval:(r,e)=>j(e).patterVisits?.(Y(r,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:X("patter_seen"),eval:(r,e)=>(j(e).patterVisits?.(Y(r,e,"patter_seen"))??0)>0}}},vn=Ne.map(r=>r.token).filter(r=>r!=="patter"&&r!=="scene");function Fe(r,e){let t=r.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function Y(r,e,t){let n=e.evaluate(r[0]);if(typeof n!="string")throw new u(`${t}(id) requires a string node id`);return n}function X(r){return(e,t)=>{let n=e[0];n&&n.kind!=="string"&&t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(id): the argument must be a string id literal (a scene / block / node id)`})}}function Be(r,e,t){if(!r)throw new u(`${t}() requires at least one argument (the flags variable)`);let n=e.evaluate(r);if(Array.isArray(n))return n;if(n===!1||n===null||n===void 0)return[];throw new u(`${t}() first argument must be a flags property`)}function Ae(r){return(e,t)=>{if(e.length===0)return;let n=e[0];if(n.kind!=="scopedvar"){t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(): first argument must be a flags property reference (@name or @scope.name)`});return}let s=t.schema.properties.get(n.scope)?.get(n.name);if(s&&s.type!=="flags"){let i=n.scope===t.defaultScope?n.name:`${n.scope}.${n.name}`;t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${r}(): \'@${i}\' is not a flags property (got ${s.type})`});return}for(let i=1;i<e.length;i++){let o=e[i];o.kind!=="flagdelta"?t.report({path:[...t.path,"args",i],kind:"wrong-arg-type",severity:"error",message:`${r}(): argument ${i+1} must be +flagName or -flagName`}):s?.type==="flags"&&s.enumValues&&!s.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${r}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var Ct=/^@[A-Za-z0-9_.]+$/;function*It(r){let e="",t=0;for(;t<r.length;){let n=r[t];if(n==="{"&&r[t+1]==="{"){e+="{",t+=2;continue}if(n==="}"&&r[t+1]==="}"){e+="}",t+=2;continue}if(n==="{"){let s=r.indexOf("}",t+1);if(s!==-1){let i=r.slice(t,s+1),o=r.slice(t+1,s).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:Ct.test(o)?o:void 0},t=s+1;continue}e+=i,t=s+1;continue}}e+=n,t+=1}e&&(yield{kind:"text",value:e})}function Ot(r){return Array.isArray(r)?r.join(", "):typeof r=="boolean"?r?"true":"false":String(r)}function Pt(r){return r===" "||r===" "||r===`\n`||r==="\\r"||r==="\\f"||r==="\\v"}function Nt(r){let e="",t=!1;for(let n of r){if(Pt(n)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=n}return e}function Te(r,e,t){if(e.length===0||r.indexOf(e)<0)return r;let n="",s=0,i=!1;for(;s<r.length;){if(r.startsWith(e,s)){let o=r.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}n+=r.slice(s);break}n+=r[s],s+=1}return i?Nt(n):r}function Le(r,e){if(r.indexOf("{")<0)return r;let t="";for(let n of It(r)){if(n.kind==="text"){t+=n.value;continue}if(!n.ref){t+=n.raw;continue}let s=e(n.ref);t+=s===void 0?"":Ot(s)}return t}function C(r,e){for(let t of r){e(t);let n=t.children;n&&C(n,e)}}function Bt(r){return r.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function m(r){let e=r.gameId?.trim();return e||Bt(r.name)}function fe(r){let e=new Map;for(let[t,n]of Object.entries(r))e.set(m(n),t);return e}function Z(r,e,t){return(t??fe(r)).get(e)??(Object.hasOwn(r,e)?e:void 0)}var E=.6;function ee(r){return`cast:${r}`}function te(r){return`qualifier:${r}`}function je(r,e){return`speaker:${r}:${e}`}var Me="speaker:pattern",Ve="{name} ({qualifier})";function Ge(r,e,t){return r.split("{name}").map(n=>n.split("{qualifier}").join(t)).join(e)}var he={open:"[",close:"]"},qe="SFX";var M=3;function V(r){let e=new Set,t=[];for(let n of r)e.has(n)||(e.add(n),t.push(n));return t}function ne(r){let e=new Map,t=(n,s)=>{let i=V([...s,...n.tags??[]]);if(e.set(n.id,i),n.type==="group"&&n.prompt&&e.set(n.prompt.id,V([...i,...n.prompt.tags??[]])),n.type==="group")for(let o of n.children)t(o,i);else for(let o of n.beats??[])e.set(o.id,V([...i,...o.tags??[]]))};for(let n of Object.values(r.scenes)){let s=V(n.tags??[]);e.set(n.id,s);for(let i of n.blocks){let o=V([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var $e=r=>r.kind==="line"||r.kind==="text",G=r=>typeof r=="number"&&Number.isFinite(r)?r:void 0;function _e(r){let e=new Map,t=(i,o,a)=>{let c=i.kind==="gameEvent"?void 0:G(i.padAfter),l=c??o;a&&l<0&&(l=0),e.set(i.id,c!==void 0?{own:c,resolved:l}:{resolved:l})},n=(i,o)=>{let a=G(i.padAfterDefault)??o;if(i.type==="group"){i.prompt&&t(i.prompt,a,!1);for(let d of i.children)n(d,a);return}let c=i.beats??[],l=-1;c.forEach((d,h)=>{$e(d)&&(l=h)}),c.forEach((d,h)=>{$e(d)&&t(d,a,h===l)})},s=G(r.padAfterDefault)??E;for(let i of Object.values(r.scenes)){let o=G(i.padAfterDefault)??s;for(let a of i.blocks){let c=G(a.padAfterDefault)??o;for(let l of a.children)n(l,c)}}return e}var He=new WeakMap,Ue=new WeakMap;function At(r){let e=Ue.get(r);return e||(e=ne(r),Ue.set(r,e)),e}var We=new WeakMap;function Ft(r){let e=We.get(r);return e||(e=_e(r),We.set(r,e)),e}var Ke=new WeakMap;function Tt(r){let e=Ke.get(r);if(e)return e;e=new Map;for(let t of Object.values(r.scenes))for(let n of t.blocks)C(n.children,s=>{if(s.type==="group"){s.prompt&&e.set(s.prompt.id,s.prompt);return}for(let i of s.beats??[])e.set(i.id,i)});return Ke.set(r,e),e}function I(r,e){return r.tagIndex.get(e)?.slice()}var _="Patter",q=r=>r.replace(/%/g,"%25").replace(/\\//g,"%2F"),Lt=r=>r.replace(/%2F/g,"/").replace(/%25/g,"%"),Je=Symbol("ownsRegistry"),w={stage:r=>`patter/scene/${q(r)}`,flow:r=>`patter/flow/${q(r)}/`,flowGlobals:r=>`patter/flow/${q(r)}/patter`,flowScene:(r,e)=>`patter/flow/${q(r)}/scene/${q(e)}`},U=class{constructor(e){this.journal=e}journal},re=class r{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let n=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=n;let i=s[n]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let l=new Map;for(let p of e.cast??[])p.displayName&&l.set(p.name,p.displayName);let d=new Map;for(let p of e.qualifiers??[])p?.gameId&&d.set(p.gameId,p.name);let h=new Map;for(let p of e.cast??[])for(let[g,S]of Object.entries(p.qualifierLabels??{}))typeof S=="string"&&h.set(`${p.name}\\0${g}`,S);let f=e.speakerPattern??Ve;this.defaultSeed=(t.seed??2654435769)>>>0;let ve=new Map,be=new Map,xe=new Map;for(let[p,g]of Object.entries(e.scenes)){this.sceneGameIdToId.set(m(g),p);let S=new Map;for(let y of g.blocks)be.set(y.id,{sceneId:p}),xe.set(y.id,y),S.set(m(y),y.id),C(y.children,K=>ve.set(K.id,K));this.blockGameIdToId.set(p,S)}let ke=e.properties??[],se=ke.filter(p=>p.shared??!0).map(Xe),ut=ke.filter(p=>!(p.shared??!0)).map(Xe),gt=new Set(se.map(p=>p.name.toLowerCase())),x=t.registry??new z,Ee=!t.registry||t[Je]===!0,Re=new k(se,{pathPrefix:"@patter."}),ie=[],W=[];try{x.mountOwned("patter",Re,{owner:_}),W.push("patter");let p=new Set;for(let[g,S]of Object.entries(Ze(t))){let y=e.scopeRegistry?.scopes.find(ft=>ft.token===g),K=(y?.declarations??[]).map(rt);x.defineForeign(g,S,K,{writable:y?.writable??!0,owner:_}),W.push(g),p.add(g)}for(let g of Ee?e.scopeRegistry?.scopes??[]:[]){if(p.has(g.token)||x.has(g.token))continue;let S=(g.declarations??[]).map(y=>Vt(y,g.writable));x.defineOwned(g.token,S,{owner:_}),W.push(g.token),ie.push(g.token)}}catch(p){for(let g of W)x.remove(g,{keep:!0});throw p}let De=new Map;for(let[p,g]of Object.entries(e.scenes)){let S=new Set((g.sceneProps??[]).filter(y=>y.shared??!1).map(y=>y.name.toLowerCase()));De.set(p,S)}this.host={logEnabled:t.log??!1,tracing:t.log??!1,emitEngine:(p,g,S)=>this.emitEngine(p,g,S),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,qualifierDisplay:d,speakerLabels:h,speakerPattern:f,nodeIndex:ve,blockIndex:be,blockById:xe,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:At(e),padIndex:Ft(e),registry:x,ownsRegistry:Ee,patterBag:Re,hostScopes:ie,patterSharedDecls:se,patterLocalDecls:ut,patterSharedNames:gt,sceneSharedNames:De,sharedVisits:new Map,qualityLadders:new Map,sharedSelectors:new Map,stageBags:new Map,journal:null,customRng:t.rng,onDryChoice:t.onDryChoice,onError:t.onError??(p=>console.warn(`Patterplay: ${p.kind} on ${p.node} in flow \'${p.flow}\' failed, played through: ${p.message}`)),reportRefits:()=>{let p=x.takeMisfits(g=>g==="patter"||g.startsWith("patter/")||ie.includes(g));if(p.length!==0)if(t.onRefit)t.onRefit(p);else for(let g of p)console.warn(`Patterplay: the save\'s ${g.scope}.${g.name} was ${JSON.stringify(g.value)}, which no longer fits its declaration; it is back at its default`)},replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??he).open,captionClose:(e.closedCaptions??he).close,captionCharacter:e.closedCaptions?.character||qe,refSplitCache:new Map,refSplitRevision:x.revision},this.host.reportRefits()}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),n=c=>(c.setLocale(this.currentLocale),c.setClosedCaptions(this.host.captionsOn),c),s=()=>new r(e,{...this.creationOptions,registry:this.host.registry,[Je]:this.host.ownsRegistry}),i=new Set(["patter",...this.host.hostScopes]),o=this.host.ownsRegistry?void 0:Object.fromEntries(Object.entries(this.host.registry.save()).filter(([c])=>i.has(c)||c.startsWith("patter/")));this.release(!0),o&&this.host.registry.load(o,{keepParked:!0,refit:!0});let a=s();try{return a.loadGame(t),n(a)}catch{a.release(!0);let c=this.host.ownsRegistry?t.registry:o;c&&this.host.registry.load(c,{keepParked:!0,refit:!0});let l=s();for(let[d,h]of Object.entries(t.flows)){let f=h.cursor.currentSceneId;try{l.openFlow(d,f!==null?{scene:f}:{})}catch{}}return n(l)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){this.assertExternalScopes();let{sceneId:n,blockId:s}=this.resolveOpenAddress(t.scene,t.block),i=this.flowsById.get(e),o=this.host.journal;if(o&&i&&!i.isClosed)throw new Error(`openFlow would replace the open flow \'${e}\', which can\'t be undone while a checkpoint is open`);i?.close();let a=new O(e,this.host,t.seed??this.defaultSeed);return o&&(o.flows.add(a),o.opened.add(a),o.undo.push(()=>{a.close(),i?this.flowsById.set(e,i):this.flowsById.delete(e)})),this.flowsById.set(e,a),a.begin(n,s),a}runFlow(e,t,n){let s=this.flowsById.get(e);if(!s)return this.openFlow(e,{scene:t,block:n}).advanceToStop().played;if(!s.goto(t,n))throw new Error(`runFlow: address not found: ${t}${n===void 0?"":` / ${n}`}`);return s.advanceToStop().played}assertExternalScopes(){for(let e of this.host.bundle.externalScopes??[])if(!this.host.registry.has(e))throw new Error(`this content names @${e}, which no engine on this registry registered: give every engine the game\'s one registry`)}resolveOpenAddress(e,t){if(e!==void 0){let n=me(this.host,e);if(n===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:n};let s=ye(this.host,n,t);if(s===void 0)throw new Error(`unknown block: ${t}`);return{sceneId:n,blockId:s}}if(t!==void 0){if(!this.host.blockIndex.has(t))throw new Error(`unknown block: ${t}`);return{blockId:t}}if(Object.keys(this.host.bundle.scenes).length===0)throw new Error("no scenes in bundle");return{}}resolveSceneRef(e){if(e!=null)return me(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:ye(this.host,e,t)}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?m(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?m(t):void 0}tagsForBeat(e){return I(this.host,e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?I(this.host,t):void 0)??[]}tagsForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t);return(s!=null?I(this.host,s):void 0)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.gameData}}gameDataForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(n!=null?this.host.blockById.get(n):void 0)?.gameData}}extensionsForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.extensions}}extensionsForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(n!=null?this.host.blockById.get(n):void 0)?.extensions}}getCast(){let e=[];for(let t of this.host.bundle.cast??[])t?.name&&e.push(t.name);return e}castForScene(e){let t=this.resolveSceneRef(e),n=t!=null?this.host.bundle.scenes[t]:void 0;if(!n)return[];let s=new Set;for(let i of n.blocks)Qe(i.children,s);return[...s]}castForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t),i=s!=null?this.host.blockById.get(s):void 0;if(!i)return[];let o=new Set;return Qe(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...m(e)?{gameId:m(e)}:{},name:e.name,...ze(e.gameData),...b(e.extensions),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...m(t)?{gameId:m(t)}:{},name:t.name,...ze(t.gameData),...b(t.extensions),...this.tagsField(t.id),children:t.children.map(n=>this.outlineNode(n))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let n of t.blocks)C(n.children,s=>{if(s.type==="snippet")for(let i of s.beats??[])e.push({sceneId:t.id,blockId:n.id,snippetId:s.id,beat:this.beatInfo(i)})});return e}outlineNode(e){return e.type==="group"?{type:"group",id:e.id,...this.tagsField(e.id),...e.selector?{selector:e.selector}:{},...e.prompt?{prompt:this.beatInfo(e.prompt)}:{},children:e.children.map(t=>this.outlineNode(t))}:{type:"snippet",id:e.id,...this.tagsField(e.id),beats:(e.beats??[]).map(t=>this.beatInfo(t)),...e.jump?{jumpTo:e.jump.to,...e.jump.mode?{jumpMode:e.jump.mode}:{}}:{}}}beatInfo(e){let t=I(this.host,e.id),n={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){n.character=e.character;let i=this.host.defaultStrings[ee(e.character)]??this.host.castDisplay.get(e.character);i!==void 0&&(n.characterName=i)}if(e.direction!==void 0&&(n.direction=e.direction),e.qualifier!==void 0){n.qualifier=e.qualifier;let i=this.host.defaultStrings[te(e.qualifier)]??this.host.qualifierDisplay.get(e.qualifier);i!==void 0&&(n.qualifierName=i)}let s=$(this.host,e.character,e.qualifier,!0);s!==void 0&&(n.speakerLabel=s)}if(e.kind==="line"||e.kind==="text"){let s=this.host.padIndex.get(e.id);n.padAfter=s?.resolved??E,s?.own!==void 0&&(n.ownPadAfter=s.own);let i=this.host.defaultStrings[e.id];i!==void 0&&(n.text=i)}return e.gameData&&Object.keys(e.gameData).length&&(n.gameData=e.gameData),e.extensions&&Object.keys(e.extensions).length&&(n.extensions=e.extensions),t&&t.length&&(n.tags=t),n}tagsField(e){let t=I(this.host,e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}listBags(){let e=[{bag:this.host.patterBag}];for(let[t,n]of this.host.stageBags)e.push({bag:n,pathPrefix:`@scene:${t}.`});return e}checkpoint(){if(this.host.journal)throw new Error("a checkpoint is already open: roll it back or commit it first");let e={undo:[],flows:new Set,opened:new Set,selectors:new Set,flowSelectors:new Map};return this.host.journal=e,new U(e)}rollback(e){this.endCheckpoint(e);let t=e.journal.undo;for(let n=t.length-1;n>=0;n--)t[n]();r.forget(e.journal)}commit(e){this.endCheckpoint(e),r.forget(e.journal)}get inCheckpoint(){return this.host.journal!==null}endCheckpoint(e){if(this.host.journal!==e.journal)throw new Error("that checkpoint is not the open one");this.host.journal=null}static forget(e){e.undo=[],e.flows.clear(),e.opened.clear(),e.selectors.clear(),e.flowSelectors.clear()}refuseInCheckpoint(e){if(this.host.journal)throw new Error(`${e} can\'t be undone, so it isn\'t allowed while a checkpoint is open`)}captureFlow(e){let t=this.flowsById.get(e);if(!t)throw new Error(`no open flow \'${e}\' to capture`);return{version:M,...t.snapshot(),...qt(e,this.host.registry.save())}}restoreFlow(e,t){this.refuseInCheckpoint("restoreFlow");let n=t.version;if(n!==2&&n!==M)throw new Error(`unsupported flow state version: ${String(n)}`);let s=t.cursor;if(typeof s!="object"||s===null)throw new Error("malformed flow state: no cursor");if(this.flowsById.has(e))throw new Error(`a flow \'${e}\' is already open; close it before restoring another under that id`);this.assertExternalScopes();let i=w.flow(e);this.host.registry.discardParked(i),this.host.registry.load(it(e,t),{keepParked:!0,refit:!0});let o=new O(e,this.host,this.defaultSeed);try{o.restore(t)}catch(a){throw o.close(),this.host.registry.discardParked(i),a}return this.flowsById.set(e,o),this.host.reportRefits(),o}closeFlow(e){this.refuseInCheckpoint("closeFlow"),this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){this.refuseInCheckpoint("reset");for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.patterBag.reseed(this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear();for(let e of this.host.stageBags.keys())this.host.registry.remove(w.stage(e));this.host.stageBags.clear(),this.host.registry.discardParked("patter/")}release(e){for(let n of this.flowsById.values())n.releaseBags(e),n.close();this.flowsById.clear();let t=this.host.registry;for(let n of this.host.stageBags.keys())t.remove(w.stage(n),{keep:e});this.host.stageBags.clear();for(let n of["patter",...this.host.hostScopes])t.has(n)&&t.remove(n,{keep:e});for(let n of Object.keys(Ze(this.creationOptions)))t.has(n)&&t.remove(n)}getProperty(e){let{scope:t,name:n}=this.splitShared(e);return this.host.registry.get(t,n)}setProperty(e,t){let{scope:n,name:s}=this.splitShared(e),i=this.host.journal?this.host.registry.get(n,s):void 0;this.host.registry.set(n,s,t,{host:!0}),this.host.journal&&i!==void 0&&this.host.journal.undo.push(()=>this.host.registry.set(n,s,i,{host:!0}))}stageIndex(e,t){return P(this.ladderOf(e),t)}compareStages(e,t,n){return N(this.ladderOf(e),t,n)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitShared(e),s=Mt(this.host,t,n);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}log(){return this.engineLog}clearLog(){this.engineLog.length=0}onTrace(e){return this.engineTraceHandlers.add(e),this.host.tracing=!0,()=>{this.engineTraceHandlers.delete(e),this.host.tracing=this.host.logEnabled||this.engineTraceHandlers.size>0}}emitEngine(e,t,n){for(let s of this.engineTraceHandlers)s(e,t);this.host.logEnabled&&this.engineLog.push({...t,flow:e,seq:this.engineSeq++,...n?{scene:n}:{}})}getVisitCounts(){return Object.fromEntries(this.host.sharedVisits)}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:A(e),writable:e.writable??!0}))}splitShared(e){let t=st(this.host,e);if(t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return{patter:this.host.patterBag.save()}}load(e){e.patter&&this.host.patterBag.load(e.patter)}saveGame(){let e={};for(let[t,n]of this.flowsById)e[t]=n.snapshot();return{version:M,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:tt(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==M)throw new Error(`unsupported save version: ${String(t)}`);let n=e.flows;if(typeof n!="object"||n===null||Array.isArray(n))throw new Error("malformed save: no flows");this.assertExternalScopes();let s=this.host.registry;for(let[o,a]of this.flowsById)a.releaseBags(o in e.flows),a.close();this.flowsById.clear();for(let o of this.host.stageBags.keys())s.remove(w.stage(o),{keep:!0});this.host.stageBags.clear();let i=e.version===2?Gt(e):e.registry;i&&(this.host.ownsRegistry?s.load(i,{refit:!0}):s.load(i,{keepParked:!0,refit:!0})),this.host.sharedVisits.clear();for(let[o,a]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(o,a);this.host.sharedSelectors.clear();for(let[o,a]of nt(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new O(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}this.host.reportRefits()}},O=class{id;host;local;registered=new Set;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPrompt=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.patterBag.get(e):this.local.get(e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.registry.set("patter",e,t):this.local.set(e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;if(t!==null)return this.sceneBagFor(t,e)?.get(e)},set:(e,t)=>{let n=this.currentSceneId;if(n===null)return;let s=this.sceneBagFor(n,e);s&&s.set(e,t)}};evalCtx;ctxRevision=-1;registryQualities;flowLog=[];flowSeq=0;constructor(e,t,n){this.id=e,this.host=t,this.rngState=L(n),this.local=this.newLocal(),this.evalCtx={scopes:{},host:{nextRandom:this.rng,visits:s=>this.visitCounts.get(s)??0,patterVisits:s=>this.host.sharedVisits.get(s)??0},qualities:(s,i)=>this.stagesFor(s,i)}}context(){let e=this.host.registry;if(e.revision!==this.ctxRevision){let t=e.toEvalContext(),n=this.evalCtx.scopes;for(let s of Object.keys(n))delete n[s];Object.assign(n,t.scopes),n.patter=this.patterResolver,n.scene=this.sceneResolver,this.registryQualities=t.qualities,this.ctxRevision=e.revision}return this.evalCtx}stagesFor(e,t){let n=t.toLowerCase();if(e==="patter")return H(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(n);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:H(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(n)}return this.registryQualities?.(e,t)??H(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}begin(e,t){if(this.host.journal&&!this.host.journal.opened.has(this))throw new Error("a flow can\'t be started or reset while a checkpoint is open");if(this.releaseBags(!1),this.host.registry.discardParked(w.flow(this.id)),this.mountLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.clearPending(),this.started=!0,t){let n=this.host.blockIndex.get(t);if(!n)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(n.sceneId),this.stack=[{sceneId:n.sceneId,containerId:t,index:0}],this.enter(t)}else{let n=e??Object.keys(this.host.bundle.scenes)[0],s=n?this.host.bundle.scenes[n]:void 0;if(!s)throw new Error(n?`unknown scene: ${n}`:"no scenes in bundle");this.enterSceneSetup(n);let i=s.blocks[0];i&&(this.stack=[{sceneId:n,containerId:i.id,index:0}],this.enter(i.id))}this.settle()}reset(e,t){this.begin(e,t)}start(e,t){this.reset(e,t)}goto(e,t){if(this.closed)return!1;if(this.touch(),e==="END")return this.started=!0,this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let n=me(this.host,e);if(n===void 0)return!1;let s;return t!==void 0&&(s=ye(this.host,n,t),s===void 0)?!1:this.started?(this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??n,"jump"),this.settle(),!0):(this.begin(n,s),!0)}close(){this.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.clearPending()}clearPending(){this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started:n,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:d}=this,h=this.stack.map(f=>({...f}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:n,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:d,stack:h})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.touch(),this.pendingPrompt){let e=this.pendingPrompt;return this.pendingPrompt=null,this.pendingPromptOwnerId=null,e.shown?this.promptResult(e.beat,e.shown):this.beatResult(e.beat)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit,this.activeSnippet.id);let i=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(i);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let n=this.childrenOf(t.containerId);if(!n){this.stack.pop();continue}let s=t.index;for(;t.index<n.length&&!this.eligible(n[t.index]);)t.index++;if(this.host.tracing&&t.index!==s&&this.emit({type:"select",group:t.containerId,selector:"run",children:n.slice(s,t.index+1).map((i,o)=>({id:i.id,eligible:s+o===t.index})),picked:n[t.index]?.id??null}),t.index>=n.length){this.stack.pop();continue}this.enterChild(n[t.index++])}}listBags(){let e=[{bag:this.local,pathPrefix:`${this.id}/@patter.`}];for(let[t,n]of this.sceneBags)e.push({bag:n,pathPrefix:`${this.id}/@scene:${t}.`});return e}log(){return this.flowLog}clearLog(){this.flowLog.length=0}emit(e){let t=this.currentSceneId??void 0;this.host.emitEngine(this.id,e,t),this.host.logEnabled&&this.flowLog.push({...e,seq:this.flowSeq++,...t?{scene:t}:{}})}getVisitCounts(){return Object.fromEntries(this.visitCounts)}stepFor(e){let t=Tt(this.host.bundle).get(e);return t?this.beatResult(t):void 0}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let n=t.options.find(o=>o.id===e);if(!n)throw new Error(`unknown choice option: ${e}`);if(!n.eligible)throw new Error(`choice option is not eligible: ${e}`);this.touch();let s=t.byId.get(e);this.host.tracing&&this.emit({type:"chose",group:t.groupId,option:e}),this.pendingChoice=null;let i=this.host.replayPromptOnChoose?this.authoredPromptOf(s):void 0;this.pendingPrompt=i&&n.prompt?{beat:i,shown:{...n.prompt}}:null,this.pendingPromptOwnerId=this.pendingPrompt?s.id:null,this.enterChild(s)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:n}=this.splitRef(e);return t==="patter"?this.patterResolver.get(n):t==="scene"?this.sceneResolver.get(n):this.host.registry.get(t,n)}setProperty(e,t){this.writeProperty(e,t,!0)}stageIndex(e,t){return P(this.ladderOf(e),t)}compareStages(e,t,n){return N(this.ladderOf(e),t,n)}nextStage(e,t){return B(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitRef(e);this.context();let s=this.stagesFor(t,n);if(!s)throw new Error(`\'${e}\' is not a quality`);return s}writeProperty(e,t,n){let{scope:s,name:i}=this.splitRef(e),o=this.host.journal;if(s==="patter"){if(o){let a=this.host.patterSharedNames.has(i),c=a?this.host.patterBag.get(i):this.local.get(i),l=this.local;c!==void 0&&o.undo.push(a?()=>this.host.registry.set("patter",i,c):()=>l.set(i,c))}this.patterResolver.set(i,t)}else if(s==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);if(o){let a=this.sceneBagFor(this.currentSceneId,i),c=a?.get(i);a&&c!==void 0&&o.undo.push(()=>a.set(i,c))}this.sceneResolver.set(i,t)}else{let a=o?this.host.registry.get(s,i):void 0;this.host.registry.set(s,i,t,n?{host:!0}:void 0),o&&a!==void 0&&o.undo.push(()=>this.host.registry.set(s,i,a,{host:!0}))}}snapshot(){return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(({extensions:e,...t})=>({...t}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:jt(this.pendingPrompt.shown)}:{},selectors:tt(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");this.restoreCursor(e),this.releaseBags(!0),this.mountLocal();for(let t of new Set([this.currentSceneId,...this.stack.map(n=>n.sceneId)]))t!==null&&this.host.bundle.scenes[t]&&this.ensureSceneBags(t)}restoreCursor(e){this.rngState=L(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(n=>{let{nextId:s,...i}=n;if(s!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===s)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.activeSnippet=null,t.activeSnippetId!==null){let n=this.host.nodeIndex.get(t.activeSnippetId);n&&n.type==="snippet"&&(this.activeSnippet=n)}if(this.selectors=nt(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let n=new Map,s=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);if(!o)continue;n.set(i.id,o);let{prompt:a,extensions:c,...l}=i,d=a?.kind==="line"&&a.speakerLabel===void 0?$(this.host,a.character,a.qualifier):void 0;s.push({...l,...a?{prompt:{...a,padAfter:a.padAfter??this.promptPadOf(o),...d!==void 0?{speakerLabel:d}:{}}}:{},...b(o.extensions)})}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:n})}if(this.pendingPrompt=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let n=this.host.nodeIndex.get(this.pendingPromptOwnerId),s=n?this.authoredPromptOf(n):void 0,i=t.pendingPrompt,o=i?.kind==="line"&&i.speakerLabel===void 0?$(this.host,i.character,i.qualifier):void 0;s?this.pendingPrompt={beat:s,...i?{shown:{...i,padAfter:i.padAfter??this.padOf(s.id),...o!==void 0?{speakerLabel:o}:{}}}:{}}:this.pendingPromptOwnerId=null}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry,t.id)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let n=this.selectChild(e);n&&this.enterChild(n)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let n=this.host.nodeIndex.get(e);if(n&&n.type==="group")return n.children}beginSnippet(e){this.runEffects(e.onEnter,e.id),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],n=new Map,s=[];for(let o of e.children){if(o.fallback===!0){s.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),c=o.secretUntilEligible===!0;!a&&c||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData,...b(o.extensions)}),n.set(o.id,o))}if(t.some(o=>o.eligible)){this.host.tracing&&this.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),this.pendingChoice={groupId:e.id,options:t,byId:n};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.tracing&&this.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.host.tracing&&this.emit({type:"jump",to:e,mode:t}),e==="END"){this.flowEnded=!0,this.stack=[];return}let n,s,i=this.host.bundle.scenes[e];if(i){this.enterSceneSetup(e);let a=i.blocks[0];if(!a){t==="jump"&&(this.stack=[]);return}n=e,s=a.id}else{let a=this.host.blockIndex.get(e);if(!a)throw new Error(`jump target not found: ${e}`);a.sceneId!==this.currentSceneId&&this.enterSceneSetup(a.sceneId),n=a.sceneId,s=e}this.enter(s);let o={sceneId:n,containerId:s,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.map(c=>({id:c.id,eligible:this.eligible(c)})),n=e.children.filter((c,l)=>t[l].eligible),s=e.options?.order??"sequential",i=e.options?.exhaust??"once",o=c=>(this.host.tracing&&this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(n.length===0)return o(null);let a=this.selectorState(e);switch(e.selector){case"branch":return o(n[0]);case"sequence":return o(s==="shuffle"?this.pickShuffle(n,i,a):s==="specificity"?this.pickSpecificity(n,i,a):this.pickSequential(n,i,a));default:return null}}pickSequential(e,t,n){let s=e.length,i=n.seq??0;return n.seq=i+1,t==="repeat"?e[i%s]:i<s?e[i]:t==="stick"?e[s-1]:null}pickShuffle(e,t,n){let s=e.length,i=t==="stick",o=()=>(i?e.slice(0,s-1):e).map(f=>f.id),a=new Set(e.map(f=>f.id));if(n.bag===void 0&&(n.bag=o()),!n.bag.some(f=>a.has(f))){if(t==="once")return null;if(i){let f=e[s-1];return n.last=f.id,f}if(n.bag=o(),n.bag.length===0)return null}let c=n.bag.filter(f=>a.has(f)),l=n.last!==void 0&&c.length>1?c.indexOf(n.last):-1,d=Math.floor(this.rng()*(l>=0?c.length-1:c.length));l>=0&&d>=l&&d++;let h=c[d];return n.bag.splice(n.bag.indexOf(h),1),n.last=h,e.find(f=>f.id===h)}pickSpecificity(e,t,n){let s=e;if(t!=="repeat"){n.bag===void 0&&(n.bag=e.map(d=>d.id));let l=new Set(n.bag);if(s=e.filter(d=>l.has(d.id)),s.length===0)return t==="stick"&&n.last!==void 0?e.find(d=>d.id===n.last)??null:null}let i=-1,a=s.map(l=>{let d=this.specScore(l);return d>i&&(i=d),{c:l,s:d}}).filter(l=>l.s===i).map(l=>l.c),c;if(a.length===1)c=a[0];else{let l=n.last!==void 0?a.findIndex(h=>h.id===n.last):-1,d=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&d>=l&&d++,c=a[d]}return t!=="repeat"&&(n.bag=n.bag.filter(l=>l!==c.id)),n.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0,e):0}matchedSpec(e,t,n){return Ie(e,i=>{try{return et(J(i,this.context(),ge))}catch(o){return this.reportError("best-match",n.id,n.condition,o),!1}},{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,n=this.host.journal,s;if(n&&(s=e.shared?n.selectors:n.flowSelectors.get(this),s||(s=new Set,n.flowSelectors.set(this,s))),n&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;n.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e,t){for(let n of e??[]){let s;try{s=this.evalExpr(n.value);let i=this.host.tracing?this.getProperty(n.target):void 0;this.writeProperty(n.target,s,!1),this.host.tracing&&this.emit({type:"write",target:n.target,value:s,...i!==void 0?{prev:i}:{}})}catch(i){this.reportError("effect",t,n.value,i)}}}eligible(e){if(!e.condition)return!0;try{return et(this.evalExpr(e.condition))}catch(t){return this.reportError("condition",e.id,e.condition,t),!1}}reportError(e,t,n,s){let i=s instanceof Error?s.message:String(s),o=n?.src;this.host.onError({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i}),this.host.tracing&&this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return J(this.conditionAst(e),this.context(),ge)}conditionAst(e){let t=He.get(e);return t||(t=R(e.ast),He.set(e,t)),t}enter(e){let t=this.visitCounts,n=t.get(e);t.set(e,(n??0)+1);let s=this.host.sharedVisits,i=s.get(e);s.set(e,(i??0)+1),this.host.journal&&this.host.journal.undo.push(()=>{n===void 0?t.delete(e):t.set(e,n),i===void 0?s.delete(e):s.set(e,i)})}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=ce(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=I(this.host,e.id),n=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...b(e.extensions),...n};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),padAfter:this.padOf(e.id),...e.action?{action:!0}:{},gameData:e.gameData,...b(e.extensions),...n};case"line":{let s=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?s:this.interpolate(s)),c=i&&a.length===0;return{type:"line",id:e.id,text:a,character:c?void 0:e.character,characterName:c?void 0:this.resolveCharacterName(e.character),direction:c?void 0:e.direction,qualifier:c?void 0:e.qualifier,qualifierName:c?void 0:this.resolveQualifierName(e.qualifier),speakerLabel:c?void 0:$(this.host,e.character,e.qualifier),padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...n}}}}interpolate(e){return Le(e,t=>this.getProperty(t))}stripCaptions(e){return Te(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let n=this.interpolate(this.resolveString(t.id)),s=this.padOf(t.id);return t.kind==="line"?{kind:"line",text:this.captionLine(n),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction,qualifier:t.qualifier,qualifierName:this.resolveQualifierName(t.qualifier),speakerLabel:$(this.host,t.character,t.qualifier),padAfter:s}:{kind:"text",text:n,padAfter:s}}authoredPromptOf(e){return e.type==="group"?e.prompt:void 0}promptResult(e,t){let n=I(this.host,e.id),s=n&&n.length?{tags:n}:{};return t.kind==="text"?{type:"text",id:e.id,text:t.text,padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...s}:{type:"line",id:e.id,text:t.text,character:t.character,characterName:t.characterName,direction:t.direction,qualifier:t.qualifier,qualifierName:t.qualifierName,speakerLabel:t.speakerLabel,padAfter:this.padOf(e.id),gameData:e.gameData,...b(e.extensions),...s}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(n=>n.kind==="line"||n.kind==="text")}firstTextSnippetIn(e){let t;return C(e,n=>{!t&&n.type==="snippet"&&(n.beats??[]).some(s=>s.kind==="line"||s.kind==="text")&&(t=n)}),t}resolveString(e){if(this.host.emitIds)return e;let t=this.host.strings[e];if(t!==void 0)return t;let n=this.host.defaultStrings[e];return n!==void 0?`<Untranslated: ${e}> ${n}`:e}resolveCharacterName(e){if(e===void 0||this.host.emitIds)return;let t=ee(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}promptPadOf(e){let t=this.promptBeatOf(e);return t?this.padOf(t.id):E}padOf(e){return this.host.padIndex.get(e)?.resolved??E}resolveQualifierName(e){if(e===void 0||this.host.emitIds)return;let t=te(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.qualifierDisplay.get(e)}splitRef(e){return st(this.host,e)}newLocal(){return new k(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=w.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:_}),this.registered.add(e),this.host.reportRefits()}releaseBags(e){for(let t of this.registered)this.host.registry.remove(t,{keep:e});this.registered.clear(),this.sceneBags.clear()}sceneBagFor(e,t){if(this.host.bundle.scenes[e])return this.ensureSceneBags(e),this.host.sceneSharedNames.get(e)?.has(t.toLowerCase())?this.host.stageBags.get(e):this.sceneBags.get(e)}ensureSceneBags(e){let t=this.host.sceneSharedNames.get(e)??new Set,n=this.host.bundle.scenes[e]?.sceneProps??[],s=this.host.journal,i=!this.sceneBags.has(e)||!this.host.stageBags.has(e);if(!this.sceneBags.has(e)){let o=new k(n.filter(l=>!t.has(l.name.toLowerCase())),{pathPrefix:"@scene."}),a=w.flowScene(this.id,e),c=Ye(this.host.registry,a,o,s!==null);this.registered.add(a),this.sceneBags.set(e,o),s&&s.undo.push(()=>{this.sceneBags.get(e)===o&&(this.host.registry.remove(a,c?{keep:!0}:void 0),this.registered.delete(a),this.sceneBags.delete(e))})}if(!this.host.stageBags.has(e)){let o=new k(n.filter(c=>t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),a=Ye(this.host.registry,w.stage(e),o,s!==null);this.host.stageBags.set(e,o),s&&s.undo.push(()=>{this.host.stageBags.get(e)===o&&(this.host.registry.remove(w.stage(e),a?{keep:!0}:void 0),this.host.stageBags.delete(e))})}i&&this.host.reportRefits()}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;this.ensureSceneBags(e.id);for(let n of e.sceneProps??[]){if(!n.temporary)continue;let s=n.name.toLowerCase(),i=t.has(s)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);if(!i)continue;let o=this.host.journal?i.get(s):void 0;o!==void 0&&this.host.journal.undo.push(()=>i.set(s,o)),i.set(s,A(n))}}};function Qe(r,e){C(r,t=>{if(t.type==="group"){t.prompt?.kind==="line"&&t.prompt.character&&e.add(t.prompt.character);return}for(let n of t.beats??[])n.kind==="line"&&n.character&&e.add(n.character)})}function $(r,e,t,n=!1){if(!e||r.emitIds)return;let s=c=>(n?void 0:r.strings[c])??r.defaultStrings[c],i=s(ee(e))??r.castDisplay.get(e)??e;if(t===void 0)return i;let o=r.speakerLabels.get(`${e}\\0${t}`);if(o!==void 0)return s(je(e,t))??o;let a=s(te(t))??r.qualifierDisplay.get(t)??t;return Ge(s(Me)??r.speakerPattern,i,a)}function ze(r){return r&&Object.keys(r).length?{gameData:r}:{}}function b(r){return r&&Object.keys(r).length?{extensions:r}:{}}function jt(r){let e={kind:r.kind,text:r.text};return r.character!==void 0&&(e.character=r.character),r.characterName!==void 0&&(e.characterName=r.characterName),r.direction!==void 0&&(e.direction=r.direction),r.qualifier!==void 0&&(e.qualifier=r.qualifier),r.qualifierName!==void 0&&(e.qualifierName=r.qualifierName),r.speakerLabel!==void 0&&(e.speakerLabel=r.speakerLabel),typeof r.padAfter=="number"&&(e.padAfter=r.padAfter),e}function tt(r){let e={};for(let[t,n]of r){let s={};n.seq!==void 0&&(s.seq=n.seq),n.bag&&(s.bag=[...n.bag]),n.last!==void 0&&(s.last=n.last),e[t]=s}return e}function nt(r){let e=new Map;for(let[t,n]of Object.entries(r??{})){let s={};n.seq!==void 0&&(s.seq=n.seq),n.bag&&(s.bag=[...n.bag]),n.last!==void 0&&(s.last=n.last),e.set(t,s)}return e}function Ye(r,e,t,n){let s=n?JSON.stringify(t.save()):"";return r.mountOwned(e,t,{owner:_}),n&&JSON.stringify(t.save())!==s}function Xe(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default}}function Ze(r){return{...r.world?{world:r.world}:{},...r.hostScopes}}function H(r,e,t){let n=r.qualityLadders.get(e);if(!n){n=new Map;for(let s of t()??[]){let i=s.name.toLowerCase();s.type==="quality"&&s.stages&&!n.has(i)&&n.set(i,s.stages)}r.qualityLadders.set(e,n)}return n}function Mt(r,e,t){let n=t.toLowerCase();return e==="patter"?H(r,"patter",()=>[...r.patterSharedDecls,...r.patterLocalDecls]).get(n):r.registry.toEvalContext().qualities?.(e,t)??H(r,`host/${e}`,()=>r.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}function rt(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default,writable:r.writable}}function Vt(r,e){let t=rt(r),n=r.writable??e;return n===void 0?t:{...t,writable:n}}function me(r,e){return Z(r.bundle.scenes,e,r.sceneGameIdToId)}function ye(r,e,t){return r.blockGameIdToId.get(e)?.get(t)??(r.blockIndex.get(t)?.sceneId===e?t:void 0)}function st(r,e){r.refSplitRevision!==r.registry.revision&&(r.refSplitCache.clear(),r.refSplitRevision=r.registry.revision);let t=r.refSplitCache.get(e);return t||(t=Fe(e,n=>n==="scene"||r.registry.has(n)||(r.bundle.externalScopes?.includes(n)??!1)),r.refSplitCache.set(e,t)),t}function Gt(r){let e={};r.shared?.patter&&(e.patter=r.shared.patter);for(let[t,n]of Object.entries(r.stageBags??{}))e[w.stage(t)]=n;for(let[t,n]of Object.entries(r.flows??{}))Object.assign(e,it(t,n));return e}function it(r,e){let t={};e.scopes?.patter&&(t[w.flowGlobals(r)]=e.scopes.patter);for(let[n,s]of Object.entries(e.sceneBags??{}))t[w.flowScene(r,n)]=s;return t}function qt(r,e){let t={scopes:{},sceneBags:{}},n=w.flow(r);for(let[s,i]of Object.entries(e)){if(!s.startsWith(n))continue;let o=s.slice(n.length);o==="patter"?t.scopes.patter=i:o.startsWith("scene/")&&(t.sceneBags[Lt(o.slice(6))]=i)}return t}function et(r){return typeof r=="boolean"?r:typeof r=="number"?r!==0:typeof r=="string"?r!=="":r.length>0}var $t=(r,e)=>r.shared??e;function Se(r,e){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.default!==void 0?{default:r.default}:{},shared:$t(r,e)}}function _t(r){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.values?{values:[...r.values]}:{},...r.purpose?{purpose:r.purpose}:{}}}function Ht(r,e){e.blocks++;let t=[...r.children];for(;t.length;){let n=t.pop();if(n.type==="group"){e.groups++,n.prompt&&e.prompts++,t.push(...n.children);continue}e.snippets++;for(let s of n.beats??[])e.beats++,s.kind==="gameEvent"&&e.gameEvents++}}function ot(r){let e={scenes:0,blocks:0,groups:0,snippets:0,beats:0,prompts:0,gameEvents:0,cast:r.cast?.length??0},t=[],n=[];for(let o of Object.values(r.scenes)){e.scenes++;let a=m(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:m(c),name:c.name}))});for(let c of o.blocks)Ht(c,e);o.sceneProps?.length&&n.push({gameId:a,properties:o.sceneProps.map(c=>Se(c,!1))})}let s=(r.scopeRegistry?.scopes??[]).map(o=>({token:o.token,writable:o.writable??!0,opaque:o.declarations===void 0,properties:(o.declarations??[]).map(a=>Se(a,!0))})),i=Object.entries(r.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(_t)}));return{identity:{schema:r.schema,project:r.content.project,...r.content.version!==void 0?{version:r.content.version}:{},...r.content.hash!==void 0?{hash:r.content.hash}:{},...r.content.structureHash!==void 0?{structureHash:r.content.structureHash}:{},voiced:r.voiced,defaultLocale:r.locales.default,locales:[...r.locales.included],localisation:r.localisation?.mode??"embedded",sourceDebug:r.localisation?.sourceDebug??!1},addresses:t,hostScopes:s,properties:{patter:(r.properties??[]).map(o=>Se(o,!0)),scene:n},gameData:i,counts:e}}var F=(r,e)=>Object.prototype.hasOwnProperty.call(r,e);function at(r,e){return r.gameDataFields?.[e]??[]}function we(r,e,t){return e&&F(e,t)?e[t]:r.find(n=>n.name===t)?.default}function ct(r,e){let t={};for(let n of r){let s=we(r,e,n.name);s!==void 0&&(t[n.name]=s)}for(let[n,s]of Object.entries(e??{}))F(t,n)||(t[n]=s);return t}function lt(r,e,t){let n=r.extensions!==void 0&&F(r.extensions,e)?r.extensions[e]:void 0;return n!==void 0&&F(n.fields,t)?n.fields[t]??[]:[]}function dt(r,e){return{...(r.extensions!==void 0&&F(r.extensions,e)?r.extensions[e]:void 0)?.settings??{}}}function pt(r,e,t=""){let n=o=>({...o?.extensions??{}}),s=Object.values(r.scenes??{}),i=o=>(F(r.scenes??{},o)?r.scenes[o]:void 0)??s.find(a=>m(a)===o);switch(e){case"project":{let o={};for(let[a,c]of Object.entries(r.extensions??{}))c.project!==void 0&&(o[a]=c.project);return o}case"scene":return n(i(t));case"block":{let o=t.indexOf("/");if(o<0)return{};let a=i(t.slice(0,o))?.blocks??[],c=t.slice(o+1);return n(a.find(l=>l.id===c)??a.find(l=>m(l)===c))}case"snippet":case"option":case"line":case"text":case"gameEvent":{let o,a=(c,l)=>{for(let d of c??[]){if(o!==void 0)return;if(d.type==="group"){if(l&&d.id===t&&e==="option"){o=d;return}if(l&&d.prompt?.id===t&&d.prompt.kind===e){o=d.prompt;return}a(d.children??[],d.selector==="choice");continue}if(d.id===t&&e==="snippet"){o=d;return}let h=(d.beats??[]).find(f=>f.id===t);if(h!==void 0){h.kind===e&&(o=h);return}}};for(let c of s)for(let l of c.blocks??[])o===void 0&&a(l.children??[],!1);return n(o)}default:return{}}}return vt(Ut);})();\n';
|
|
1774
2133
|
var PLAYABLE_TIMING_JS = 'var GameSubtitles=(()=>{var l=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var u=(t,e)=>{for(var s in e)l(t,s,{get:e[s],enumerable:!0})},f=(t,e,s,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of c(e))!_.call(t,i)&&i!==s&&l(t,i,{get:()=>e[i],enumerable:!(n=a(e,i))||n.enumerable});return t};var p=t=>f(l({},"__esModule",{value:!0}),t);var d={};u(d,{estimateDuration:()=>h});function h(t,{charsPerSecond:e=14,minSeconds:s=1.5,maxSeconds:n=18}={}){let i=0;for(let o of t??"")o!=="\\xAD"&&i++;let r=e>0?e:14;return Math.min(Math.max(i/r,s),n)}return p(d);})();\n';
|
|
1775
2134
|
|
|
1776
2135
|
// src/play-timeline.ts
|
|
@@ -1911,7 +2270,9 @@ var PLAYER_JS = String.raw`
|
|
|
1911
2270
|
resumeSave = null; // what's shown is where the engine is
|
|
1912
2271
|
if (!s || s.type === "end") { add("end", "The End"); return; } // no step is defensive: never hard-crash
|
|
1913
2272
|
if (s.type === "choice") { renderChoice(s); return; }
|
|
1914
|
-
|
|
2273
|
+
// Action text (a direction for whoever stages the scene, never shown to the player) takes no time of its
|
|
2274
|
+
// own, only its pause, and reads as a direction.
|
|
2275
|
+
timeline.place(s, s.action ? 0 : estimate(s.text));
|
|
1915
2276
|
if (s.type === "line") {
|
|
1916
2277
|
// The speaker's label as the runtime resolved it (TAM (O.S.), or a pair's own label): this page never
|
|
1917
2278
|
// puts a name and a qualifier together itself. (Its bundle always carries the strings, so a line with a
|
|
@@ -1920,7 +2281,7 @@ var PLAYER_JS = String.raw`
|
|
|
1920
2281
|
var dir = s.direction ? '<em class="dir">(' + esc(s.direction) + ')</em> ' : "";
|
|
1921
2282
|
add("line", '<span class="who" style="color:hsl(' + hueOf(s.character || who) + ',55%,38%)">' + esc(who) + '</span>' + dir + esc(s.text));
|
|
1922
2283
|
} else {
|
|
1923
|
-
add("text", esc(s.text));
|
|
2284
|
+
add(s.action ? "text action" : "text", esc(s.text));
|
|
1924
2285
|
}
|
|
1925
2286
|
scrollDown();
|
|
1926
2287
|
next(readNext());
|
|
@@ -1970,6 +2331,7 @@ var STYLE = String.raw`
|
|
|
1970
2331
|
.line .who { font-weight: 700; font-family: ui-sans-serif, system-ui, sans-serif; font-size: 0.8rem; letter-spacing: 0.02em; text-transform: uppercase; margin-right: 0.4rem; }
|
|
1971
2332
|
.line .dir { color: #9a8f78; font-style: italic; }
|
|
1972
2333
|
.text { color: #4a4334; }
|
|
2334
|
+
.text.action { color: #9a8f78; font-style: italic; }
|
|
1973
2335
|
.end { color: #9a8f78; font-style: italic; text-align: center; margin-top: 1.5rem; }
|
|
1974
2336
|
#controls { margin: 1.25rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
|
|
1975
2337
|
.choice { font: 1rem Georgia, serif; text-align: left; background: #fff; border: 1px solid #d9cfbb; border-radius: 8px; padding: 0.55rem 0.8rem; cursor: pointer; transition: border-color .15s, background .15s; animation: fade 0.4s ease-out both; }
|
|
@@ -1989,7 +2351,7 @@ function sourceStory(loaded) {
|
|
|
1989
2351
|
const def = full.locales?.default ?? Object.keys(full.strings)[0] ?? "en";
|
|
1990
2352
|
const bundle = { ...full, locales: { default: def, included: [def] }, strings: { [def]: full.strings[def] ?? {} } };
|
|
1991
2353
|
const title = loaded.project.project.name?.trim() || "A Patter story";
|
|
1992
|
-
const bundleJson =
|
|
2354
|
+
const bundleJson = canonicalStringify5(bundle, { trailingComma: false }).replace(/</g, "\\u003c");
|
|
1993
2355
|
return { title, lang: def, bundleJson };
|
|
1994
2356
|
}
|
|
1995
2357
|
function page(title, lang, head, tail) {
|
|
@@ -2038,99 +2400,32 @@ function runExportHtml(loaded) {
|
|
|
2038
2400
|
`<script>${PLAYABLE_RUNTIME_JS}</script>
|
|
2039
2401
|
<script>${PLAYABLE_TIMING_JS}</script>
|
|
2040
2402
|
<script>window.PATTER_BUNDLE=${bundleJson};</script>
|
|
2041
|
-
<script>${PLAYER_JS}</script>`
|
|
2042
|
-
);
|
|
2043
|
-
}
|
|
2044
|
-
function runExportWeb(loaded) {
|
|
2045
|
-
const { title, lang, bundleJson } = sourceStory(loaded);
|
|
2046
|
-
return {
|
|
2047
|
-
indexHtml: page(
|
|
2048
|
-
title,
|
|
2049
|
-
lang,
|
|
2050
|
-
`<link rel="stylesheet" href="style.css" />`,
|
|
2051
|
-
// The story must load first (patterplay.js reads window.PATTER_BUNDLE as it starts).
|
|
2052
|
-
`<script src="story.js"></script>
|
|
2053
|
-
<script src="patterplay.js"></script>`
|
|
2054
|
-
),
|
|
2055
|
-
styleCss: `${STYLE.trim()}
|
|
2056
|
-
`,
|
|
2057
|
-
patterplayJs: `${PLAYABLE_RUNTIME_JS}
|
|
2058
|
-
${PLAYABLE_TIMING_JS}
|
|
2059
|
-
${PLAYER_JS}`,
|
|
2060
|
-
storyJs: `window.PATTER_BUNDLE=${bundleJson};
|
|
2061
|
-
`
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
|
|
2065
|
-
// src/play.ts
|
|
2066
|
-
import { Engine } from "@patterkit/runtime";
|
|
2067
|
-
|
|
2068
|
-
// src/loaded-helpers.ts
|
|
2069
|
-
import { RERECORD_STATUS, effectiveGameId } from "@patterkit/model";
|
|
2070
|
-
function effectiveRecording(id, base, rerecord, lowest) {
|
|
2071
|
-
return rerecord.has(id) ? RERECORD_STATUS : base.get(id) ?? lowest;
|
|
2072
|
-
}
|
|
2073
|
-
function tableFor(loaded, locale) {
|
|
2074
|
-
const table = {};
|
|
2075
|
-
for (const l of loaded.locales) if (l.locale === locale) Object.assign(table, l.strings);
|
|
2076
|
-
return table;
|
|
2077
|
-
}
|
|
2078
|
-
function sourceStrings(loaded) {
|
|
2079
|
-
return tableFor(loaded, loaded.project.locales.default);
|
|
2080
|
-
}
|
|
2081
|
-
function resolveStart(loaded, override) {
|
|
2082
|
-
if (override?.scene) return { scene: override.scene, block: override.block };
|
|
2083
|
-
if (override?.block) return findBlockAnywhere(loaded, override.block);
|
|
2084
|
-
if (loaded.project.start) return { scene: loaded.project.start.scene, block: loaded.project.start.block };
|
|
2085
|
-
return {};
|
|
2086
|
-
}
|
|
2087
|
-
function findBlockAnywhere(loaded, ref) {
|
|
2088
|
-
const matches = [];
|
|
2089
|
-
for (const scene of loaded.scenes) {
|
|
2090
|
-
for (const block of scene.blocks) {
|
|
2091
|
-
if (block.id === ref) return { scene: scene.id, block: block.id };
|
|
2092
|
-
if (block.name === ref || effectiveGameId(block) === ref) matches.push({ scene, block });
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
if (matches.length === 0) throw new Error(`no scene has a block "${ref}"`);
|
|
2096
|
-
if (matches.length > 1) {
|
|
2097
|
-
const scenes = [...new Set(matches.map((m) => `"${m.scene.name}" (${m.scene.id})`))].join(", ");
|
|
2098
|
-
throw new Error(`more than one block is called "${ref}" (in ${scenes}): name the scene as well`);
|
|
2099
|
-
}
|
|
2100
|
-
return { scene: matches[0].scene.id, block: matches[0].block.id };
|
|
2101
|
-
}
|
|
2102
|
-
function stringsByLocale(loaded) {
|
|
2103
|
-
const byLocale = /* @__PURE__ */ new Map();
|
|
2104
|
-
for (const l of loaded.locales) {
|
|
2105
|
-
const table = byLocale.get(l.locale) ?? {};
|
|
2106
|
-
Object.assign(table, l.strings);
|
|
2107
|
-
byLocale.set(l.locale, table);
|
|
2108
|
-
}
|
|
2109
|
-
return byLocale;
|
|
2403
|
+
<script>${PLAYER_JS}</script>`
|
|
2404
|
+
);
|
|
2110
2405
|
}
|
|
2111
|
-
function
|
|
2112
|
-
const
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
}
|
|
2130
|
-
return { writing, recording, cut, rerecord, documentation, edits };
|
|
2406
|
+
function runExportWeb(loaded) {
|
|
2407
|
+
const { title, lang, bundleJson } = sourceStory(loaded);
|
|
2408
|
+
return {
|
|
2409
|
+
indexHtml: page(
|
|
2410
|
+
title,
|
|
2411
|
+
lang,
|
|
2412
|
+
`<link rel="stylesheet" href="style.css" />`,
|
|
2413
|
+
// The story must load first (patterplay.js reads window.PATTER_BUNDLE as it starts).
|
|
2414
|
+
`<script src="story.js"></script>
|
|
2415
|
+
<script src="patterplay.js"></script>`
|
|
2416
|
+
),
|
|
2417
|
+
styleCss: `${STYLE.trim()}
|
|
2418
|
+
`,
|
|
2419
|
+
patterplayJs: `${PLAYABLE_RUNTIME_JS}
|
|
2420
|
+
${PLAYABLE_TIMING_JS}
|
|
2421
|
+
${PLAYER_JS}`,
|
|
2422
|
+
storyJs: `window.PATTER_BUNDLE=${bundleJson};
|
|
2423
|
+
`
|
|
2424
|
+
};
|
|
2131
2425
|
}
|
|
2132
2426
|
|
|
2133
2427
|
// src/play.ts
|
|
2428
|
+
import { Engine } from "@patterkit/runtime";
|
|
2134
2429
|
function runPlay(loaded, opts = {}) {
|
|
2135
2430
|
const bundle = compileLoaded(loaded);
|
|
2136
2431
|
const events = [];
|
|
@@ -2216,7 +2511,7 @@ function renderPlay(result) {
|
|
|
2216
2511
|
|
|
2217
2512
|
// src/coverage.ts
|
|
2218
2513
|
import { Engine as Engine2 } from "@patterkit/runtime";
|
|
2219
|
-
import { isContentlessBeat as isContentlessBeat2, walkNodes } from "@patterkit/model";
|
|
2514
|
+
import { isContentlessBeat as isContentlessBeat2, walkNodes as walkNodes2 } from "@patterkit/model";
|
|
2220
2515
|
import { deserialiseAst, makePrng, stagesEitherSide } from "@wildwinter/expr";
|
|
2221
2516
|
var CADENCE_PROB = {
|
|
2222
2517
|
rarely: 0.15,
|
|
@@ -2467,7 +2762,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
2467
2762
|
for (const scene of loaded.scenes) {
|
|
2468
2763
|
nodeScene.set(scene.id, scene.id);
|
|
2469
2764
|
for (const block of scene.blocks) {
|
|
2470
|
-
|
|
2765
|
+
walkNodes2(block.children, (node) => {
|
|
2471
2766
|
nodeScene.set(node.id, scene.id);
|
|
2472
2767
|
if (node.type === "group") {
|
|
2473
2768
|
if (node.selector === "choice") choiceScene.set(node.id, scene.id);
|
|
@@ -2720,17 +3015,17 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
|
|
|
2720
3015
|
|
|
2721
3016
|
// src/format.ts
|
|
2722
3017
|
import { readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
2723
|
-
import { dirname as
|
|
2724
|
-
import { canonicalStringify as
|
|
3018
|
+
import { dirname as dirname7, resolve as resolve5 } from "path";
|
|
3019
|
+
import { canonicalStringify as canonicalStringify7, parseSource as parseSource6 } from "@patterkit/core";
|
|
2725
3020
|
|
|
2726
3021
|
// src/extensions.ts
|
|
2727
3022
|
import { readFileSync as readFileSync7 } from "fs";
|
|
2728
|
-
import { join as
|
|
2729
|
-
import { canonicalStringify as
|
|
3023
|
+
import { join as join8, relative as relative5, sep as sep4 } from "path";
|
|
3024
|
+
import { canonicalStringify as canonicalStringify6, parseSource as parseSource5 } from "@patterkit/core";
|
|
2730
3025
|
import { EXTENSIONS_DIR as EXTENSIONS_DIR3, MANIFEST_EXTENSION as MANIFEST_EXTENSION3 } from "@patterkit/model";
|
|
2731
3026
|
import { manifestProblems, parseManifest, renameStages, targetOf } from "@wildwinter/toolkit/extensions";
|
|
2732
3027
|
var CONFLICT_MARKERS = /^(<{7}|={7}|>{7})( |$)/m;
|
|
2733
|
-
var manifestPath = (projectDir, id) =>
|
|
3028
|
+
var manifestPath = (projectDir, id) => join8(projectDir, EXTENSIONS_DIR3, `${id}${MANIFEST_EXTENSION3}`);
|
|
2734
3029
|
function readProjectManifest(projectDir, id) {
|
|
2735
3030
|
if (!/^[a-z][a-z0-9-]*$/.test(id)) return void 0;
|
|
2736
3031
|
let text;
|
|
@@ -2761,7 +3056,7 @@ function planExtensionUpgrades(loaded) {
|
|
|
2761
3056
|
};
|
|
2762
3057
|
const taken = loaded.project.extensions ?? {};
|
|
2763
3058
|
const moved = {};
|
|
2764
|
-
const rel = (path) => relative5(loaded.root, path).split(
|
|
3059
|
+
const rel = (path) => relative5(loaded.root, path).split(sep4).join("/");
|
|
2765
3060
|
for (const [id, pin] of Object.entries(taken)) {
|
|
2766
3061
|
const file = loaded.manifests.find((m) => m.id === id);
|
|
2767
3062
|
const manifest = file !== void 0 ? soundOf(file.manifest) : void 0;
|
|
@@ -2790,7 +3085,7 @@ function planExtensionUpgrades(loaded) {
|
|
|
2790
3085
|
}
|
|
2791
3086
|
if (renamed > 0) {
|
|
2792
3087
|
const path = loaded.extensionShardFiles[i];
|
|
2793
|
-
writes.set(path,
|
|
3088
|
+
writes.set(path, canonicalStringify6({ ...shard, entries }));
|
|
2794
3089
|
const what = `renamed ${renamed} stage value${renamed === 1 ? "" : "s"} for "${id}"`;
|
|
2795
3090
|
report.push(` ${what} in ${rel(path)}`);
|
|
2796
3091
|
note(path, what);
|
|
@@ -2799,7 +3094,7 @@ function planExtensionUpgrades(loaded) {
|
|
|
2799
3094
|
}
|
|
2800
3095
|
if (Object.keys(moved).length > 0) {
|
|
2801
3096
|
const project = parseSource5(readFileSync7(loaded.projectFile, "utf8"));
|
|
2802
|
-
writes.set(loaded.projectFile,
|
|
3097
|
+
writes.set(loaded.projectFile, canonicalStringify6({ ...project, extensions: { ...taken, ...moved } }));
|
|
2803
3098
|
}
|
|
2804
3099
|
return { writes, report, notes };
|
|
2805
3100
|
}
|
|
@@ -2881,7 +3176,7 @@ function expand(path) {
|
|
|
2881
3176
|
} catch {
|
|
2882
3177
|
}
|
|
2883
3178
|
if (!dir) return [path];
|
|
2884
|
-
const root =
|
|
3179
|
+
const root = dirname7(findProjectFile(path));
|
|
2885
3180
|
return SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).sort();
|
|
2886
3181
|
}
|
|
2887
3182
|
function upgradesFor(paths) {
|
|
@@ -2912,7 +3207,7 @@ function runFormat(paths) {
|
|
|
2912
3207
|
const notes = upgrades.notes.get(resolve5(file));
|
|
2913
3208
|
let canonical;
|
|
2914
3209
|
try {
|
|
2915
|
-
canonical = upgrades.content.get(resolve5(file)) ??
|
|
3210
|
+
canonical = upgrades.content.get(resolve5(file)) ?? canonicalStringify7(parseSource6(original));
|
|
2916
3211
|
} catch (e) {
|
|
2917
3212
|
throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
2918
3213
|
}
|
|
@@ -2922,7 +3217,7 @@ function runFormat(paths) {
|
|
|
2922
3217
|
}
|
|
2923
3218
|
|
|
2924
3219
|
// src/report.ts
|
|
2925
|
-
import { DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2, RERECORD_STATUS as RERECORD_STATUS2 } from "@patterkit/model";
|
|
3220
|
+
import { DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2, RERECORD_STATUS as RERECORD_STATUS2, textIsAction as textIsAction2 } from "@patterkit/model";
|
|
2926
3221
|
var DEFAULT_WORDS_PER_LINE = 6;
|
|
2927
3222
|
var UNATTRIBUTED = "(unattributed)";
|
|
2928
3223
|
function allocateLargestRemainder(total, weights) {
|
|
@@ -2982,6 +3277,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
2982
3277
|
const rungIdxOf = (id) => writingIndex.get(wsOf(id)) ?? 0;
|
|
2983
3278
|
for (const scene of loaded.scenes) {
|
|
2984
3279
|
const sceneCut = cutSet.has(scene.id);
|
|
3280
|
+
const sceneAction = textIsAction2(project, scene);
|
|
2985
3281
|
const units = [];
|
|
2986
3282
|
const underCut = /* @__PURE__ */ new Set();
|
|
2987
3283
|
let choices = 0;
|
|
@@ -3011,7 +3307,8 @@ function runReport(loaded, recordingOverride) {
|
|
|
3011
3307
|
id: beat.id,
|
|
3012
3308
|
voiced: beat.kind === "line",
|
|
3013
3309
|
words: wordsOf(beat.id),
|
|
3014
|
-
character: beat.kind === "line" ? beat.character : void 0
|
|
3310
|
+
character: beat.kind === "line" ? beat.character : void 0,
|
|
3311
|
+
...sceneAction && beat.kind === "text" ? { action: true } : {}
|
|
3015
3312
|
});
|
|
3016
3313
|
}
|
|
3017
3314
|
}
|
|
@@ -3020,7 +3317,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
3020
3317
|
}
|
|
3021
3318
|
const isCut = (id) => cutSet.has(id) || underCut.has(id);
|
|
3022
3319
|
for (const u of units) if (sceneCut || isCut(u.id)) {
|
|
3023
|
-
cut.writtenLines++;
|
|
3320
|
+
if (!u.action) cut.writtenLines++;
|
|
3024
3321
|
if (u.voiced) cut.voicedLines++;
|
|
3025
3322
|
}
|
|
3026
3323
|
if (sceneCut) {
|
|
@@ -3028,7 +3325,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
3028
3325
|
continue;
|
|
3029
3326
|
}
|
|
3030
3327
|
const live = units.filter((u) => !isCut(u.id));
|
|
3031
|
-
for (const u of live) if (sourceStrings2[u.id]) localeIds.push(u.id);
|
|
3328
|
+
for (const u of live) if (sourceStrings2[u.id] && !u.action) localeIds.push(u.id);
|
|
3032
3329
|
const statusIdx = live.length ? Math.min(...live.map((u) => rungIdxOf(u.id))) : void 0;
|
|
3033
3330
|
const status = statusIdx === void 0 ? void 0 : writingLadder[statusIdx];
|
|
3034
3331
|
const highestIdx = live.length ? Math.max(...live.map((u) => rungIdxOf(u.id))) : void 0;
|
|
@@ -3057,6 +3354,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
3057
3354
|
choices,
|
|
3058
3355
|
voiced: emptyVoiced(writingLadder, recordingLadder),
|
|
3059
3356
|
written: emptyWritten(writingLadder),
|
|
3357
|
+
action: emptyWritten(writingLadder),
|
|
3060
3358
|
estimate: estimateLines,
|
|
3061
3359
|
writtenDone: 0,
|
|
3062
3360
|
writtenRemaining: estimateLines,
|
|
@@ -3069,8 +3367,15 @@ function runReport(loaded, recordingOverride) {
|
|
|
3069
3367
|
}
|
|
3070
3368
|
const voiced = emptyVoiced(writingLadder, recordingLadder);
|
|
3071
3369
|
const written = emptyWritten(writingLadder);
|
|
3370
|
+
const action = emptyWritten(writingLadder);
|
|
3072
3371
|
for (const u of live) {
|
|
3073
3372
|
const ws = wsOf(u.id);
|
|
3373
|
+
if (u.action) {
|
|
3374
|
+
action.count++;
|
|
3375
|
+
action.words += u.words;
|
|
3376
|
+
action.byWriting[ws] = (action.byWriting[ws] ?? 0) + 1;
|
|
3377
|
+
continue;
|
|
3378
|
+
}
|
|
3074
3379
|
written.count++;
|
|
3075
3380
|
written.words += u.words;
|
|
3076
3381
|
written.byWriting[ws] = (written.byWriting[ws] ?? 0) + 1;
|
|
@@ -3102,6 +3407,7 @@ function runReport(loaded, recordingOverride) {
|
|
|
3102
3407
|
choices,
|
|
3103
3408
|
voiced,
|
|
3104
3409
|
written,
|
|
3410
|
+
action,
|
|
3105
3411
|
writtenDone: written.count - writtenStub,
|
|
3106
3412
|
writtenRemaining: writtenStub,
|
|
3107
3413
|
voicedDone: voiced.count - voicedStub,
|
|
@@ -3168,6 +3474,7 @@ function computeTotals(scenes, writingLadder, recordingLadder) {
|
|
|
3168
3474
|
const t = {
|
|
3169
3475
|
voiced: emptyVoiced(writingLadder, recordingLadder),
|
|
3170
3476
|
written: emptyWritten(writingLadder),
|
|
3477
|
+
action: emptyWritten(writingLadder),
|
|
3171
3478
|
choices: 0,
|
|
3172
3479
|
writtenDone: 0,
|
|
3173
3480
|
writtenRemaining: 0,
|
|
@@ -3189,6 +3496,9 @@ function computeTotals(scenes, writingLadder, recordingLadder) {
|
|
|
3189
3496
|
t.written.count += s.written.count;
|
|
3190
3497
|
t.written.words += s.written.words;
|
|
3191
3498
|
add(t.written.byWriting, s.written.byWriting);
|
|
3499
|
+
t.action.count += s.action.count;
|
|
3500
|
+
t.action.words += s.action.words;
|
|
3501
|
+
add(t.action.byWriting, s.action.byWriting);
|
|
3192
3502
|
t.choices += s.choices;
|
|
3193
3503
|
t.writtenDone += s.writtenDone;
|
|
3194
3504
|
t.writtenRemaining += s.writtenRemaining;
|
|
@@ -3204,6 +3514,7 @@ function renderReportText(data) {
|
|
|
3204
3514
|
const t = data.totals;
|
|
3205
3515
|
out.push(`${data.project.name} - ${data.scenes.length} scene(s)`);
|
|
3206
3516
|
out.push(`written lines: ${t.writtenDone} done / ${t.writtenRemaining} to write -> ${t.projectedWritten} projected (${t.written.words} words)`);
|
|
3517
|
+
if (t.action.count > 0) out.push(`action lines: ${t.action.count}, not shown to the player (${t.action.words} words)`);
|
|
3207
3518
|
if (data.voiced) out.push(`voiced lines: ${t.voicedDone} done / ${t.voicedRemaining} to write -> ${t.projectedVoiced} projected (${t.voiced.words} words)`);
|
|
3208
3519
|
out.push(`writing status: ${Object.entries(t.written.byWriting).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
3209
3520
|
out.push(`scene status: ${Object.entries(data.scenesByStatus).map(([k, v]) => `${k} ${v}`).join(", ")}`);
|
|
@@ -3228,6 +3539,7 @@ async function runReportXlsx(data) {
|
|
|
3228
3539
|
const v = data.voiced;
|
|
3229
3540
|
const rec = data.recordingTracked;
|
|
3230
3541
|
const scenes = wb.addWorksheet("Scenes");
|
|
3542
|
+
const action = data.totals.action.count > 0;
|
|
3231
3543
|
scenes.columns = [
|
|
3232
3544
|
{ header: "Scene", key: "name", width: 24 },
|
|
3233
3545
|
{ header: "Status", key: "status", width: 10 },
|
|
@@ -3235,6 +3547,8 @@ async function runReportXlsx(data) {
|
|
|
3235
3547
|
{ header: "Written", key: "wCount", width: 8 },
|
|
3236
3548
|
{ header: "Words", key: "words", width: 9 },
|
|
3237
3549
|
{ header: "Choices", key: "choices", width: 8 },
|
|
3550
|
+
// Action (a dialogue scene's text the project doesn't localise): beside the written lines, never in them.
|
|
3551
|
+
...action ? [{ header: "Action", key: "aCount", width: 8 }, { header: "Action words", key: "aWords", width: 12 }] : [],
|
|
3238
3552
|
...data.writingLadder.map((s) => ({ header: s, key: `w:${s}`, width: 10 })),
|
|
3239
3553
|
...v ? [{ header: "Ready record", key: "rr", width: 12 }, { header: "Ready ship", key: "rs", width: 11 }] : [],
|
|
3240
3554
|
...rec ? data.recordingLadder.map((s) => ({ header: `rec ${s}`, key: `r:${s}`, width: 10 })) : [],
|
|
@@ -3249,6 +3563,8 @@ async function runReportXlsx(data) {
|
|
|
3249
3563
|
wCount: s.written.count,
|
|
3250
3564
|
words: s.written.words,
|
|
3251
3565
|
choices: s.choices,
|
|
3566
|
+
aCount: s.action.count,
|
|
3567
|
+
aWords: s.action.words,
|
|
3252
3568
|
...Object.fromEntries(data.writingLadder.map((k) => [`w:${k}`, s.written.byWriting[k] ?? 0])),
|
|
3253
3569
|
rr: s.voiced.readyToRecord,
|
|
3254
3570
|
rs: s.voiced.readyToShip,
|
|
@@ -3311,242 +3627,6 @@ async function runReportXlsx(data) {
|
|
|
3311
3627
|
return Buffer.from(await wb.xlsx.writeBuffer());
|
|
3312
3628
|
}
|
|
3313
3629
|
|
|
3314
|
-
// src/localisation.ts
|
|
3315
|
-
import { basename as basename5, join as join8, sep as sep4 } from "path";
|
|
3316
|
-
import { canonicalStringify as canonicalStringify7 } from "@patterkit/core";
|
|
3317
|
-
import {
|
|
3318
|
-
walkNodes as walkNodes2,
|
|
3319
|
-
castStringKey,
|
|
3320
|
-
qualifierStringKey,
|
|
3321
|
-
speakerLabelKey,
|
|
3322
|
-
usedQualifiers,
|
|
3323
|
-
usedSpeakerPairs,
|
|
3324
|
-
projectQualifiers,
|
|
3325
|
-
SPEAKER_PATTERN_KEY,
|
|
3326
|
-
DEFAULT_SPEAKER_PATTERN,
|
|
3327
|
-
PROJECT_LOCALE_SCENE as PROJECT_LOCALE_SCENE2,
|
|
3328
|
-
projectLayout as projectLayout3,
|
|
3329
|
-
STRINGS_SCHEMA,
|
|
3330
|
-
AUTHORING_SCHEMA
|
|
3331
|
-
} from "@patterkit/model";
|
|
3332
|
-
|
|
3333
|
-
// src/documentation.ts
|
|
3334
|
-
import { DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
|
|
3335
|
-
function classesForChannel(classes, channel) {
|
|
3336
|
-
const out = /* @__PURE__ */ new Set();
|
|
3337
|
-
for (const c of classes) {
|
|
3338
|
-
if (c.deliver === "*" || Array.isArray(c.deliver) && c.deliver.includes(channel)) out.add(c.name);
|
|
3339
|
-
}
|
|
3340
|
-
return out;
|
|
3341
|
-
}
|
|
3342
|
-
function resolveDocumentation(loaded, channel) {
|
|
3343
|
-
const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
|
|
3344
|
-
const allowed = classesForChannel(classes, channel);
|
|
3345
|
-
const docsOf = mergeAuthoring2(loaded).documentation;
|
|
3346
|
-
const own = (id) => (docsOf.get(id) ?? []).filter((l) => l.type !== void 0 && allowed.has(l.type));
|
|
3347
|
-
const out = /* @__PURE__ */ new Map();
|
|
3348
|
-
const visit = (id, inherited) => {
|
|
3349
|
-
const mine = own(id);
|
|
3350
|
-
if (inherited.length === 0 && mine.length === 0) return inherited;
|
|
3351
|
-
const resolved = [...inherited, ...mine];
|
|
3352
|
-
out.set(id, resolved);
|
|
3353
|
-
return resolved;
|
|
3354
|
-
};
|
|
3355
|
-
const walkNode = (node, inherited) => {
|
|
3356
|
-
const passed = visit(node.id, inherited);
|
|
3357
|
-
if (node.type === "group") {
|
|
3358
|
-
if (node.prompt) visit(node.prompt.id, passed);
|
|
3359
|
-
for (const child2 of node.children ?? []) walkNode(child2, passed);
|
|
3360
|
-
} else {
|
|
3361
|
-
for (const beat of node.beats ?? []) visit(beat.id, passed);
|
|
3362
|
-
}
|
|
3363
|
-
};
|
|
3364
|
-
for (const scene of loaded.scenes) {
|
|
3365
|
-
const sceneInh = visit(scene.id, []);
|
|
3366
|
-
for (const block of scene.blocks) {
|
|
3367
|
-
const blockInh = visit(block.id, sceneInh);
|
|
3368
|
-
for (const child2 of block.children ?? []) walkNode(child2, blockInh);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
return out;
|
|
3372
|
-
}
|
|
3373
|
-
|
|
3374
|
-
// src/localisation.ts
|
|
3375
|
-
function projectStringSources(project, sourceTable) {
|
|
3376
|
-
const out = {};
|
|
3377
|
-
const put = (id, authored) => {
|
|
3378
|
-
if (authored) out[id] = sourceTable[id] ?? authored;
|
|
3379
|
-
};
|
|
3380
|
-
for (const c of project.cast ?? []) {
|
|
3381
|
-
put(castStringKey(c.name), c.displayName);
|
|
3382
|
-
for (const [q, label] of Object.entries(c.qualifierLabels ?? {})) put(speakerLabelKey(c.name, q), label);
|
|
3383
|
-
}
|
|
3384
|
-
for (const q of projectQualifiers(project)) put(qualifierStringKey(q.gameId), q.name);
|
|
3385
|
-
put(SPEAKER_PATTERN_KEY, project.speakerPattern ?? DEFAULT_SPEAKER_PATTERN);
|
|
3386
|
-
return out;
|
|
3387
|
-
}
|
|
3388
|
-
function extractLoc(loaded, opts = {}) {
|
|
3389
|
-
const defaultLocale = loaded.project.locales.default;
|
|
3390
|
-
const isTemplate = opts.locale === void 0 || opts.locale === defaultLocale;
|
|
3391
|
-
const targetLocale = isTemplate ? void 0 : opts.locale;
|
|
3392
|
-
const source = tableFor(loaded, defaultLocale);
|
|
3393
|
-
const target = targetLocale ? tableFor(loaded, targetLocale) : {};
|
|
3394
|
-
const edits = mergeAuthoring2(loaded).edits;
|
|
3395
|
-
const projectSources = projectStringSources(loaded.project, source);
|
|
3396
|
-
const docs = resolveDocumentation(loaded, "loc");
|
|
3397
|
-
const commentsOf = (id) => (docs.get(id) ?? []).map((d) => d.text);
|
|
3398
|
-
const staleFor = (id, translation) => {
|
|
3399
|
-
if (isTemplate || !translation) return false;
|
|
3400
|
-
const e = edits.get(id);
|
|
3401
|
-
const localised = e?.localisedAt?.[targetLocale];
|
|
3402
|
-
return !!(e?.modifiedAt && localised && e.modifiedAt > localised);
|
|
3403
|
-
};
|
|
3404
|
-
const genderOf = /* @__PURE__ */ new Map();
|
|
3405
|
-
for (const c of loaded.project.cast ?? []) if (c.gender) genderOf.set(c.name, c.gender);
|
|
3406
|
-
const withGender = (context) => {
|
|
3407
|
-
const g = context?.character ? genderOf.get(context.character) : void 0;
|
|
3408
|
-
return g ? { ...context, gender: g } : context;
|
|
3409
|
-
};
|
|
3410
|
-
const qualifierName = new Map(projectQualifiers(loaded.project).map((q) => [q.gameId, q.name]));
|
|
3411
|
-
const lineContext = (b) => {
|
|
3412
|
-
const q = b.kind === "line" && b.qualifier ? qualifierName.get(b.qualifier) ?? b.qualifier : void 0;
|
|
3413
|
-
return { character: b.kind === "line" ? b.character : void 0, kind: b.kind, ...q ? { qualifier: q } : {} };
|
|
3414
|
-
};
|
|
3415
|
-
const entries = [];
|
|
3416
|
-
const push = (id, scene, context) => {
|
|
3417
|
-
const src = source[id];
|
|
3418
|
-
if (src === void 0) return;
|
|
3419
|
-
const translation = isTemplate ? "" : target[id] ?? "";
|
|
3420
|
-
entries.push({ id, scene, source: src, translation, comments: commentsOf(id), context: withGender(context), stale: staleFor(id, translation) });
|
|
3421
|
-
};
|
|
3422
|
-
for (const scene of loaded.scenes) {
|
|
3423
|
-
for (const block of scene.blocks) {
|
|
3424
|
-
walkNodes2(block.children, (node) => {
|
|
3425
|
-
if (node.type === "group") {
|
|
3426
|
-
const p = node.prompt;
|
|
3427
|
-
if (p) push(p.id, scene.id, lineContext(p));
|
|
3428
|
-
return;
|
|
3429
|
-
}
|
|
3430
|
-
for (const beat of node.beats ?? []) {
|
|
3431
|
-
if (beat.kind !== "gameEvent") push(beat.id, scene.id, lineContext(beat));
|
|
3432
|
-
}
|
|
3433
|
-
});
|
|
3434
|
-
}
|
|
3435
|
-
}
|
|
3436
|
-
for (const c of loaded.project.cast ?? []) {
|
|
3437
|
-
if (!c.displayName) continue;
|
|
3438
|
-
const id = castStringKey(c.name);
|
|
3439
|
-
const src = projectSources[id];
|
|
3440
|
-
const translation = isTemplate ? "" : target[id] ?? "";
|
|
3441
|
-
entries.push({ id, scene: PROJECT_LOCALE_SCENE2, source: src, translation, comments: commentsOf(id), context: withGender({ character: c.name }), stale: staleFor(id, translation) });
|
|
3442
|
-
}
|
|
3443
|
-
for (const q of usedQualifiers(loaded.project, loaded.scenes)) {
|
|
3444
|
-
const id = qualifierStringKey(q.gameId);
|
|
3445
|
-
const src = projectSources[id] ?? q.name;
|
|
3446
|
-
const translation = isTemplate ? "" : target[id] ?? "";
|
|
3447
|
-
entries.push({ id, scene: PROJECT_LOCALE_SCENE2, source: src, translation, comments: commentsOf(id), context: { kind: "qualifier" }, stale: staleFor(id, translation) });
|
|
3448
|
-
}
|
|
3449
|
-
const pairs = usedSpeakerPairs(loaded.scenes);
|
|
3450
|
-
if (pairs.length > 0) {
|
|
3451
|
-
const id = SPEAKER_PATTERN_KEY;
|
|
3452
|
-
const src = projectSources[id];
|
|
3453
|
-
const translation = isTemplate ? "" : target[id] ?? "";
|
|
3454
|
-
entries.push({ id, scene: PROJECT_LOCALE_SCENE2, source: src, translation, comments: [SPEAKER_PATTERN_NOTE, ...commentsOf(id)], context: { kind: "speaker pattern" }, stale: staleFor(id, translation) });
|
|
3455
|
-
const labelsOf = new Map((loaded.project.cast ?? []).map((c) => [c.name, c.qualifierLabels]));
|
|
3456
|
-
for (const p of pairs) {
|
|
3457
|
-
const label = labelsOf.get(p.character)?.[p.qualifier];
|
|
3458
|
-
if (!label) continue;
|
|
3459
|
-
const pid = speakerLabelKey(p.character, p.qualifier);
|
|
3460
|
-
const psrc = projectSources[pid] ?? label;
|
|
3461
|
-
const ptranslation = isTemplate ? "" : target[pid] ?? "";
|
|
3462
|
-
const qualifier = qualifierName.get(p.qualifier) ?? p.qualifier;
|
|
3463
|
-
entries.push({ id: pid, scene: PROJECT_LOCALE_SCENE2, source: psrc, translation: ptranslation, comments: commentsOf(pid), context: withGender({ character: p.character, kind: "speaker", qualifier }), stale: staleFor(pid, ptranslation) });
|
|
3464
|
-
}
|
|
3465
|
-
}
|
|
3466
|
-
return { project: loaded.project.project.id, defaultLocale, locale: targetLocale, entries };
|
|
3467
|
-
}
|
|
3468
|
-
var SPEAKER_PATTERN_NOTE = "{name} and {qualifier} are filled in with the speaker's name and the qualifier, so keep both as they are. You can put them in any order.";
|
|
3469
|
-
var layoutOf = (loaded) => projectLayout3(loaded.project);
|
|
3470
|
-
function existingLocPath(loaded, scene, locale) {
|
|
3471
|
-
for (let i = 0; i < loaded.locales.length; i++) {
|
|
3472
|
-
const l = loaded.locales[i];
|
|
3473
|
-
if (l.scene === scene && l.locale === locale) return loaded.localeFiles[i] ?? null;
|
|
3474
|
-
}
|
|
3475
|
-
return null;
|
|
3476
|
-
}
|
|
3477
|
-
function targetLocPath(loaded, scene, locale, defaultLocale) {
|
|
3478
|
-
const existing = existingLocPath(loaded, scene, locale);
|
|
3479
|
-
if (existing) return existing;
|
|
3480
|
-
const fromDefault = existingLocPath(loaded, scene, defaultLocale);
|
|
3481
|
-
if (fromDefault) {
|
|
3482
|
-
const parts = fromDefault.split(sep4);
|
|
3483
|
-
for (let i = parts.length - 2; i >= 0; i--) {
|
|
3484
|
-
if (parts[i] === defaultLocale) {
|
|
3485
|
-
parts[i] = locale;
|
|
3486
|
-
return parts.join(sep4);
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
}
|
|
3490
|
-
const stem = scene === PROJECT_LOCALE_SCENE2 ? "_project" : scene;
|
|
3491
|
-
return join8(loaded.root, layoutOf(loaded).strings, locale, `${stem}.patterloc`);
|
|
3492
|
-
}
|
|
3493
|
-
function authoringPath(loaded, scene) {
|
|
3494
|
-
const dir = join8(loaded.root, layoutOf(loaded).authoring);
|
|
3495
|
-
if (scene === PROJECT_LOCALE_SCENE2) return join8(dir, "_project.patterx");
|
|
3496
|
-
const flow = loaded.sceneFiles[scene];
|
|
3497
|
-
const stem = flow ? basename5(flow).replace(/\.patterflow$/, "") : scene;
|
|
3498
|
-
return join8(dir, `${stem}.patterx`);
|
|
3499
|
-
}
|
|
3500
|
-
var findFile = (files, items, path) => {
|
|
3501
|
-
const i = files.indexOf(path);
|
|
3502
|
-
return i >= 0 ? items[i] : void 0;
|
|
3503
|
-
};
|
|
3504
|
-
function applyLoc(loaded, catalog, opts = {}) {
|
|
3505
|
-
const locale = catalog.locale;
|
|
3506
|
-
if (!locale || locale === loaded.project.locales.default) {
|
|
3507
|
-
return { writes: [], stats: { updated: 0, files: 0 } };
|
|
3508
|
-
}
|
|
3509
|
-
if (!loaded.project.locales.all.includes(locale)) {
|
|
3510
|
-
throw new Error(`'${locale}' is not one of the project's languages (${loaded.project.locales.all.join(", ")}); add it to the project first`);
|
|
3511
|
-
}
|
|
3512
|
-
const known = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id), PROJECT_LOCALE_SCENE2]);
|
|
3513
|
-
const unknown = [...new Set(catalog.entries.filter((e) => e.translation.trim() && !known.has(e.scene)).map((e) => e.scene))];
|
|
3514
|
-
if (unknown.length) {
|
|
3515
|
-
throw new Error(`the file names scene(s) this project does not have: ${unknown.map((u) => `'${u}'`).join(", ")}; nothing was imported`);
|
|
3516
|
-
}
|
|
3517
|
-
const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3518
|
-
const bySceneText = /* @__PURE__ */ new Map();
|
|
3519
|
-
const bySceneFresh = /* @__PURE__ */ new Map();
|
|
3520
|
-
for (const e of catalog.entries) {
|
|
3521
|
-
if (!e.translation.trim()) continue;
|
|
3522
|
-
(bySceneText.get(e.scene) ?? bySceneText.set(e.scene, /* @__PURE__ */ new Map()).get(e.scene)).set(e.id, e.translation);
|
|
3523
|
-
if (!e.stale) (bySceneFresh.get(e.scene) ?? bySceneFresh.set(e.scene, /* @__PURE__ */ new Set()).get(e.scene)).add(e.id);
|
|
3524
|
-
}
|
|
3525
|
-
const writes = [];
|
|
3526
|
-
let updated = 0;
|
|
3527
|
-
for (const [scene, strings] of bySceneText) {
|
|
3528
|
-
const path = targetLocPath(loaded, scene, locale, loaded.project.locales.default);
|
|
3529
|
-
const existing = findFile(loaded.localeFiles, loaded.locales, path);
|
|
3530
|
-
const merged = { ...existing?.strings ?? {} };
|
|
3531
|
-
for (const [id, text] of strings) {
|
|
3532
|
-
if (merged[id] !== text) updated++;
|
|
3533
|
-
merged[id] = text;
|
|
3534
|
-
}
|
|
3535
|
-
const file = { schema: existing?.schema ?? STRINGS_SCHEMA, scene, locale, strings: merged };
|
|
3536
|
-
writes.push({ path, content: canonicalStringify7(file) });
|
|
3537
|
-
const fresh = bySceneFresh.get(scene);
|
|
3538
|
-
if (!fresh?.size) continue;
|
|
3539
|
-
const aPath = authoringPath(loaded, scene);
|
|
3540
|
-
const aExisting = findFile(loaded.authoringFiles, loaded.authoring, aPath);
|
|
3541
|
-
const authoring = aExisting ? { ...aExisting, edits: { ...aExisting.edits } } : { schema: AUTHORING_SCHEMA };
|
|
3542
|
-
const editsMap = { ...authoring.edits ?? {} };
|
|
3543
|
-
for (const id of fresh) editsMap[id] = { ...editsMap[id], localisedAt: { ...editsMap[id]?.localisedAt, [locale]: now } };
|
|
3544
|
-
authoring.edits = editsMap;
|
|
3545
|
-
writes.push({ path: aPath, content: canonicalStringify7(authoring) });
|
|
3546
|
-
}
|
|
3547
|
-
return { writes, stats: { updated, files: bySceneText.size } };
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
3630
|
// src/loc-format.ts
|
|
3551
3631
|
function checkCatalogField(format, field, value) {
|
|
3552
3632
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -3914,7 +3994,7 @@ var TOKENS = {
|
|
|
3914
3994
|
inkRead: "3a352d",
|
|
3915
3995
|
// dialogue body (ink ~84% + muted)
|
|
3916
3996
|
inkSoft: "5c554b",
|
|
3917
|
-
//
|
|
3997
|
+
// text (ink ~68% + muted) - a step softer than dialogue
|
|
3918
3998
|
muted: "8c8479",
|
|
3919
3999
|
// labels, quiet tags
|
|
3920
4000
|
accent: "8a5a34",
|
|
@@ -6724,6 +6804,8 @@ export {
|
|
|
6724
6804
|
SHARD_EXTENSIONS,
|
|
6725
6805
|
UnsafeEntryError,
|
|
6726
6806
|
UnsupportedMergeError,
|
|
6807
|
+
actionTextChanges,
|
|
6808
|
+
actionTextIds,
|
|
6727
6809
|
applyLoc,
|
|
6728
6810
|
applySceneOrder,
|
|
6729
6811
|
applySuggestionDecisions,
|
|
@@ -6745,6 +6827,7 @@ export {
|
|
|
6745
6827
|
currentBundlePosture,
|
|
6746
6828
|
defaultBundlePath,
|
|
6747
6829
|
defaultGameScopesDir,
|
|
6830
|
+
describeLocExport,
|
|
6748
6831
|
detectMergeType,
|
|
6749
6832
|
discoverGameScopes,
|
|
6750
6833
|
drivableScopes,
|
|
@@ -6835,6 +6918,7 @@ export {
|
|
|
6835
6918
|
scanAudio,
|
|
6836
6919
|
scanAudioStatus,
|
|
6837
6920
|
sceneIdForShard,
|
|
6921
|
+
sceneTextIds,
|
|
6838
6922
|
scriptFontLicencePath,
|
|
6839
6923
|
scriptFontLicences,
|
|
6840
6924
|
scriptToDocx,
|