@patterkit/ops 0.20.1 → 0.21.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.js CHANGED
@@ -546,7 +546,7 @@ 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 basename4, dirname as dirname5, isAbsolute as isAbsolute2, join as join5, relative as relative3 } from "path";
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";
@@ -1083,7 +1083,7 @@ function besideBundlePath(loaded, ext) {
1083
1083
  }
1084
1084
 
1085
1085
  // src/validate.ts
1086
- import { EXTENSION_SHARD as EXTENSION_SHARD3, PROJECT_LOCALE_SCENE, projectLayout as projectLayout2 } from "@patterkit/model";
1086
+ import { EXTENSION_SHARD as EXTENSION_SHARD3, PROJECT_LOCALE_SCENE as PROJECT_LOCALE_SCENE2, projectLayout as projectLayout3 } from "@patterkit/model";
1087
1087
 
1088
1088
  // src/reachability.ts
1089
1089
  import { disjuncts, latchOf, scopedRef, terms } from "@wildwinter/expr";
@@ -1244,22 +1244,416 @@ function reachabilityIssues(loaded, compiled) {
1244
1244
  return issues;
1245
1245
  }
1246
1246
 
1247
+ // src/localisation.ts
1248
+ import { basename as basename3, join as join3, sep } from "path";
1249
+ import { canonicalStringify as canonicalStringify3 } from "@patterkit/core";
1250
+ import {
1251
+ walkNodes,
1252
+ textIsAction,
1253
+ castStringKey,
1254
+ qualifierStringKey,
1255
+ speakerLabelKey,
1256
+ usedQualifiers,
1257
+ usedSpeakerPairs,
1258
+ projectQualifiers,
1259
+ SPEAKER_PATTERN_KEY,
1260
+ DEFAULT_SPEAKER_PATTERN,
1261
+ PROJECT_LOCALE_SCENE,
1262
+ projectLayout as projectLayout2,
1263
+ STRINGS_SCHEMA,
1264
+ AUTHORING_SCHEMA
1265
+ } from "@patterkit/model";
1266
+
1267
+ // src/loaded-helpers.ts
1268
+ import { RERECORD_STATUS, effectiveGameId } from "@patterkit/model";
1269
+ function effectiveRecording(id, base, rerecord, lowest) {
1270
+ return rerecord.has(id) ? RERECORD_STATUS : base.get(id) ?? lowest;
1271
+ }
1272
+ function tableFor(loaded, locale) {
1273
+ const table = {};
1274
+ for (const l of loaded.locales) if (l.locale === locale) Object.assign(table, l.strings);
1275
+ return table;
1276
+ }
1277
+ function sourceStrings(loaded) {
1278
+ return tableFor(loaded, loaded.project.locales.default);
1279
+ }
1280
+ function resolveStart(loaded, override) {
1281
+ if (override?.scene) return { scene: override.scene, block: override.block };
1282
+ if (override?.block) return findBlockAnywhere(loaded, override.block);
1283
+ if (loaded.project.start) return { scene: loaded.project.start.scene, block: loaded.project.start.block };
1284
+ return {};
1285
+ }
1286
+ function findBlockAnywhere(loaded, ref) {
1287
+ const matches = [];
1288
+ for (const scene of loaded.scenes) {
1289
+ for (const block of scene.blocks) {
1290
+ if (block.id === ref) return { scene: scene.id, block: block.id };
1291
+ if (block.name === ref || effectiveGameId(block) === ref) matches.push({ scene, block });
1292
+ }
1293
+ }
1294
+ if (matches.length === 0) throw new Error(`no scene has a block "${ref}"`);
1295
+ if (matches.length > 1) {
1296
+ const scenes = [...new Set(matches.map((m) => `"${m.scene.name}" (${m.scene.id})`))].join(", ");
1297
+ throw new Error(`more than one block is called "${ref}" (in ${scenes}): name the scene as well`);
1298
+ }
1299
+ return { scene: matches[0].scene.id, block: matches[0].block.id };
1300
+ }
1301
+ function stringsByLocale(loaded) {
1302
+ const byLocale = /* @__PURE__ */ new Map();
1303
+ for (const l of loaded.locales) {
1304
+ const table = byLocale.get(l.locale) ?? {};
1305
+ Object.assign(table, l.strings);
1306
+ byLocale.set(l.locale, table);
1307
+ }
1308
+ return byLocale;
1309
+ }
1310
+ function mergeAuthoring2(loaded) {
1311
+ const writing = /* @__PURE__ */ new Map();
1312
+ const recording = /* @__PURE__ */ new Map();
1313
+ const cut = /* @__PURE__ */ new Set();
1314
+ const rerecord = /* @__PURE__ */ new Set();
1315
+ const documentation = /* @__PURE__ */ new Map();
1316
+ const edits = /* @__PURE__ */ new Map();
1317
+ for (const a of loaded.authoring) {
1318
+ for (const [id, v] of Object.entries(a.writing ?? {})) writing.set(id, v);
1319
+ for (const [id, v] of Object.entries(a.recording ?? {})) recording.set(id, v);
1320
+ for (const [id, v] of Object.entries(a.cut ?? {})) if (v) cut.add(id);
1321
+ for (const [id, v] of Object.entries(a.rerecord ?? {})) if (v) rerecord.add(id);
1322
+ for (const [id, lines] of Object.entries(a.documentation ?? {})) {
1323
+ const cur = documentation.get(id);
1324
+ if (cur) cur.push(...lines);
1325
+ else documentation.set(id, [...lines]);
1326
+ }
1327
+ for (const [id, v] of Object.entries(a.edits ?? {})) edits.set(id, v);
1328
+ }
1329
+ return { writing, recording, cut, rerecord, documentation, edits };
1330
+ }
1331
+
1332
+ // src/documentation.ts
1333
+ import { DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
1334
+ function classesForChannel(classes, channel) {
1335
+ const out = /* @__PURE__ */ new Set();
1336
+ for (const c of classes) {
1337
+ if (c.deliver === "*" || Array.isArray(c.deliver) && c.deliver.includes(channel)) out.add(c.name);
1338
+ }
1339
+ return out;
1340
+ }
1341
+ function resolveDocumentation(loaded, channel) {
1342
+ const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
1343
+ const allowed = classesForChannel(classes, channel);
1344
+ const docsOf = mergeAuthoring2(loaded).documentation;
1345
+ const own = (id) => (docsOf.get(id) ?? []).filter((l) => l.type !== void 0 && allowed.has(l.type));
1346
+ const out = /* @__PURE__ */ new Map();
1347
+ const visit = (id, inherited) => {
1348
+ const mine = own(id);
1349
+ if (inherited.length === 0 && mine.length === 0) return inherited;
1350
+ const resolved = [...inherited, ...mine];
1351
+ out.set(id, resolved);
1352
+ return resolved;
1353
+ };
1354
+ const walkNode = (node, inherited) => {
1355
+ const passed = visit(node.id, inherited);
1356
+ if (node.type === "group") {
1357
+ if (node.prompt) visit(node.prompt.id, passed);
1358
+ for (const child2 of node.children ?? []) walkNode(child2, passed);
1359
+ } else {
1360
+ for (const beat of node.beats ?? []) visit(beat.id, passed);
1361
+ }
1362
+ };
1363
+ for (const scene of loaded.scenes) {
1364
+ const sceneInh = visit(scene.id, []);
1365
+ for (const block of scene.blocks) {
1366
+ const blockInh = visit(block.id, sceneInh);
1367
+ for (const child2 of block.children ?? []) walkNode(child2, blockInh);
1368
+ }
1369
+ }
1370
+ return out;
1371
+ }
1372
+
1373
+ // src/localisation.ts
1374
+ var SETTING_NAME = `"Localise text lines in dialogue scenes" (Project Settings, Language; \`localiseDialogueText\` in the project file)`;
1375
+ function describeLocExport(loaded, opts = {}) {
1376
+ const source = tableFor(loaded, loaded.project.locales.default);
1377
+ let lines = 0, scenes = 0;
1378
+ for (const ids of actionTextIds(loaded).values()) {
1379
+ const n = [...ids].filter((id) => source[id]).length;
1380
+ if (n) {
1381
+ lines += n;
1382
+ scenes++;
1383
+ }
1384
+ }
1385
+ const out = [];
1386
+ if (lines) {
1387
+ 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"}.`);
1388
+ }
1389
+ return [...out, ...describeActionTextChanges(loaded, actionTextChanges(loaded, opts))];
1390
+ }
1391
+ function describeActionTextChanges(loaded, changes) {
1392
+ const nameOf = new Map(loaded.scenes.map((sc) => [sc.id, sc.name]));
1393
+ const out = [];
1394
+ const groups = /* @__PURE__ */ new Map();
1395
+ for (const c of changes) {
1396
+ const key = `${c.locale}|${c.change}`;
1397
+ (groups.get(key) ?? groups.set(key, []).get(key)).push(c);
1398
+ }
1399
+ for (const [key, list] of groups) {
1400
+ const [locale, change] = key.split("|");
1401
+ const n = `${list.length} text line${list.length === 1 ? "" : "s"}`;
1402
+ 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`);
1403
+ for (const c of list) out.push(` ${c.id} in '${nameOf.get(c.scene) ?? c.scene}'`);
1404
+ }
1405
+ return out;
1406
+ }
1407
+ function sceneTextIds(scene) {
1408
+ const out = [];
1409
+ for (const block of scene.blocks) {
1410
+ walkNodes(block.children, (node) => {
1411
+ if (node.type === "snippet") {
1412
+ for (const beat of node.beats ?? []) if (beat.kind === "text") out.push(beat.id);
1413
+ }
1414
+ });
1415
+ }
1416
+ return out;
1417
+ }
1418
+ function actionTextIds(loaded) {
1419
+ const out = /* @__PURE__ */ new Map();
1420
+ for (const scene of loaded.scenes) if (textIsAction(loaded.project, scene)) out.set(scene.id, new Set(sceneTextIds(scene)));
1421
+ return out;
1422
+ }
1423
+ function actionTextChanges(loaded, opts = {}) {
1424
+ const defaultLocale = loaded.project.locales.default;
1425
+ const locales = opts.locale !== void 0 ? opts.locale === defaultLocale ? [] : [opts.locale] : loaded.project.locales.all.filter((l) => l !== defaultLocale);
1426
+ const source = tableFor(loaded, defaultLocale);
1427
+ const out = [];
1428
+ for (const locale of locales) {
1429
+ for (const scene of loaded.scenes) {
1430
+ const path = existingLocPath(loaded, scene.id, locale);
1431
+ const shard = path ? findFile(loaded.localeFiles, loaded.locales, path) : void 0;
1432
+ if (!shard) continue;
1433
+ const action = textIsAction(loaded.project, scene);
1434
+ for (const id of sceneTextIds(scene)) {
1435
+ if (action && shard.strings[id]) out.push({ id, scene: scene.id, locale, change: "dropped" });
1436
+ else if (!action && shard.textAction && source[id]) out.push({ id, scene: scene.id, locale, change: "added" });
1437
+ }
1438
+ }
1439
+ }
1440
+ return out;
1441
+ }
1442
+ function projectStringSources(project, sourceTable) {
1443
+ const out = {};
1444
+ const put = (id, authored) => {
1445
+ if (authored) out[id] = sourceTable[id] ?? authored;
1446
+ };
1447
+ for (const c of project.cast ?? []) {
1448
+ put(castStringKey(c.name), c.displayName);
1449
+ for (const [q, label] of Object.entries(c.qualifierLabels ?? {})) put(speakerLabelKey(c.name, q), label);
1450
+ }
1451
+ for (const q of projectQualifiers(project)) put(qualifierStringKey(q.gameId), q.name);
1452
+ put(SPEAKER_PATTERN_KEY, project.speakerPattern ?? DEFAULT_SPEAKER_PATTERN);
1453
+ return out;
1454
+ }
1455
+ function extractLoc(loaded, opts = {}) {
1456
+ const defaultLocale = loaded.project.locales.default;
1457
+ const isTemplate = opts.locale === void 0 || opts.locale === defaultLocale;
1458
+ const targetLocale = isTemplate ? void 0 : opts.locale;
1459
+ const source = tableFor(loaded, defaultLocale);
1460
+ const target = targetLocale ? tableFor(loaded, targetLocale) : {};
1461
+ const edits = mergeAuthoring2(loaded).edits;
1462
+ const projectSources = projectStringSources(loaded.project, source);
1463
+ const docs = resolveDocumentation(loaded, "loc");
1464
+ const commentsOf = (id) => (docs.get(id) ?? []).map((d) => d.text);
1465
+ const staleFor = (id, translation) => {
1466
+ if (isTemplate || !translation) return false;
1467
+ const e = edits.get(id);
1468
+ const localised = e?.localisedAt?.[targetLocale];
1469
+ return !!(e?.modifiedAt && localised && e.modifiedAt > localised);
1470
+ };
1471
+ const genderOf = /* @__PURE__ */ new Map();
1472
+ for (const c of loaded.project.cast ?? []) if (c.gender) genderOf.set(c.name, c.gender);
1473
+ const withGender = (context) => {
1474
+ const g = context?.character ? genderOf.get(context.character) : void 0;
1475
+ return g ? { ...context, gender: g } : context;
1476
+ };
1477
+ const qualifierName = new Map(projectQualifiers(loaded.project).map((q) => [q.gameId, q.name]));
1478
+ const lineContext = (b) => {
1479
+ const q = b.kind === "line" && b.qualifier ? qualifierName.get(b.qualifier) ?? b.qualifier : void 0;
1480
+ return { character: b.kind === "line" ? b.character : void 0, kind: b.kind, ...q ? { qualifier: q } : {} };
1481
+ };
1482
+ const entries = [];
1483
+ const push = (id, scene, context) => {
1484
+ const src = source[id];
1485
+ if (src === void 0) return;
1486
+ const translation = isTemplate ? "" : target[id] ?? "";
1487
+ entries.push({ id, scene, source: src, translation, comments: commentsOf(id), context: withGender(context), stale: staleFor(id, translation) });
1488
+ };
1489
+ for (const scene of loaded.scenes) {
1490
+ const action = textIsAction(loaded.project, scene);
1491
+ for (const block of scene.blocks) {
1492
+ walkNodes(block.children, (node) => {
1493
+ if (node.type === "group") {
1494
+ const p = node.prompt;
1495
+ if (p) push(p.id, scene.id, lineContext(p));
1496
+ return;
1497
+ }
1498
+ for (const beat of node.beats ?? []) {
1499
+ if (beat.kind === "gameEvent" || action && beat.kind === "text") continue;
1500
+ push(beat.id, scene.id, lineContext(beat));
1501
+ }
1502
+ });
1503
+ }
1504
+ }
1505
+ for (const c of loaded.project.cast ?? []) {
1506
+ if (!c.displayName) continue;
1507
+ const id = castStringKey(c.name);
1508
+ const src = projectSources[id];
1509
+ const translation = isTemplate ? "" : target[id] ?? "";
1510
+ entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: commentsOf(id), context: withGender({ character: c.name }), stale: staleFor(id, translation) });
1511
+ }
1512
+ for (const q of usedQualifiers(loaded.project, loaded.scenes)) {
1513
+ const id = qualifierStringKey(q.gameId);
1514
+ const src = projectSources[id] ?? q.name;
1515
+ const translation = isTemplate ? "" : target[id] ?? "";
1516
+ entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: commentsOf(id), context: { kind: "qualifier" }, stale: staleFor(id, translation) });
1517
+ }
1518
+ const pairs = usedSpeakerPairs(loaded.scenes);
1519
+ if (pairs.length > 0) {
1520
+ const id = SPEAKER_PATTERN_KEY;
1521
+ const src = projectSources[id];
1522
+ const translation = isTemplate ? "" : target[id] ?? "";
1523
+ entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: [SPEAKER_PATTERN_NOTE, ...commentsOf(id)], context: { kind: "speaker pattern" }, stale: staleFor(id, translation) });
1524
+ const labelsOf = new Map((loaded.project.cast ?? []).map((c) => [c.name, c.qualifierLabels]));
1525
+ for (const p of pairs) {
1526
+ const label = labelsOf.get(p.character)?.[p.qualifier];
1527
+ if (!label) continue;
1528
+ const pid = speakerLabelKey(p.character, p.qualifier);
1529
+ const psrc = projectSources[pid] ?? label;
1530
+ const ptranslation = isTemplate ? "" : target[pid] ?? "";
1531
+ const qualifier = qualifierName.get(p.qualifier) ?? p.qualifier;
1532
+ 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) });
1533
+ }
1534
+ }
1535
+ return { project: loaded.project.project.id, defaultLocale, locale: targetLocale, entries };
1536
+ }
1537
+ 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.";
1538
+ var layoutOf = (loaded) => projectLayout2(loaded.project);
1539
+ function existingLocPath(loaded, scene, locale) {
1540
+ for (let i = 0; i < loaded.locales.length; i++) {
1541
+ const l = loaded.locales[i];
1542
+ if (l.scene === scene && l.locale === locale) return loaded.localeFiles[i] ?? null;
1543
+ }
1544
+ return null;
1545
+ }
1546
+ function targetLocPath(loaded, scene, locale, defaultLocale) {
1547
+ const existing = existingLocPath(loaded, scene, locale);
1548
+ if (existing) return existing;
1549
+ const fromDefault = existingLocPath(loaded, scene, defaultLocale);
1550
+ if (fromDefault) {
1551
+ const parts = fromDefault.split(sep);
1552
+ for (let i = parts.length - 2; i >= 0; i--) {
1553
+ if (parts[i] === defaultLocale) {
1554
+ parts[i] = locale;
1555
+ return parts.join(sep);
1556
+ }
1557
+ }
1558
+ }
1559
+ const stem = scene === PROJECT_LOCALE_SCENE ? "_project" : scene;
1560
+ return join3(loaded.root, layoutOf(loaded).strings, locale, `${stem}.patterloc`);
1561
+ }
1562
+ function authoringPath(loaded, scene) {
1563
+ const dir = join3(loaded.root, layoutOf(loaded).authoring);
1564
+ if (scene === PROJECT_LOCALE_SCENE) return join3(dir, "_project.patterx");
1565
+ const flow = loaded.sceneFiles[scene];
1566
+ const stem = flow ? basename3(flow).replace(/\.patterflow$/, "") : scene;
1567
+ return join3(dir, `${stem}.patterx`);
1568
+ }
1569
+ var findFile = (files, items, path) => {
1570
+ const i = files.indexOf(path);
1571
+ return i >= 0 ? items[i] : void 0;
1572
+ };
1573
+ function applyLoc(loaded, catalog, opts = {}) {
1574
+ const locale = catalog.locale;
1575
+ if (!locale || locale === loaded.project.locales.default) {
1576
+ return { writes: [], stats: { updated: 0, files: 0, dropped: 0 } };
1577
+ }
1578
+ if (!loaded.project.locales.all.includes(locale)) {
1579
+ throw new Error(`'${locale}' is not one of the project's languages (${loaded.project.locales.all.join(", ")}); add it to the project first`);
1580
+ }
1581
+ const known = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id), PROJECT_LOCALE_SCENE]);
1582
+ const unknown = [...new Set(catalog.entries.filter((e) => e.translation.trim() && !known.has(e.scene)).map((e) => e.scene))];
1583
+ if (unknown.length) {
1584
+ throw new Error(`the file names scene(s) this project does not have: ${unknown.map((u) => `'${u}'`).join(", ")}; nothing was imported`);
1585
+ }
1586
+ const now = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
1587
+ const action = actionTextIds(loaded);
1588
+ const bySceneText = /* @__PURE__ */ new Map();
1589
+ const bySceneFresh = /* @__PURE__ */ new Map();
1590
+ for (const e of catalog.entries) {
1591
+ if (!e.translation.trim()) continue;
1592
+ if (action.get(e.scene)?.has(e.id)) continue;
1593
+ (bySceneText.get(e.scene) ?? bySceneText.set(e.scene, /* @__PURE__ */ new Map()).get(e.scene)).set(e.id, e.translation);
1594
+ if (!e.stale) (bySceneFresh.get(e.scene) ?? bySceneFresh.set(e.scene, /* @__PURE__ */ new Set()).get(e.scene)).add(e.id);
1595
+ }
1596
+ const writes = [];
1597
+ let updated = 0;
1598
+ let dropped = 0;
1599
+ let files = 0;
1600
+ const scenes = new Set(bySceneText.keys());
1601
+ for (const l of loaded.locales) if (l.locale === locale && l.scene !== PROJECT_LOCALE_SCENE && known.has(l.scene)) scenes.add(l.scene);
1602
+ for (const scene of scenes) {
1603
+ const strings = bySceneText.get(scene) ?? /* @__PURE__ */ new Map();
1604
+ const path = targetLocPath(loaded, scene, locale, loaded.project.locales.default);
1605
+ const existing = findFile(loaded.localeFiles, loaded.locales, path);
1606
+ const merged = { ...existing?.strings ?? {} };
1607
+ for (const [id, text] of strings) {
1608
+ if (merged[id] !== text) updated++;
1609
+ merged[id] = text;
1610
+ }
1611
+ const actionIds = action.get(scene);
1612
+ for (const id of actionIds ?? []) if (id in merged) {
1613
+ delete merged[id];
1614
+ dropped++;
1615
+ }
1616
+ const file = {
1617
+ schema: existing?.schema ?? STRINGS_SCHEMA,
1618
+ scene,
1619
+ locale,
1620
+ ...actionIds ? { textAction: true } : {},
1621
+ strings: merged
1622
+ };
1623
+ const content = canonicalStringify3(file);
1624
+ if (!existing || strings.size > 0 || canonicalStringify3(existing) !== content) {
1625
+ writes.push({ path, content });
1626
+ files++;
1627
+ }
1628
+ const fresh = bySceneFresh.get(scene);
1629
+ if (!fresh?.size) continue;
1630
+ const aPath = authoringPath(loaded, scene);
1631
+ const aExisting = findFile(loaded.authoringFiles, loaded.authoring, aPath);
1632
+ const authoring = aExisting ? { ...aExisting, edits: { ...aExisting.edits } } : { schema: AUTHORING_SCHEMA };
1633
+ const editsMap = { ...authoring.edits ?? {} };
1634
+ for (const id of fresh) editsMap[id] = { ...editsMap[id], localisedAt: { ...editsMap[id]?.localisedAt, [locale]: now } };
1635
+ authoring.edits = editsMap;
1636
+ writes.push({ path: aPath, content: canonicalStringify3(authoring) });
1637
+ }
1638
+ return { writes, stats: { updated, files, dropped } };
1639
+ }
1640
+
1247
1641
  // src/pack.ts
1248
1642
  import JSZip from "jszip";
1249
1643
  import { readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
1250
- import { dirname as dirname4, join as join4, relative as relative2, sep } from "path";
1644
+ import { dirname as dirname5, join as join5, relative as relative2, sep as sep2 } from "path";
1251
1645
  import { parseSource as parseSource3 } from "@patterkit/core";
1252
1646
  import { EXTENSION_SHARD as EXTENSION_SHARD2, EXTENSIONS_DIR as EXTENSIONS_DIR2, MANIFEST_EXTENSION as MANIFEST_EXTENSION2 } from "@patterkit/model";
1253
1647
  import { ARCHIVE_ENTRY_OPTS } from "@wildwinter/toolkit/archive";
1254
1648
 
1255
1649
  // src/handoff.ts
1256
1650
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1257
- import { basename as basename3, join as join3 } from "path";
1651
+ import { basename as basename4, join as join4 } from "path";
1258
1652
  import { HANDOFF_SCHEMA } from "@patterkit/model";
1259
1653
  var HANDOFF_DIR = "handoffs";
1260
1654
  var HANDOFF_EXT = ".json";
1261
1655
  function handoffPath(root, id) {
1262
- return join3(root, HANDOFF_DIR, `${id}${HANDOFF_EXT}`);
1656
+ return join4(root, HANDOFF_DIR, `${id}${HANDOFF_EXT}`);
1263
1657
  }
1264
1658
  function serialiseHandoff(handoff) {
1265
1659
  return JSON.stringify(handoff, null, 2) + "\n";
@@ -1276,11 +1670,11 @@ function parseHandoff(text) {
1276
1670
  function readHandoffs(root) {
1277
1671
  const handoffs = [];
1278
1672
  const issues = [];
1279
- for (const file of walkFiles(join3(root, HANDOFF_DIR), HANDOFF_EXT)) {
1673
+ for (const file of walkFiles(join4(root, HANDOFF_DIR), HANDOFF_EXT)) {
1280
1674
  try {
1281
1675
  handoffs.push(parseHandoff(readFileSync3(file, "utf8")));
1282
1676
  } catch (e) {
1283
- issues.push({ file, message: `${basename3(file)}: ${e instanceof Error ? e.message : String(e)}` });
1677
+ issues.push({ file, message: `${basename4(file)}: ${e instanceof Error ? e.message : String(e)}` });
1284
1678
  }
1285
1679
  }
1286
1680
  handoffs.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
@@ -1340,7 +1734,7 @@ var SHARD_EXTENSIONS = [".patterflow", ".patterloc", ".patterx", ".patterproj",
1340
1734
  var ENTRY_OPTS = ARCHIVE_ENTRY_OPTS;
1341
1735
  async function runPack(startPath) {
1342
1736
  const projectFile = findProjectFile(startPath);
1343
- const root = dirname4(projectFile);
1737
+ const root = dirname5(projectFile);
1344
1738
  const project = parseSource3(readFileSync4(projectFile, "utf8"));
1345
1739
  const unresolved = sidecarIssues(walkFiles(root, CONFLICT_SIDECAR));
1346
1740
  if (unresolved.length) {
@@ -1349,7 +1743,7 @@ async function runPack(startPath) {
1349
1743
  ` + unresolved.map((i) => ` ${i.file}`).join("\n")
1350
1744
  );
1351
1745
  }
1352
- const shardFiles = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).map((abs) => ({ abs, rel: relative2(root, abs).split(sep).join("/") }));
1746
+ const shardFiles = SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).map((abs) => ({ abs, rel: relative2(root, abs).split(sep2).join("/") }));
1353
1747
  const marked = [...new Set(unresolvedMarkIssues(shardFiles.map((f) => ({ file: f.rel, text: readFileSync4(f.abs, "utf8") }))).map((i) => i.file))];
1354
1748
  if (marked.length) {
1355
1749
  throw new Error(
@@ -1359,7 +1753,7 @@ async function runPack(startPath) {
1359
1753
  }
1360
1754
  const files = [...shardFiles, ...extensionManifests(root)].sort((a, b) => a.rel.localeCompare(b.rel));
1361
1755
  const scopes = gameScopesSnapshot(root, project);
1362
- const handoffs = walkFiles(join4(root, HANDOFF_DIR), ".json").map((abs) => ({ rel: relative2(root, abs).split(sep).join("/"), text: readFileSync4(abs, "utf8") })).filter((f) => {
1756
+ const handoffs = walkFiles(join5(root, HANDOFF_DIR), ".json").map((abs) => ({ rel: relative2(root, abs).split(sep2).join("/"), text: readFileSync4(abs, "utf8") })).filter((f) => {
1363
1757
  try {
1364
1758
  return !parseHandoff(f.text).closed;
1365
1759
  } catch {
@@ -1383,11 +1777,11 @@ async function runPack(startPath) {
1383
1777
  function extensionManifests(root) {
1384
1778
  let names;
1385
1779
  try {
1386
- names = readdirSync3(join4(root, EXTENSIONS_DIR2), { withFileTypes: true }).filter((e) => e.isFile() && !e.name.startsWith(".") && e.name.endsWith(MANIFEST_EXTENSION2)).map((e) => e.name);
1780
+ 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
1781
  } catch {
1388
1782
  return [];
1389
1783
  }
1390
- return names.map((name) => ({ abs: join4(root, EXTENSIONS_DIR2, name), rel: `${EXTENSIONS_DIR2}/${name}` }));
1784
+ return names.map((name) => ({ abs: join5(root, EXTENSIONS_DIR2, name), rel: `${EXTENSIONS_DIR2}/${name}` }));
1391
1785
  }
1392
1786
  var isManifestEntry = (name) => {
1393
1787
  const rest = name.startsWith(`${EXTENSIONS_DIR2}/`) ? name.slice(EXTENSIONS_DIR2.length + 1) : void 0;
@@ -1467,17 +1861,17 @@ function gitattributesHint(loaded) {
1467
1861
  const files = [];
1468
1862
  let dir = loaded.root, repo = false;
1469
1863
  for (; ; ) {
1470
- const f = join5(dir, ".gitattributes");
1864
+ const f = join6(dir, ".gitattributes");
1471
1865
  if (existsSync4(f)) files.push(f);
1472
- if (existsSync4(join5(dir, ".git"))) {
1866
+ if (existsSync4(join6(dir, ".git"))) {
1473
1867
  repo = true;
1474
1868
  break;
1475
1869
  }
1476
- const up = dirname5(dir);
1870
+ const up = dirname6(dir);
1477
1871
  if (up === dir) break;
1478
1872
  dir = up;
1479
1873
  }
1480
- const consulted = repo ? files : files.filter((f) => dirname5(f) === loaded.root);
1874
+ const consulted = repo ? files : files.filter((f) => dirname6(f) === loaded.root);
1481
1875
  const texts = consulted.map((f) => {
1482
1876
  try {
1483
1877
  return readFileSync5(f, "utf8");
@@ -1499,7 +1893,7 @@ function gameScopesIssues(loaded, notes) {
1499
1893
  const gs = loaded.gameScopes;
1500
1894
  if (!gs) return out;
1501
1895
  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: join5(gs.dir, n.file), severity: "warning", message: n.message });
1896
+ for (const n of notes) out.push({ file: join6(gs.dir, n.file), severity: "warning", message: n.message });
1503
1897
  const stale = patterScopesStale(loaded.project, gs);
1504
1898
  if (stale) out.push({ ...stale, severity: "warning" });
1505
1899
  return out;
@@ -1513,7 +1907,7 @@ function unplayableScopes(loaded, compiled) {
1513
1907
  }));
1514
1908
  }
1515
1909
  function orphanShards(loaded, tree = walkFilesByExt(loaded.root, SHARD_EXTENSIONS)) {
1516
- const layout = projectLayout2(loaded.project);
1910
+ const layout = projectLayout3(loaded.project);
1517
1911
  const kind = {
1518
1912
  ".patterflow": { what: "scene", home: layout.flow },
1519
1913
  ".patterloc": { what: "strings", home: layout.strings },
@@ -1528,9 +1922,9 @@ function orphanShards(loaded, tree = walkFilesByExt(loaded.root, SHARD_EXTENSION
1528
1922
  const out = [];
1529
1923
  for (const [ext, { what, home }] of Object.entries(kind)) {
1530
1924
  for (const file of tree.get(ext) ?? []) {
1531
- if (home ? inside(file, join5(loaded.root, home)) : file === loaded.projectFile) continue;
1925
+ if (home ? inside(file, join6(loaded.root, home)) : file === loaded.projectFile) continue;
1532
1926
  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 ${basename4(loaded.projectFile)} is read. Delete it, or move it out of the project.`;
1927
+ 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
1928
  out.push({ file, message });
1535
1929
  }
1536
1930
  }
@@ -1559,7 +1953,7 @@ function checkBundles(loaded, compiled, inTree) {
1559
1953
  }
1560
1954
  function locIssues(loaded) {
1561
1955
  const out = [];
1562
- const scenes = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id), PROJECT_LOCALE_SCENE]);
1956
+ const scenes = /* @__PURE__ */ new Set([...loaded.scenes.map((sc) => sc.id), PROJECT_LOCALE_SCENE2]);
1563
1957
  const declared = new Set(loaded.project.locales.all);
1564
1958
  const bySlot = /* @__PURE__ */ new Map();
1565
1959
  const byKey = /* @__PURE__ */ new Map();
@@ -1580,6 +1974,21 @@ function locIssues(loaded) {
1580
1974
  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
1975
  }
1582
1976
  });
1977
+ const changes = /* @__PURE__ */ new Map();
1978
+ for (const c of actionTextChanges(loaded)) {
1979
+ const key = `${c.scene}|${c.locale}`;
1980
+ const n = changes.get(key) ?? changes.set(key, { dropped: 0, added: 0 }).get(key);
1981
+ n[c.change]++;
1982
+ }
1983
+ const nameOf = new Map(loaded.scenes.map((sc) => [sc.id, sc.name]));
1984
+ for (const [key, n] of changes) {
1985
+ const [scene, locale] = key.split("|");
1986
+ const file = existingLocPath(loaded, scene, locale) ?? "";
1987
+ const name = nameOf.get(scene) ?? scene;
1988
+ const lines = (k) => `${k} text line${k === 1 ? "" : "s"}`;
1989
+ 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"}` });
1990
+ 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` });
1991
+ }
1583
1992
  return out;
1584
1993
  }
1585
1994
  var hygieneCache = /* @__PURE__ */ new Map();
@@ -1647,11 +2056,11 @@ function checkHygiene(files) {
1647
2056
 
1648
2057
  // src/export.ts
1649
2058
  import { readFileSync as readFileSync6 } from "fs";
1650
- import { canonicalStringify as canonicalStringify3 } from "@patterkit/core";
2059
+ import { canonicalStringify as canonicalStringify4 } from "@patterkit/core";
1651
2060
 
1652
2061
  // src/audio-index.ts
1653
2062
  import { readdirSync as readdirSync4 } from "fs";
1654
- import { join as join6, relative as relative4, resolve as resolve4, sep as sep2 } from "path";
2063
+ import { join as join7, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
1655
2064
  import { deriveRecordingFolders, DEFAULT_RECORDING_STATUSES } from "@patterkit/model";
1656
2065
  var AUDIO_MANIFEST_SCHEMA = "patter/audio@0";
1657
2066
  var AUDIO_MANIFEST_FILE = "patteraudio.json";
@@ -1702,14 +2111,14 @@ function audioManifest(snapshot, projectRoot, audioRoot) {
1702
2111
  const clips = {};
1703
2112
  for (const beatId of Object.keys(snapshot).sort()) {
1704
2113
  const entry = snapshot[beatId];
1705
- clips[beatId] = { file: relative4(base, entry.path).split(sep2).join("/"), status: entry.status };
2114
+ clips[beatId] = { file: relative4(base, entry.path).split(sep3).join("/"), status: entry.status };
1706
2115
  }
1707
2116
  return JSON.stringify({ schema: AUDIO_MANIFEST_SCHEMA, clips }, null, 2) + "\n";
1708
2117
  }
1709
2118
  function audioManifestWrite(loaded, snapshot) {
1710
2119
  const p = loaded.project;
1711
2120
  if (!snapshot || !audioFoldersActive(p) || !Object.keys(snapshot).length) return void 0;
1712
- return { path: join6(resolve4(loaded.root, p.audioRoot), AUDIO_MANIFEST_FILE), content: audioManifest(snapshot, loaded.root, p.audioRoot) };
2121
+ return { path: join7(resolve4(loaded.root, p.audioRoot), AUDIO_MANIFEST_FILE), content: audioManifest(snapshot, loaded.root, p.audioRoot) };
1713
2122
  }
1714
2123
 
1715
2124
  // src/export.ts
@@ -1755,7 +2164,7 @@ function runExportFull(loaded, opts = {}) {
1755
2164
  }
1756
2165
  function planBuild(loaded, opts = {}) {
1757
2166
  const bundle = runExport(loaded, opts);
1758
- const text = canonicalStringify3(bundle, { trailingComma: false });
2167
+ const text = canonicalStringify4(bundle, { trailingComma: false });
1759
2168
  const scopes = patterScopesWrite(loaded.project, loaded.gameScopes);
1760
2169
  const audio = audioManifestWrite(loaded, opts.audio ?? scanAudio(loaded));
1761
2170
  return {
@@ -1766,11 +2175,11 @@ function planBuild(loaded, opts = {}) {
1766
2175
  }
1767
2176
 
1768
2177
  // src/export-html.ts
1769
- import { canonicalStringify as canonicalStringify4 } from "@patterkit/core";
2178
+ import { canonicalStringify as canonicalStringify5 } from "@patterkit/core";
1770
2179
  import { DEFAULT_PAD_AFTER } from "@patterkit/model";
1771
2180
 
1772
2181
  // 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';
2182
+ 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 D(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:D(r[2])};case"bin":return{kind:"binary",op:r[1],left:D(r[2]),right:D(r[3])};case"call":{let e=r.slice(2).map(D);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 De(o,a);case"!=":return!De(o,a);case">":return R(o,a,">"),o>a;case">=":return R(o,a,">="),o>=a;case"<":return R(o,a,"<"),o<a;case"<=":return R(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new u(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return R(o,a,"-"),o-a;case"*":return R(o,a,"*"),o*a;case"/":if(R(o,a,"/"),a===0)throw new u("division by zero");return o/a}}}};return s(r)}function De(r,e){if(Array.isArray(r)||Array.isArray(e)){if(!Array.isArray(r)||!Array.isArray(e)||r.length!==e.length)return!1;let t=[...r].sort(),n=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==n[s])return!1;return!0}return r===e}function R(r,e,t){if(typeof r!="number"||typeof e!="number")throw new u(`\'${t}\' requires numeric operands, got ${typeof r} and ${typeof e}`)}function oe(r,e){if(!(r.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(r.scope,r.name)}function v(r,e,t){if(typeof r!="string")throw new u(`\'${t}\' on a quality compares stages, got ${typeof r}`);let n=e.indexOf(r);if(n<0)throw new u(`"${r}" is not a stage of this quality (stages: ${e.join(", ")})`);return n}var 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 Re(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 Ce(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=Ce(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 Ce(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 Dt=/^@[A-Za-z0-9_.]+$/;function*Rt(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:Dt.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 Ct(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 Rt(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?"":Ct(s)}return t}function C(r,e){for(let t of r){e(t);let n=t.children;n&&C(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)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 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),C(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)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[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 Re(e,i=>{try{return Xe(J(i,this.context(),ue))}catch(o){return this.reportError("best-match",n.id,n.condition,o),!1}},{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,n=this.host.journal,s;if(n&&(s=e.shared?n.selectors:n.flowSelectors.get(this),s||(s=new Set,n.flowSelectors.set(this,s))),n&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;n.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e,t){for(let n of e??[]){let s;try{s=this.evalExpr(n.value);let i=this.host.tracing?this.getProperty(n.target):void 0;this.writeProperty(n.target,s,!1),this.host.tracing&&this.emit({type:"write",target:n.target,value:s,...i!==void 0?{prev:i}:{}})}catch(i){this.reportError("effect",t,n.value,i)}}}eligible(e){if(!e.condition)return!0;try{return Xe(this.evalExpr(e.condition))}catch(t){return this.reportError("condition",e.id,e.condition,t),!1}}reportError(e,t,n,s){let i=s instanceof Error?s.message:String(s),o=n?.src;this.host.onError({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i}),this.host.tracing&&this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return J(this.conditionAst(e),this.context(),ue)}conditionAst(e){let t=$e.get(e);return t||(t=D(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),...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 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 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=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){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(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';
1774
2183
  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
2184
 
1776
2185
  // src/play-timeline.ts
@@ -1911,7 +2320,9 @@ var PLAYER_JS = String.raw`
1911
2320
  resumeSave = null; // what's shown is where the engine is
1912
2321
  if (!s || s.type === "end") { add("end", "The End"); return; } // no step is defensive: never hard-crash
1913
2322
  if (s.type === "choice") { renderChoice(s); return; }
1914
- timeline.place(s, estimate(s.text));
2323
+ // Action text (a direction for whoever stages the scene, never shown to the player) takes no time of its
2324
+ // own, only its pause, and reads as a direction.
2325
+ timeline.place(s, s.action ? 0 : estimate(s.text));
1915
2326
  if (s.type === "line") {
1916
2327
  // The speaker's label as the runtime resolved it (TAM (O.S.), or a pair's own label): this page never
1917
2328
  // puts a name and a qualifier together itself. (Its bundle always carries the strings, so a line with a
@@ -1920,7 +2331,7 @@ var PLAYER_JS = String.raw`
1920
2331
  var dir = s.direction ? '<em class="dir">(' + esc(s.direction) + ')</em> ' : "";
1921
2332
  add("line", '<span class="who" style="color:hsl(' + hueOf(s.character || who) + ',55%,38%)">' + esc(who) + '</span>' + dir + esc(s.text));
1922
2333
  } else {
1923
- add("text", esc(s.text));
2334
+ add(s.action ? "text action" : "text", esc(s.text));
1924
2335
  }
1925
2336
  scrollDown();
1926
2337
  next(readNext());
@@ -1970,6 +2381,7 @@ var STYLE = String.raw`
1970
2381
  .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
2382
  .line .dir { color: #9a8f78; font-style: italic; }
1972
2383
  .text { color: #4a4334; }
2384
+ .text.action { color: #9a8f78; font-style: italic; }
1973
2385
  .end { color: #9a8f78; font-style: italic; text-align: center; margin-top: 1.5rem; }
1974
2386
  #controls { margin: 1.25rem 0; display: flex; flex-direction: column; gap: 0.5rem; }
1975
2387
  .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 +2401,7 @@ function sourceStory(loaded) {
1989
2401
  const def = full.locales?.default ?? Object.keys(full.strings)[0] ?? "en";
1990
2402
  const bundle = { ...full, locales: { default: def, included: [def] }, strings: { [def]: full.strings[def] ?? {} } };
1991
2403
  const title = loaded.project.project.name?.trim() || "A Patter story";
1992
- const bundleJson = canonicalStringify4(bundle, { trailingComma: false }).replace(/</g, "\\u003c");
2404
+ const bundleJson = canonicalStringify5(bundle, { trailingComma: false }).replace(/</g, "\\u003c");
1993
2405
  return { title, lang: def, bundleJson };
1994
2406
  }
1995
2407
  function page(title, lang, head, tail) {
@@ -2038,99 +2450,32 @@ function runExportHtml(loaded) {
2038
2450
  `<script>${PLAYABLE_RUNTIME_JS}</script>
2039
2451
  <script>${PLAYABLE_TIMING_JS}</script>
2040
2452
  <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;
2453
+ <script>${PLAYER_JS}</script>`
2454
+ );
2110
2455
  }
2111
- function mergeAuthoring2(loaded) {
2112
- const writing = /* @__PURE__ */ new Map();
2113
- const recording = /* @__PURE__ */ new Map();
2114
- const cut = /* @__PURE__ */ new Set();
2115
- const rerecord = /* @__PURE__ */ new Set();
2116
- const documentation = /* @__PURE__ */ new Map();
2117
- const edits = /* @__PURE__ */ new Map();
2118
- for (const a of loaded.authoring) {
2119
- for (const [id, v] of Object.entries(a.writing ?? {})) writing.set(id, v);
2120
- for (const [id, v] of Object.entries(a.recording ?? {})) recording.set(id, v);
2121
- for (const [id, v] of Object.entries(a.cut ?? {})) if (v) cut.add(id);
2122
- for (const [id, v] of Object.entries(a.rerecord ?? {})) if (v) rerecord.add(id);
2123
- for (const [id, lines] of Object.entries(a.documentation ?? {})) {
2124
- const cur = documentation.get(id);
2125
- if (cur) cur.push(...lines);
2126
- else documentation.set(id, [...lines]);
2127
- }
2128
- for (const [id, v] of Object.entries(a.edits ?? {})) edits.set(id, v);
2129
- }
2130
- return { writing, recording, cut, rerecord, documentation, edits };
2456
+ function runExportWeb(loaded) {
2457
+ const { title, lang, bundleJson } = sourceStory(loaded);
2458
+ return {
2459
+ indexHtml: page(
2460
+ title,
2461
+ lang,
2462
+ `<link rel="stylesheet" href="style.css" />`,
2463
+ // The story must load first (patterplay.js reads window.PATTER_BUNDLE as it starts).
2464
+ `<script src="story.js"></script>
2465
+ <script src="patterplay.js"></script>`
2466
+ ),
2467
+ styleCss: `${STYLE.trim()}
2468
+ `,
2469
+ patterplayJs: `${PLAYABLE_RUNTIME_JS}
2470
+ ${PLAYABLE_TIMING_JS}
2471
+ ${PLAYER_JS}`,
2472
+ storyJs: `window.PATTER_BUNDLE=${bundleJson};
2473
+ `
2474
+ };
2131
2475
  }
2132
2476
 
2133
2477
  // src/play.ts
2478
+ import { Engine } from "@patterkit/runtime";
2134
2479
  function runPlay(loaded, opts = {}) {
2135
2480
  const bundle = compileLoaded(loaded);
2136
2481
  const events = [];
@@ -2216,7 +2561,7 @@ function renderPlay(result) {
2216
2561
 
2217
2562
  // src/coverage.ts
2218
2563
  import { Engine as Engine2 } from "@patterkit/runtime";
2219
- import { isContentlessBeat as isContentlessBeat2, walkNodes } from "@patterkit/model";
2564
+ import { isContentlessBeat as isContentlessBeat2, walkNodes as walkNodes2 } from "@patterkit/model";
2220
2565
  import { deserialiseAst, makePrng, stagesEitherSide } from "@wildwinter/expr";
2221
2566
  var CADENCE_PROB = {
2222
2567
  rarely: 0.15,
@@ -2467,7 +2812,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
2467
2812
  for (const scene of loaded.scenes) {
2468
2813
  nodeScene.set(scene.id, scene.id);
2469
2814
  for (const block of scene.blocks) {
2470
- walkNodes(block.children, (node) => {
2815
+ walkNodes2(block.children, (node) => {
2471
2816
  nodeScene.set(node.id, scene.id);
2472
2817
  if (node.type === "group") {
2473
2818
  if (node.selector === "choice") choiceScene.set(node.id, scene.id);
@@ -2720,17 +3065,17 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
2720
3065
 
2721
3066
  // src/format.ts
2722
3067
  import { readFileSync as readFileSync8, statSync as statSync3 } from "fs";
2723
- import { dirname as dirname6, resolve as resolve5 } from "path";
2724
- import { canonicalStringify as canonicalStringify6, parseSource as parseSource6 } from "@patterkit/core";
3068
+ import { dirname as dirname7, resolve as resolve5 } from "path";
3069
+ import { canonicalStringify as canonicalStringify7, parseSource as parseSource6 } from "@patterkit/core";
2725
3070
 
2726
3071
  // src/extensions.ts
2727
3072
  import { readFileSync as readFileSync7 } from "fs";
2728
- import { join as join7, relative as relative5, sep as sep3 } from "path";
2729
- import { canonicalStringify as canonicalStringify5, parseSource as parseSource5 } from "@patterkit/core";
3073
+ import { join as join8, relative as relative5, sep as sep4 } from "path";
3074
+ import { canonicalStringify as canonicalStringify6, parseSource as parseSource5 } from "@patterkit/core";
2730
3075
  import { EXTENSIONS_DIR as EXTENSIONS_DIR3, MANIFEST_EXTENSION as MANIFEST_EXTENSION3 } from "@patterkit/model";
2731
3076
  import { manifestProblems, parseManifest, renameStages, targetOf } from "@wildwinter/toolkit/extensions";
2732
3077
  var CONFLICT_MARKERS = /^(<{7}|={7}|>{7})( |$)/m;
2733
- var manifestPath = (projectDir, id) => join7(projectDir, EXTENSIONS_DIR3, `${id}${MANIFEST_EXTENSION3}`);
3078
+ var manifestPath = (projectDir, id) => join8(projectDir, EXTENSIONS_DIR3, `${id}${MANIFEST_EXTENSION3}`);
2734
3079
  function readProjectManifest(projectDir, id) {
2735
3080
  if (!/^[a-z][a-z0-9-]*$/.test(id)) return void 0;
2736
3081
  let text;
@@ -2761,7 +3106,7 @@ function planExtensionUpgrades(loaded) {
2761
3106
  };
2762
3107
  const taken = loaded.project.extensions ?? {};
2763
3108
  const moved = {};
2764
- const rel = (path) => relative5(loaded.root, path).split(sep3).join("/");
3109
+ const rel = (path) => relative5(loaded.root, path).split(sep4).join("/");
2765
3110
  for (const [id, pin] of Object.entries(taken)) {
2766
3111
  const file = loaded.manifests.find((m) => m.id === id);
2767
3112
  const manifest = file !== void 0 ? soundOf(file.manifest) : void 0;
@@ -2790,7 +3135,7 @@ function planExtensionUpgrades(loaded) {
2790
3135
  }
2791
3136
  if (renamed > 0) {
2792
3137
  const path = loaded.extensionShardFiles[i];
2793
- writes.set(path, canonicalStringify5({ ...shard, entries }));
3138
+ writes.set(path, canonicalStringify6({ ...shard, entries }));
2794
3139
  const what = `renamed ${renamed} stage value${renamed === 1 ? "" : "s"} for "${id}"`;
2795
3140
  report.push(` ${what} in ${rel(path)}`);
2796
3141
  note(path, what);
@@ -2799,7 +3144,7 @@ function planExtensionUpgrades(loaded) {
2799
3144
  }
2800
3145
  if (Object.keys(moved).length > 0) {
2801
3146
  const project = parseSource5(readFileSync7(loaded.projectFile, "utf8"));
2802
- writes.set(loaded.projectFile, canonicalStringify5({ ...project, extensions: { ...taken, ...moved } }));
3147
+ writes.set(loaded.projectFile, canonicalStringify6({ ...project, extensions: { ...taken, ...moved } }));
2803
3148
  }
2804
3149
  return { writes, report, notes };
2805
3150
  }
@@ -2881,7 +3226,7 @@ function expand(path) {
2881
3226
  } catch {
2882
3227
  }
2883
3228
  if (!dir) return [path];
2884
- const root = dirname6(findProjectFile(path));
3229
+ const root = dirname7(findProjectFile(path));
2885
3230
  return SHARD_EXTENSIONS.flatMap((ext) => walkFiles(root, ext)).sort();
2886
3231
  }
2887
3232
  function upgradesFor(paths) {
@@ -2912,7 +3257,7 @@ function runFormat(paths) {
2912
3257
  const notes = upgrades.notes.get(resolve5(file));
2913
3258
  let canonical;
2914
3259
  try {
2915
- canonical = upgrades.content.get(resolve5(file)) ?? canonicalStringify6(parseSource6(original));
3260
+ canonical = upgrades.content.get(resolve5(file)) ?? canonicalStringify7(parseSource6(original));
2916
3261
  } catch (e) {
2917
3262
  throw new Error(`${file}: ${e instanceof Error ? e.message : String(e)}`);
2918
3263
  }
@@ -2922,7 +3267,7 @@ function runFormat(paths) {
2922
3267
  }
2923
3268
 
2924
3269
  // src/report.ts
2925
- import { DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2, RERECORD_STATUS as RERECORD_STATUS2 } from "@patterkit/model";
3270
+ import { DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2, RERECORD_STATUS as RERECORD_STATUS2, textIsAction as textIsAction2 } from "@patterkit/model";
2926
3271
  var DEFAULT_WORDS_PER_LINE = 6;
2927
3272
  var UNATTRIBUTED = "(unattributed)";
2928
3273
  function allocateLargestRemainder(total, weights) {
@@ -2982,6 +3327,7 @@ function runReport(loaded, recordingOverride) {
2982
3327
  const rungIdxOf = (id) => writingIndex.get(wsOf(id)) ?? 0;
2983
3328
  for (const scene of loaded.scenes) {
2984
3329
  const sceneCut = cutSet.has(scene.id);
3330
+ const sceneAction = textIsAction2(project, scene);
2985
3331
  const units = [];
2986
3332
  const underCut = /* @__PURE__ */ new Set();
2987
3333
  let choices = 0;
@@ -3011,7 +3357,8 @@ function runReport(loaded, recordingOverride) {
3011
3357
  id: beat.id,
3012
3358
  voiced: beat.kind === "line",
3013
3359
  words: wordsOf(beat.id),
3014
- character: beat.kind === "line" ? beat.character : void 0
3360
+ character: beat.kind === "line" ? beat.character : void 0,
3361
+ ...sceneAction && beat.kind === "text" ? { action: true } : {}
3015
3362
  });
3016
3363
  }
3017
3364
  }
@@ -3020,7 +3367,7 @@ function runReport(loaded, recordingOverride) {
3020
3367
  }
3021
3368
  const isCut = (id) => cutSet.has(id) || underCut.has(id);
3022
3369
  for (const u of units) if (sceneCut || isCut(u.id)) {
3023
- cut.writtenLines++;
3370
+ if (!u.action) cut.writtenLines++;
3024
3371
  if (u.voiced) cut.voicedLines++;
3025
3372
  }
3026
3373
  if (sceneCut) {
@@ -3028,7 +3375,7 @@ function runReport(loaded, recordingOverride) {
3028
3375
  continue;
3029
3376
  }
3030
3377
  const live = units.filter((u) => !isCut(u.id));
3031
- for (const u of live) if (sourceStrings2[u.id]) localeIds.push(u.id);
3378
+ for (const u of live) if (sourceStrings2[u.id] && !u.action) localeIds.push(u.id);
3032
3379
  const statusIdx = live.length ? Math.min(...live.map((u) => rungIdxOf(u.id))) : void 0;
3033
3380
  const status = statusIdx === void 0 ? void 0 : writingLadder[statusIdx];
3034
3381
  const highestIdx = live.length ? Math.max(...live.map((u) => rungIdxOf(u.id))) : void 0;
@@ -3057,6 +3404,7 @@ function runReport(loaded, recordingOverride) {
3057
3404
  choices,
3058
3405
  voiced: emptyVoiced(writingLadder, recordingLadder),
3059
3406
  written: emptyWritten(writingLadder),
3407
+ action: emptyWritten(writingLadder),
3060
3408
  estimate: estimateLines,
3061
3409
  writtenDone: 0,
3062
3410
  writtenRemaining: estimateLines,
@@ -3069,8 +3417,15 @@ function runReport(loaded, recordingOverride) {
3069
3417
  }
3070
3418
  const voiced = emptyVoiced(writingLadder, recordingLadder);
3071
3419
  const written = emptyWritten(writingLadder);
3420
+ const action = emptyWritten(writingLadder);
3072
3421
  for (const u of live) {
3073
3422
  const ws = wsOf(u.id);
3423
+ if (u.action) {
3424
+ action.count++;
3425
+ action.words += u.words;
3426
+ action.byWriting[ws] = (action.byWriting[ws] ?? 0) + 1;
3427
+ continue;
3428
+ }
3074
3429
  written.count++;
3075
3430
  written.words += u.words;
3076
3431
  written.byWriting[ws] = (written.byWriting[ws] ?? 0) + 1;
@@ -3102,6 +3457,7 @@ function runReport(loaded, recordingOverride) {
3102
3457
  choices,
3103
3458
  voiced,
3104
3459
  written,
3460
+ action,
3105
3461
  writtenDone: written.count - writtenStub,
3106
3462
  writtenRemaining: writtenStub,
3107
3463
  voicedDone: voiced.count - voicedStub,
@@ -3168,6 +3524,7 @@ function computeTotals(scenes, writingLadder, recordingLadder) {
3168
3524
  const t = {
3169
3525
  voiced: emptyVoiced(writingLadder, recordingLadder),
3170
3526
  written: emptyWritten(writingLadder),
3527
+ action: emptyWritten(writingLadder),
3171
3528
  choices: 0,
3172
3529
  writtenDone: 0,
3173
3530
  writtenRemaining: 0,
@@ -3189,6 +3546,9 @@ function computeTotals(scenes, writingLadder, recordingLadder) {
3189
3546
  t.written.count += s.written.count;
3190
3547
  t.written.words += s.written.words;
3191
3548
  add(t.written.byWriting, s.written.byWriting);
3549
+ t.action.count += s.action.count;
3550
+ t.action.words += s.action.words;
3551
+ add(t.action.byWriting, s.action.byWriting);
3192
3552
  t.choices += s.choices;
3193
3553
  t.writtenDone += s.writtenDone;
3194
3554
  t.writtenRemaining += s.writtenRemaining;
@@ -3204,6 +3564,7 @@ function renderReportText(data) {
3204
3564
  const t = data.totals;
3205
3565
  out.push(`${data.project.name} - ${data.scenes.length} scene(s)`);
3206
3566
  out.push(`written lines: ${t.writtenDone} done / ${t.writtenRemaining} to write -> ${t.projectedWritten} projected (${t.written.words} words)`);
3567
+ if (t.action.count > 0) out.push(`action lines: ${t.action.count}, not shown to the player (${t.action.words} words)`);
3207
3568
  if (data.voiced) out.push(`voiced lines: ${t.voicedDone} done / ${t.voicedRemaining} to write -> ${t.projectedVoiced} projected (${t.voiced.words} words)`);
3208
3569
  out.push(`writing status: ${Object.entries(t.written.byWriting).map(([k, v]) => `${k} ${v}`).join(", ")}`);
3209
3570
  out.push(`scene status: ${Object.entries(data.scenesByStatus).map(([k, v]) => `${k} ${v}`).join(", ")}`);
@@ -3228,6 +3589,7 @@ async function runReportXlsx(data) {
3228
3589
  const v = data.voiced;
3229
3590
  const rec = data.recordingTracked;
3230
3591
  const scenes = wb.addWorksheet("Scenes");
3592
+ const action = data.totals.action.count > 0;
3231
3593
  scenes.columns = [
3232
3594
  { header: "Scene", key: "name", width: 24 },
3233
3595
  { header: "Status", key: "status", width: 10 },
@@ -3235,6 +3597,8 @@ async function runReportXlsx(data) {
3235
3597
  { header: "Written", key: "wCount", width: 8 },
3236
3598
  { header: "Words", key: "words", width: 9 },
3237
3599
  { header: "Choices", key: "choices", width: 8 },
3600
+ // Action (a dialogue scene's text the project doesn't localise): beside the written lines, never in them.
3601
+ ...action ? [{ header: "Action", key: "aCount", width: 8 }, { header: "Action words", key: "aWords", width: 12 }] : [],
3238
3602
  ...data.writingLadder.map((s) => ({ header: s, key: `w:${s}`, width: 10 })),
3239
3603
  ...v ? [{ header: "Ready record", key: "rr", width: 12 }, { header: "Ready ship", key: "rs", width: 11 }] : [],
3240
3604
  ...rec ? data.recordingLadder.map((s) => ({ header: `rec ${s}`, key: `r:${s}`, width: 10 })) : [],
@@ -3249,6 +3613,8 @@ async function runReportXlsx(data) {
3249
3613
  wCount: s.written.count,
3250
3614
  words: s.written.words,
3251
3615
  choices: s.choices,
3616
+ aCount: s.action.count,
3617
+ aWords: s.action.words,
3252
3618
  ...Object.fromEntries(data.writingLadder.map((k) => [`w:${k}`, s.written.byWriting[k] ?? 0])),
3253
3619
  rr: s.voiced.readyToRecord,
3254
3620
  rs: s.voiced.readyToShip,
@@ -3311,242 +3677,6 @@ async function runReportXlsx(data) {
3311
3677
  return Buffer.from(await wb.xlsx.writeBuffer());
3312
3678
  }
3313
3679
 
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
3680
  // src/loc-format.ts
3551
3681
  function checkCatalogField(format, field, value) {
3552
3682
  if (typeof value !== "string" || value.trim() === "") {
@@ -3914,7 +4044,7 @@ var TOKENS = {
3914
4044
  inkRead: "3a352d",
3915
4045
  // dialogue body (ink ~84% + muted)
3916
4046
  inkSoft: "5c554b",
3917
- // narration (ink ~68% + muted) - a step softer than dialogue
4047
+ // text (ink ~68% + muted) - a step softer than dialogue
3918
4048
  muted: "8c8479",
3919
4049
  // labels, quiet tags
3920
4050
  accent: "8a5a34",
@@ -6724,6 +6854,8 @@ export {
6724
6854
  SHARD_EXTENSIONS,
6725
6855
  UnsafeEntryError,
6726
6856
  UnsupportedMergeError,
6857
+ actionTextChanges,
6858
+ actionTextIds,
6727
6859
  applyLoc,
6728
6860
  applySceneOrder,
6729
6861
  applySuggestionDecisions,
@@ -6745,6 +6877,7 @@ export {
6745
6877
  currentBundlePosture,
6746
6878
  defaultBundlePath,
6747
6879
  defaultGameScopesDir,
6880
+ describeLocExport,
6748
6881
  detectMergeType,
6749
6882
  discoverGameScopes,
6750
6883
  drivableScopes,
@@ -6835,6 +6968,7 @@ export {
6835
6968
  scanAudio,
6836
6969
  scanAudioStatus,
6837
6970
  sceneIdForShard,
6971
+ sceneTextIds,
6838
6972
  scriptFontLicencePath,
6839
6973
  scriptFontLicences,
6840
6974
  scriptToDocx,