@patterkit/ops 0.25.1 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -237,6 +237,10 @@ declare function loadProjectLanding(startPath: string, opts?: {
237
237
  * Listed scenes come first in list order; unlisted scenes follow in their existing (file) order;
238
238
  * listed ids that no longer exist are simply ignored. Presentation only - play order is untouched. */
239
239
  declare function applySceneOrder(scenes: Scene[], order: string[] | undefined): void;
240
+ /** Sort scenes into the scene list's order as shown (`sceneFolderLayout`: `sceneOrder` within each folder,
241
+ * folders nested), in place. The same as `applySceneOrder` for a project with no folders. Presentation
242
+ * only - play order is untouched. */
243
+ declare function applySceneNavOrder(scenes: Scene[], project: Pick<ProjectFile, "sceneOrder" | "sceneFolders" | "sceneFolder">): void;
240
244
  declare function loadProject(startPath: string): LoadedProject;
241
245
 
242
246
  /** A raw-bytes hygiene problem in one source file (repairable by `format`). */
@@ -437,7 +441,7 @@ interface WebExport {
437
441
  indexHtml: string;
438
442
  /** The look of the page, separated so it's approachable to edit. Published once. */
439
443
  styleCss: string;
440
- /** The Patterplay runtime, the line-timing estimate, and this page's player glue. Refreshed on every publish. */
444
+ /** The Patterplay runtime (with the line-timing estimate) and this page's player glue. Refreshed on every publish. */
441
445
  patterplayJs: string;
442
446
  /** The compiled story as `window.PATTER_BUNDLE = …`. Refreshed on every publish. */
443
447
  storyJs: string;
@@ -714,6 +718,9 @@ declare function resolveStart(loaded: LoadedProject, override?: {
714
718
  scene?: string;
715
719
  block?: string;
716
720
  };
721
+ /** Each filed scene's folder path ("Act 1 / The docks"), by scene id: the Folder column the report, voice
722
+ * script, and loc spreadsheets carry (scene folders). Empty when the project has no folders. */
723
+ declare function sceneFolderPaths(loaded: Pick<LoadedProject, "project" | "scenes">): Map<string, string>;
717
724
 
718
725
  interface FormatResult {
719
726
  file: string;
@@ -744,6 +751,8 @@ interface VoicedCounts extends WrittenCounts {
744
751
  interface SceneReport {
745
752
  sceneId: string;
746
753
  name: string;
754
+ /** The scene's folder path in Patterpad's list ("Act 1 / The docks"), when it's filed in one. */
755
+ folder?: string;
747
756
  file?: string;
748
757
  /** The scene's writing status: its LOWEST-rung status-tracked beat. Absent when it has no such beats. */
749
758
  status?: string;
@@ -1003,7 +1012,9 @@ declare function catalogToPo(catalog: LocCatalog): string;
1003
1012
  declare function poToCatalog(text: string): LocCatalog;
1004
1013
 
1005
1014
  /** Render the catalog as an .xlsx workbook: a sheet per scene, translator-facing columns. */
1006
- declare function catalogToXlsx(catalog: LocCatalog): Promise<Buffer>;
1015
+ declare function catalogToXlsx(catalog: LocCatalog, opts?: {
1016
+ folders?: ReadonlyMap<string, string>;
1017
+ }): Promise<Buffer>;
1007
1018
  /** Parse an .xlsx workbook back into a catalog. Scene = the hidden Scene column (else, for an older export,
1008
1019
  * the sheet name); stale = Status "stale". `locale` (and the project and source language) come from the
1009
1020
  * hidden "Patter" sheet; an older export has none, so its `locale` is undefined and the caller supplies one.
@@ -1011,6 +1022,8 @@ declare function catalogToXlsx(catalog: LocCatalog): Promise<Buffer>;
1011
1022
  declare function xlsxToCatalog(buffer: Buffer): Promise<LocCatalog>;
1012
1023
 
1013
1024
  interface VoiceLine {
1025
+ /** The scene's folder path in Patterpad's list ("Act 1 / The docks"), when it's filed in one. */
1026
+ folder?: string;
1014
1027
  /** Readable container trail (`Scene › Block › Option`) - the scope changes signal a new run / branch. */
1015
1028
  scope: string;
1016
1029
  /** The line beat's id (recordings / filenames key to this). */
@@ -2193,4 +2206,4 @@ declare const REFUSE_MISSING_SHARE = 0.25;
2193
2206
  /** Plan an import. Reads the handoff record named in the document from the project. */
2194
2207
  declare function planEditableImport(loaded: LoadedProject, returned: ReturnedDoc, opts: ImportOptions): ImportPlan;
2195
2208
 
2196
- export { AUDIO_MANIFEST_FILE, AUDIO_MANIFEST_SCHEMA, AUTHORING_HANDLED, type ActionTextChange, type ApplyStats, type AudioEntry, type AudioRung, type AudioSnapshot, type BuildPlan, CONFLICT_SIDECAR, type CastKeyRewrite, type CharacterReport, type ClashPathSegment, type Conflict, type ConflictKind, type ConflictSidecar, type ContentError, type CoverageAsyncHooks, type CoverageBeat, type CoverageHooks, type CoverageOptions, type CoverageOrder, type CoverageReport, type DecisionPlan, type DecisionResult, type DocumentManifest, type EditableScript, type EditableScriptOptions, type ExportOptions, type ExtensionUpgrade, type FormatResult, GAME_SCOPES_OWNER, type GameDataStageRenameResult, type GameScopes, type GameScopesIssue, HANDOFF_DIR, type HandoffIssue, type HygieneIssue, type ImportOptions, type ImportPlan, type ImportProblem, type ImportReport, type InitOptions, type InitResult, type InitVcs, type LoadedManifest, type LoadedProject, type LocCatalog, type LocEntry, type LocImportLine, type LocImportReport, type LocMismatch, type LocaleReport, type MarkedShard, type MergeFileType, type MergeResult, type MergedShard, NotAPackError, type OpenSuggestion, PATTER_SCOPES_FILE, PATTER_SCOPES_OWNER, PATTER_VCS_FORMAT, PROJECT_HANDLED, type PinPlan, type PinnedName, type PlannedWrite, type PlayEvent, type PlayOptions, type PlayOutcome, type PlayResult, type ProvenanceCheck, RARE_REACH_PCT, REFUSE_MISSING_SHARE, type Random, type ReadBox, type ReadComment, type ReadEffect, type ReadItem, type ReadMarker, type ReadParagraph, type ReadText, type ReplaceField, type ReplaceHit, type ReplaceOptions, type ReplacePlan, type ReportData, type ReportTotals, type Resolution, type ResolutionDecision, ResolutionError, type ResolveEntry, type ReturnedDoc, SCRIPT_FONT_FILES, SCRIPT_FONT_PACKAGES, SHARD_EXTENSIONS, type SceneReport, type ScriptDoc, type ScriptElement, type ScriptFace, type SearchFocus, type ShardText, type StageRenameResult, type StageTarget, type SuggestionDecision, type TrackedChange, type UnpackMergeResult, type UnpackResult, type UnresolvedClash, UnsafeEntryError, UnsupportedMergeError, type ValidateResult, type VoiceLine, type VoiceScript, type VoicedCounts, type WebExport, type WrittenCounts, actionTextChanges, actionTextIds, applyLoc, applyResolutions, applySceneOrder, applySuggestionDecisions, applyWrites, audioFolderRungs, audioFoldersActive, audioManifest, audioManifestWrite, authoringPath, besideBundlePath, bundleOutputPath, castColours, catalogToJson, catalogToPo, catalogToXlsx, characterColour, clashPathSegments, classesForChannel, cleanCastRenames, compareLocTokens, compileLoaded, cueLabel, currentBundlePosture, defaultBundlePath, defaultGameScopesDir, describeLocExport, describeLocImport, detectMergeType, discoverGameScopes, drivableScopes, effectDescriber, exportBlockers, exportEditableScript, extractLoc, findMarkedShards, findProjectFile, formatMarker, gameScopeTokens, gameScopesCatalogue, gameScopesSnapshot, handoffPath, handoffWrite, hostScopeTokens, importLocale, isSettableClash, issueMarkerCodes, jsonToCatalog, keepCastColours, leastReachedFirst, listGameDataFields, listOpenSuggestions, listProjectTags, loadProject, loadProjectLanding, loadedExtensions, locLineHomes, manifestPath, markerLength, moveCastKeys, newHandoffId, parseHandoff, patterScopesFile, patterScopesStale, patterScopesWrite, pickAudio, planBuild, planEditableImport, planExtensionUpgrades, planPins, planReturnedWorld, planShareScopes, planWorldSave, poToCatalog, previewRegistry, projectManifests, projectStringSources, proposeCoverageDrivers, qualifierNamer, reachabilityIssues, readEditableDocx, readGameScopes, readHandoff, readHandoffs, readMarker, readProjectManifest, renameCastInScene, renameCastKeys, renameCastStrings, renameGameDataStage, renameStage, renderCoverageText, renderPlay, renderReportText, resolveDocumentation, resolveStart, runCoverage, runCoverageAsync, runExport, runExportFull, runExportHtml, runExportWeb, runFieldBrowse, runFormat, runInit, runMerge, runPack, runPlay, runPropertyUsage, runReplace, runReport, runReportXlsx, runResolve, runScriptDoc, runSearch, runStatusBrowse, runTagBrowse, runUnpack, runUnpackMerge, runValidate, runVoiceScript, scanAudio, scanAudioStatus, sceneIdForShard, sceneTextIds, scriptFontLicencePath, scriptFontLicences, scriptToDocx, scriptToPdf, serialiseHandoff, setCut, sidecarIssues, soundManifest, stageDialect, stageGateCount, staleParts, unresolvedClashes, unresolvedMarkIssues, useScriptFontFiles, valueAtClashPath, vcsConfigWrites, voiceScriptToXlsx, worldSettingsScopes, xlsxToCatalog };
2209
+ export { AUDIO_MANIFEST_FILE, AUDIO_MANIFEST_SCHEMA, AUTHORING_HANDLED, type ActionTextChange, type ApplyStats, type AudioEntry, type AudioRung, type AudioSnapshot, type BuildPlan, CONFLICT_SIDECAR, type CastKeyRewrite, type CharacterReport, type ClashPathSegment, type Conflict, type ConflictKind, type ConflictSidecar, type ContentError, type CoverageAsyncHooks, type CoverageBeat, type CoverageHooks, type CoverageOptions, type CoverageOrder, type CoverageReport, type DecisionPlan, type DecisionResult, type DocumentManifest, type EditableScript, type EditableScriptOptions, type ExportOptions, type ExtensionUpgrade, type FormatResult, GAME_SCOPES_OWNER, type GameDataStageRenameResult, type GameScopes, type GameScopesIssue, HANDOFF_DIR, type HandoffIssue, type HygieneIssue, type ImportOptions, type ImportPlan, type ImportProblem, type ImportReport, type InitOptions, type InitResult, type InitVcs, type LoadedManifest, type LoadedProject, type LocCatalog, type LocEntry, type LocImportLine, type LocImportReport, type LocMismatch, type LocaleReport, type MarkedShard, type MergeFileType, type MergeResult, type MergedShard, NotAPackError, type OpenSuggestion, PATTER_SCOPES_FILE, PATTER_SCOPES_OWNER, PATTER_VCS_FORMAT, PROJECT_HANDLED, type PinPlan, type PinnedName, type PlannedWrite, type PlayEvent, type PlayOptions, type PlayOutcome, type PlayResult, type ProvenanceCheck, RARE_REACH_PCT, REFUSE_MISSING_SHARE, type Random, type ReadBox, type ReadComment, type ReadEffect, type ReadItem, type ReadMarker, type ReadParagraph, type ReadText, type ReplaceField, type ReplaceHit, type ReplaceOptions, type ReplacePlan, type ReportData, type ReportTotals, type Resolution, type ResolutionDecision, ResolutionError, type ResolveEntry, type ReturnedDoc, SCRIPT_FONT_FILES, SCRIPT_FONT_PACKAGES, SHARD_EXTENSIONS, type SceneReport, type ScriptDoc, type ScriptElement, type ScriptFace, type SearchFocus, type ShardText, type StageRenameResult, type StageTarget, type SuggestionDecision, type TrackedChange, type UnpackMergeResult, type UnpackResult, type UnresolvedClash, UnsafeEntryError, UnsupportedMergeError, type ValidateResult, type VoiceLine, type VoiceScript, type VoicedCounts, type WebExport, type WrittenCounts, actionTextChanges, actionTextIds, applyLoc, applyResolutions, applySceneNavOrder, applySceneOrder, applySuggestionDecisions, applyWrites, audioFolderRungs, audioFoldersActive, audioManifest, audioManifestWrite, authoringPath, besideBundlePath, bundleOutputPath, castColours, catalogToJson, catalogToPo, catalogToXlsx, characterColour, clashPathSegments, classesForChannel, cleanCastRenames, compareLocTokens, compileLoaded, cueLabel, currentBundlePosture, defaultBundlePath, defaultGameScopesDir, describeLocExport, describeLocImport, detectMergeType, discoverGameScopes, drivableScopes, effectDescriber, exportBlockers, exportEditableScript, extractLoc, findMarkedShards, findProjectFile, formatMarker, gameScopeTokens, gameScopesCatalogue, gameScopesSnapshot, handoffPath, handoffWrite, hostScopeTokens, importLocale, isSettableClash, issueMarkerCodes, jsonToCatalog, keepCastColours, leastReachedFirst, listGameDataFields, listOpenSuggestions, listProjectTags, loadProject, loadProjectLanding, loadedExtensions, locLineHomes, manifestPath, markerLength, moveCastKeys, newHandoffId, parseHandoff, patterScopesFile, patterScopesStale, patterScopesWrite, pickAudio, planBuild, planEditableImport, planExtensionUpgrades, planPins, planReturnedWorld, planShareScopes, planWorldSave, poToCatalog, previewRegistry, projectManifests, projectStringSources, proposeCoverageDrivers, qualifierNamer, reachabilityIssues, readEditableDocx, readGameScopes, readHandoff, readHandoffs, readMarker, readProjectManifest, renameCastInScene, renameCastKeys, renameCastStrings, renameGameDataStage, renameStage, renderCoverageText, renderPlay, renderReportText, resolveDocumentation, resolveStart, runCoverage, runCoverageAsync, runExport, runExportFull, runExportHtml, runExportWeb, runFieldBrowse, runFormat, runInit, runMerge, runPack, runPlay, runPropertyUsage, runReplace, runReport, runReportXlsx, runResolve, runScriptDoc, runSearch, runStatusBrowse, runTagBrowse, runUnpack, runUnpackMerge, runValidate, runVoiceScript, scanAudio, scanAudioStatus, sceneFolderPaths, sceneIdForShard, sceneTextIds, scriptFontLicencePath, scriptFontLicences, scriptToDocx, scriptToPdf, serialiseHandoff, setCut, sidecarIssues, soundManifest, stageDialect, stageGateCount, staleParts, unresolvedClashes, unresolvedMarkIssues, useScriptFontFiles, valueAtClashPath, vcsConfigWrites, voiceScriptToXlsx, worldSettingsScopes, xlsxToCatalog };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync, existsSync as existsSync2 } from "fs";
3
3
  import { join as join2, dirname as dirname2, resolve as resolve2, basename } from "path";
4
4
  import { parseSource } from "@patterkit/core";
5
- import { EXTENSION_SHARD, EXTENSIONS_DIR, MANIFEST_EXTENSION, isCaseOnlyPropertyName, projectLayout } from "@patterkit/model";
5
+ import { EXTENSION_SHARD, EXTENSIONS_DIR, MANIFEST_EXTENSION, isCaseOnlyPropertyName, projectLayout, sceneFolderLayout } from "@patterkit/model";
6
6
 
7
7
  // src/game-scopes.ts
8
8
  import { existsSync, readdirSync, readFileSync } from "fs";
@@ -490,6 +490,11 @@ function applySceneOrder(scenes, order) {
490
490
  return (fileRank.get(a.id) ?? 0) - (fileRank.get(b.id) ?? 0);
491
491
  });
492
492
  }
493
+ function applySceneNavOrder(scenes, project) {
494
+ if (!project.sceneOrder?.length && !project.sceneFolders?.length) return;
495
+ const rank = new Map(sceneFolderLayout(project, scenes.map((s) => s.id)).sceneIds.map((id, i) => [id, i]));
496
+ scenes.sort((a, b) => (rank.get(a.id) ?? 0) - (rank.get(b.id) ?? 0));
497
+ }
493
498
  function loadProject(startPath) {
494
499
  const projectFile = findProjectFile(startPath);
495
500
  const root = dirname2(projectFile);
@@ -506,7 +511,7 @@ function loadProject(startPath) {
506
511
  scenes.push(scene);
507
512
  sceneFiles[scene.id] = f;
508
513
  }
509
- applySceneOrder(scenes, project.sceneOrder);
514
+ applySceneNavOrder(scenes, project);
510
515
  const locales = [];
511
516
  const localeFiles = [];
512
517
  for (const f of walkFiles(join2(root, layout.strings), ".patterloc")) {
@@ -737,7 +742,9 @@ function mergeDecls(strategy, base, ours, theirs, path, conflicts) {
737
742
  const out = shared(strategy, asArr(base), asArr(ours), asArr(theirs), "", path, conflicts);
738
743
  return out.map((m) => isObj(m) && isObj(m.qualifierLabels) && Object.keys(m.qualifierLabels).length === 0 ? (({ qualifierLabels: _q, ...rest }) => rest)(m) : m);
739
744
  }
740
- var PROJECT_HANDLED = /* @__PURE__ */ new Set(["properties", "cast", "gameDataFields", "qualifiers", "locales", "extensions"]);
745
+ var PROJECT_HANDLED = /* @__PURE__ */ new Set(["properties", "cast", "gameDataFields", "qualifiers", "locales", "extensions", "sceneOrder", "sceneFolders", "sceneFolder"]);
746
+ var SCENE_FOLDERS = keyedBy("id", objectOf({}), { order: "preserve" });
747
+ var SCENE_FOLDER = mapOf(ATOMIC);
741
748
  function mergeProject(base, ours, theirs) {
742
749
  const conflicts = [];
743
750
  const merged = {};
@@ -754,6 +761,15 @@ function mergeProject(base, ours, theirs) {
754
761
  } else if (k === "qualifiers") {
755
762
  const v = mergeQualifiers(base[k], ours[k], theirs[k], conflicts);
756
763
  if (v !== void 0) merged[k] = v;
764
+ } else if (k === "sceneOrder") {
765
+ const v = mergeOrder(asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), k, conflicts);
766
+ if (v.length > 0) merged[k] = v;
767
+ } else if (k === "sceneFolders") {
768
+ const v = shared(SCENE_FOLDERS, asArr(base[k]), asArr(ours[k]), asArr(theirs[k]), "", k, conflicts);
769
+ if (v.length > 0) merged[k] = v;
770
+ } else if (k === "sceneFolder") {
771
+ const v = shared(SCENE_FOLDER, asMap(base[k]), asMap(ours[k]), asMap(theirs[k]), "", k, conflicts);
772
+ if (Object.keys(v).length > 0) merged[k] = v;
757
773
  } else if (k === "locales") {
758
774
  merged.locales = mergeLocales(asMap(base.locales), asMap(ours.locales), asMap(theirs.locales), conflicts);
759
775
  } else if (k === "extensions" && [base[k], ours[k], theirs[k]].every((x) => x === void 0 || isObj(x))) {
@@ -766,6 +782,55 @@ function mergeProject(base, ours, theirs) {
766
782
  }
767
783
  return { type: "project", merged, conflicts, warnings: [] };
768
784
  }
785
+ function mergeOrder(base, ours, theirs, path, conflicts) {
786
+ const b = base.filter((x) => typeof x === "string");
787
+ const o = ours.filter((x) => typeof x === "string");
788
+ const t = theirs.filter((x) => typeof x === "string");
789
+ if (eq(o, t) || eq(b, t)) return o;
790
+ if (eq(b, o)) return t;
791
+ const moved = (side) => {
792
+ const at = new Map(b.map((id, i) => [id, i]));
793
+ const kept = side.filter((id) => at.has(id));
794
+ const run = longestRun(kept.map((id) => at.get(id)));
795
+ const keep = new Set(run.map((i) => b[i]));
796
+ return new Set(kept.filter((id) => !keep.has(id)));
797
+ };
798
+ const movedO = moved(o), movedT = moved(t);
799
+ const inB = new Set(b), inO = new Set(o), inT = new Set(t);
800
+ const before = (side, id) => side[side.indexOf(id) - 1];
801
+ let clash = false;
802
+ for (const id of movedT) if (movedO.has(id) && before(o, id) !== before(t, id)) clash = true;
803
+ const out = o.filter((id) => !(inB.has(id) && !inT.has(id)));
804
+ for (let i = 0; i < t.length; i++) {
805
+ const id = t[i];
806
+ const added = !inB.has(id) && !inO.has(id);
807
+ if (!added && !(movedT.has(id) && !movedO.has(id))) continue;
808
+ const from = out.indexOf(id);
809
+ if (from >= 0) out.splice(from, 1);
810
+ let after = -1;
811
+ for (let j = i - 1; j >= 0 && after < 0; j--) after = out.indexOf(t[j]);
812
+ out.splice(after + 1, 0, id);
813
+ }
814
+ if (clash) conflicts.push({ id: "", path, base: b, ours: o, theirs: t, kind: "moved" });
815
+ return out;
816
+ }
817
+ function longestRun(xs) {
818
+ const tails = [], prev = new Array(xs.length).fill(-1), tailAt = [];
819
+ xs.forEach((x, i) => {
820
+ let lo = 0, hi = tails.length;
821
+ while (lo < hi) {
822
+ const mid = lo + hi >> 1;
823
+ if (tails[mid] < x) lo = mid + 1;
824
+ else hi = mid;
825
+ }
826
+ tails[lo] = x;
827
+ tailAt[lo] = i;
828
+ prev[i] = lo > 0 ? tailAt[lo - 1] : -1;
829
+ });
830
+ const run = [];
831
+ for (let i = tailAt[tails.length - 1] ?? -1; i >= 0; i = prev[i]) run.unshift(xs[i]);
832
+ return run;
833
+ }
769
834
  function mergeProjectExtensions(b, o, t, conflicts) {
770
835
  const out = {};
771
836
  for (const id of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(o), ...Object.keys(t)])) {
@@ -1210,7 +1275,7 @@ import {
1210
1275
  } from "@patterkit/model";
1211
1276
 
1212
1277
  // src/loaded-helpers.ts
1213
- import { RERECORD_STATUS, effectiveGameId } from "@patterkit/model";
1278
+ import { RERECORD_STATUS, effectiveGameId, sceneFolderLayout as sceneFolderLayout2, sceneFolderPath } from "@patterkit/model";
1214
1279
  function effectiveRecording(id, base, rerecord, lowest) {
1215
1280
  return rerecord.has(id) ? RERECORD_STATUS : base.get(id) ?? lowest;
1216
1281
  }
@@ -1273,6 +1338,16 @@ function mergeAuthoring2(loaded) {
1273
1338
  }
1274
1339
  return { writing, recording, cut, rerecord, documentation, edits };
1275
1340
  }
1341
+ function sceneFolderPaths(loaded) {
1342
+ const out = /* @__PURE__ */ new Map();
1343
+ if (!loaded.project.sceneFolders?.length) return out;
1344
+ const layout = sceneFolderLayout2(loaded.project, loaded.scenes.map((s) => s.id));
1345
+ for (const s of loaded.scenes) {
1346
+ const path = sceneFolderPath(loaded.project, layout, s.id);
1347
+ if (path) out.set(s.id, path);
1348
+ }
1349
+ return out;
1350
+ }
1276
1351
 
1277
1352
  // src/documentation.ts
1278
1353
  import { DEFAULT_DOCUMENTATION_CLASSES } from "@patterkit/model";
@@ -2255,7 +2330,7 @@ function runValidate(loaded) {
2255
2330
  const unresolvedMerges = [...sidecarIssues(tree.get(CONFLICT_SIDECAR)), ...markedShards(SHARD_EXTENSIONS.flatMap((ext) => tree.get(ext)))];
2256
2331
  const orphans = orphanShards(loaded, tree);
2257
2332
  const extensions = [...extensionIssues(loaded), ...gitattributesHint(loaded)];
2258
- const structuralErrors = structural.filter((i) => i.severity !== "warning");
2333
+ const structuralErrors = structural.filter((i) => i.severity === void 0);
2259
2334
  const reachability = compiled && structuralErrors.length === 0 && !conditions.some(isError) ? reachabilityIssues(loaded, compiled) : [];
2260
2335
  return {
2261
2336
  structural,
@@ -2277,7 +2352,7 @@ function exportBlockers(loaded) {
2277
2352
  const { project, scenes, locales } = loaded;
2278
2353
  const merged = loaded.gameScopes?.merged;
2279
2354
  const foreignScopes = hostScopesToSpec2(projectScopes2(project, merged).host);
2280
- const structural = validateProject({ project, scenes, authoring: loaded.authoring }).filter((i) => i.severity !== "warning");
2355
+ const structural = validateProject({ project, scenes, authoring: loaded.authoring }).filter((i) => i.severity === void 0);
2281
2356
  const conditions = validateConditions({ project, scenes }, { foreignScopes, gameScopes: merged }).filter(isError);
2282
2357
  const interpolation = validateInterpolation({ project, scenes, locales }, { foreignScopes, gameScopes: merged }).filter(isError);
2283
2358
  return [
@@ -2629,8 +2704,7 @@ import { canonicalStringify as canonicalStringify5 } from "@patterkit/core";
2629
2704
  import { DEFAULT_PAD_AFTER } from "@patterkit/model";
2630
2705
 
2631
2706
  // src/playable-runtime.ts
2632
- var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var de=Object.defineProperty;var vt=Object.getOwnPropertyDescriptor;var xt=Object.getOwnPropertyNames;var kt=Object.prototype.hasOwnProperty;var Et=(r,e)=>{for(var t in e)de(r,t,{get:e[t],enumerable:!0})},Ct=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of xt(e))!kt.call(r,s)&&s!==t&&de(r,s,{get:()=>e[s],enumerable:!(n=vt(e,s))||n.enumerable});return r};var Dt=r=>Ct(de({},"__esModule",{value:!0}),r);var Zt={};Et(Zt,{Checkpoint:()=>K,DEFAULT_PAD_AFTER:()=>O,Engine:()=>ae,Flow:()=>N,buildTagIndex:()=>ie,compareStages:()=>B,describeBundle:()=>ut,effectiveGameData:()=>ft,extensionDecls:()=>ht,extensionsOf:()=>mt,gameDataFields:()=>gt,gameDataValue:()=>ke,nextStage:()=>F,resolveSceneRef:()=>ne,sceneAddressIndex:()=>ye,stageIndex:()=>A});function j(r,e){return r!==void 0&&Object.prototype.hasOwnProperty.call(r,e)?r[e]:void 0}function I(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:I(r[2])};case"bin":return{kind:"binary",op:r[1],left:I(r[2]),right:I(r[3])};case"call":{let e=r.slice(2).map(I);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 Y(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=j(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):j(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"&&!j(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=pe(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=j(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 g=s(i.left);if(typeof g!="boolean")throw new u(`\'and\' requires boolean operands, left is ${typeof g}`);if(!g)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 g=s(i.left);if(typeof g!="boolean")throw new u(`\'or\' requires boolean operands, left is ${typeof g}`);if(g)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=pe(i.left,e),l=pe(i.right,e),d=c??l;if(d!==void 0){if(c&&l&&!It(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 Pe(o,a);case"!=":return!Pe(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 Pe(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 pe(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 It=(r,e)=>r.length===e.length&&r.every((t,n)=>t===e[n]);function M(r){if(Number.isNaN(r)||!Number.isFinite(r))return 0;let e=Math.trunc(r)%4294967296;return e<0?e+4294967296:e}function ue(r){let e=M(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 fe=r=>Array.isArray(r)?r:r.stages??[],Rt=r=>Array.isArray(r)?"flags":r==null?"nothing":typeof r;function A(r,e){return typeof e=="string"?fe(r).indexOf(e):-1}function ge(r,e){if(typeof e!="string")throw new u(`a stage is a name, got ${Rt(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 B(r,e,t){let n=fe(r),s=ge(n,e)-ge(n,t);return s<0?-1:s>0?1:0}function F(r,e){let t=fe(r);return t[Math.min(ge(t,e)+1,t.length-1)]}function $(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 Ot={name:"check_flags",count:r=>Math.max(1,r.args.length-1)},Pt=[Ot];function Ne(r,e,t){let n=t?.countingCalls??Pt;return X(r,t?.want??!0,e,n)}function X(r,e,t,n){if(r.kind==="binary"&&(r.op==="and"||r.op==="or")){let s=X(r.left,e,t,n),i=X(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 X(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 E=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??L(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])=>Be(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);$(o,a,{noneChosen:o.type==="enum"&&o.default===""})||(this.values[i]=structuredClone(L(o)),s.push({name:i,value:a}))}return s}};function Be(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??L(r),...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:t??r.writable??!0}}var Nt=r=>r.toLowerCase(),he=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 E(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??Nt,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,...Be(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(At(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 Ae(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=Ae(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:he,scopes:this.save()}}loadFragment(e){if(e.version!==he)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${he})`);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 Ae(r){return r.kind==="foreign"?[...r.decls.entries()]:r.bag.declarations().map(e=>[r.bag.normalise(e.name),e])}function At(r){return r.kind==="foreign"?r.norm:e=>r.bag.normalise(e)}function L(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 Fe=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function G(r){return r.ctx.host??{}}var me={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=G(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:Te("check_flags"),eval(r,e){let t=Le(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:Te("set_flags"),eval(r,e){let t=[...Le(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:te("visits"),eval:(r,e)=>G(e).visits?.(ee(r,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:te("seen"),eval:(r,e)=>(G(e).visits?.(ee(r,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:te("patter_visits"),eval:(r,e)=>G(e).patterVisits?.(ee(r,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:te("patter_seen"),eval:(r,e)=>(G(e).patterVisits?.(ee(r,e,"patter_seen"))??0)>0}}},Rn=Fe.map(r=>r.token).filter(r=>r!=="patter"&&r!=="scene");function je(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 ee(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 te(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 Le(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 Te(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 Bt=/^@[A-Za-z0-9_.]+$/;function*Ft(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:Bt.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 Lt(r){return Array.isArray(r)?r.join(", "):typeof r=="boolean"?r?"true":"false":String(r)}function Tt(r){return r===" "||r===" "||r===`\n`||r==="\\r"||r==="\\f"||r==="\\v"}function jt(r){let e="",t=!1;for(let n of r){if(Tt(n)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=n}return e}function Me(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?jt(n):r}function $e(r,e){if(r.indexOf("{")<0)return r;let t="";for(let n of Ft(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?"":Lt(s)}return t}function Ge(r){return r.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function C(r,e){for(let t of r){e(t);let n=t.children;n&&C(n,e)}}function y(r){let e=r.gameId?.trim();return e||Ge(r.name)}function ye(r){let e=new Map;for(let[t,n]of Object.entries(r))e.set(y(n),t);return e}function ne(r,e,t){return(t??ye(r)).get(e)??(Object.hasOwn(r,e)?e:void 0)}var O=.6;function re(r){return`cast:${r}`}function se(r){return`qualifier:${r}`}function Ve(r,e){return`speaker:${r}:${e}`}var qe="speaker:pattern",_e="{name} ({qualifier})";function He(r,e,t){return r.split("{name}").map(n=>n.split("{qualifier}").join(t)).join(e)}var Se={open:"[",close:"]"},Ue="SFX";var V=4;function q(r){let e=new Set,t=[];for(let n of r)e.has(n)||(e.add(n),t.push(n));return t}function ie(r){let e=new Map,t=(n,s)=>{let i=q([...s,...n.tags??[]]);if(e.set(n.id,i),n.type==="group"&&n.prompt&&e.set(n.prompt.id,q([...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,q([...i,...o.tags??[]]))};for(let n of Object.values(r.scenes)){let s=q(n.tags??[]);e.set(n.id,s);for(let i of n.blocks){let o=q([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var We=r=>r.kind==="line"||r.kind==="text",_=r=>typeof r=="number"&&Number.isFinite(r)?r:void 0;function Ke(r){let e=new Map,t=(i,o,a)=>{let c=i.kind==="gameEvent"?void 0:_(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=_(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,g)=>{We(d)&&(l=g)}),c.forEach((d,g)=>{We(d)&&t(d,a,g===l)})},s=_(r.padAfterDefault)??O;for(let i of Object.values(r.scenes)){let o=_(i.padAfterDefault)??s;for(let a of i.blocks){let c=_(a.padAfterDefault)??o;for(let l of a.children)n(l,c)}}return e}var Je=new WeakMap,Qe=new WeakMap;function Mt(r){let e=Qe.get(r);return e||(e=ie(r),Qe.set(r,e)),e}var ze=new WeakMap;function $t(r){let e=ze.get(r);return e||(e=Ke(r),ze.set(r,e)),e}var Ye=new WeakMap;function Gt(r){let e=Ye.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.selector==="choice")for(let i of s.children)e.set(i.id,s.id)});return Ye.set(r,e),e}var Xe=new WeakMap;function Vt(r){let e=Xe.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 Xe.set(r,e),e}function b(r){if(r===void 0)return;let e={};for(let[t,n]of Object.entries(r))e[t]=Array.isArray(n)?[...n]:n;return e}function P(r,e){return r.tagIndex.get(e)?.slice()}var U="Patter",H=r=>r.replace(/%/g,"%25").replace(/\\//g,"%2F"),qt=r=>r.replace(/%2F/g,"/").replace(/%25/g,"%"),Ze=Symbol("ownsRegistry"),et=Symbol("setupReported"),w={stage:r=>`patter/scene/${H(r)}`,flow:r=>`patter/flow/${H(r)}/`,flowGlobals:r=>`patter/flow/${H(r)}/patter`,flowScene:(r,e)=>`patter/flow/${H(r)}/scene/${H(e)}`},K=class{constructor(e){this.journal=e}journal},ae=class r{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;requestedLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let n=[];for(let p of Object.keys(t))Ht.has(p)||n.push({node:p,message:p==="world"?\'the engine option "world" has gone: bind the scope as hostScopes: { world }\':`"${p}" isn\'t an engine option, so it\'s ignored`});let s=e.strings;this.allStrings=s,this.requestedLocale=t.locale,this.currentLocale=we(e,t.locale),this.currentLocale.problem&&n.push({node:t.locale,message:this.currentLocale.problem});let i=it(s,this.currentLocale),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 g=new Map;for(let p of e.cast??[])for(let[m,h]of Object.entries(p.qualifierLabels??{}))typeof h=="string"&&g.set(`${p.name}\\0${m}`,h);let f=e.speakerPattern??_e;this.defaultSeed=(t.seed??2654435769)>>>0;let T=new Map,Ee=new Map,Ce=new Map;for(let[p,m]of Object.entries(e.scenes)){this.sceneGameIdToId.set(y(m),p);let h=new Map;for(let S of m.blocks)Ee.set(S.id,{sceneId:p}),Ce.set(S.id,S),h.set(y(S),S.id),C(S.children,k=>T.set(k.id,k));this.blockGameIdToId.set(p,h)}let De=e.properties??[],ce=De.filter(p=>p.shared??!0).map(st),yt=De.filter(p=>!(p.shared??!0)).map(st),St=new Set(ce.map(p=>p.name.toLowerCase())),x=t.registry??new Z,Ie=!t.registry||t[Ze]===!0,Re=new E(ce,{pathPrefix:"@patter."}),le=[],Q=[];try{x.mountOwned("patter",Re,{owner:U}),Q.push("patter");let p=new Set,m=e.scopeRegistry?.scopes??[];for(let[h,S]of Object.entries(t.hostScopes??{})){let k=m.find(z=>z.token===h);if(!k&&!(e.externalScopes??[]).includes(h)){let z=m.find(bt=>bt.token.toLowerCase()===h.toLowerCase());n.push({node:h,message:z?`hostScopes binds "${h}", which this bundle doesn\'t declare: it declares "${z.token}", and a scope\'s token is case-sensitive`:`hostScopes binds "${h}", which this bundle doesn\'t declare`})}let wt=(k?.declarations??[]).map(dt);x.defineForeign(h,S,wt,{writable:k?.writable??!0,owner:U}),Q.push(h),p.add(h)}for(let h of Ie?e.scopeRegistry?.scopes??[]:[]){if(p.has(h.token)||x.has(h.token))continue;let S=(h.declarations??[]).map(k=>Kt(k,h.writable));x.defineOwned(h.token,S,{owner:U}),Q.push(h.token),le.push(h.token)}}catch(p){for(let m of Q)x.remove(m,{keep:!0});throw p}let Oe=new Map;for(let[p,m]of Object.entries(e.scenes)){let h=new Set((m.sceneProps??[]).filter(S=>S.shared??!1).map(S=>S.name.toLowerCase()));Oe.set(p,h)}if(this.host={logEnabled:t.log??!1,tracing:t.log??!1,emitEngine:(p,m,h)=>this.emitEngine(p,m,h),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,qualifierDisplay:d,speakerLabels:g,speakerPattern:f,nodeIndex:T,blockIndex:Ee,blockById:Ce,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:Mt(e),padIndex:$t(e),registry:x,ownsRegistry:Ie,patterBag:Re,hostScopes:le,patterSharedDecls:ce,patterLocalDecls:yt,patterSharedNames:St,sceneSharedNames:Oe,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(p.kind==="setup"?`Patterplay: ${p.message}`:p.kind==="save"?`Patterplay: flow \'${p.flow}\': ${p.message}`:`Patterplay: ${p.kind} on ${p.node} in flow \'${p.flow}\' failed, played through: ${p.message}`)),reportRefits:()=>{let p=x.takeMisfits(m=>m==="patter"||m.startsWith("patter/")||le.includes(m));if(p.length!==0)if(t.onRefit)t.onRefit(p);else for(let m of p)console.warn(`Patterplay: the save\'s ${m.scope}.${m.name} was ${JSON.stringify(m.value)}, which no longer fits its declaration; it is back at its default`)},replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??Se).open,captionClose:(e.closedCaptions??Se).close,captionCharacter:e.closedCaptions?.character||Ue,refSplitCache:new Map,refSplitRevision:x.revision},this.host.reportRefits(),t[et]!==!0)for(let p of n)this.reportSetup(p.node,p.message)}reportSetup(e,t){this.host.onError({flow:"",kind:"setup",node:e,message:t}),this.host.tracing&&this.emitEngine("",{type:"diagnostic",kind:"setup",node:e,message:t})}get locale(){return this.currentLocale.locale}get locales(){let{default:e,included:t}=this.host.bundle.locales;return{default:e,included:[...t]}}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.requestedLocale=e,this.useLocale(we(this.host.bundle,e)),this.currentLocale.problem&&this.reportSetup(e,this.currentLocale.problem)}useLocale(e){this.currentLocale=e,this.host.strings=it(this.allStrings,e)}replaceStrings(e){this.allStrings=e.strings;let t=this.currentLocale.problem;this.useLocale(we(e,this.requestedLocale)),this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{};let n=this.currentLocale.problem;n&&n!==t&&this.reportSetup(this.requestedLocale,n)}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),n=l=>(l.setClosedCaptions(this.host.captionsOn),l),s=this.requestedLocale,i=(l=!1)=>new r(e,{...this.creationOptions,...s!==void 0?{locale:s}:{},registry:this.host.registry,[Ze]:this.host.ownsRegistry,[et]:l}),o=new Set(["patter",...this.host.hostScopes]),a=this.host.ownsRegistry?void 0:Object.fromEntries(Object.entries(this.host.registry.save()).filter(([l])=>o.has(l)||l.startsWith("patter/")));this.release(!0),a&&this.host.registry.load(a,{keepParked:!0,refit:!0});let c=i();try{return c.loadGame(t),n(c)}catch{c.release(!0);let l=this.host.ownsRegistry?t.registry:a;l&&this.host.registry.load(l,{keepParked:!0,refit:!0});let d=i(!0);for(let[g,f]of Object.entries(t.flows)){let T=f.cursor.currentSceneId;try{d.openFlow(g,T!==null?{scene:T}:{})}catch{}}return n(d)}}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 N(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=be(this.host,e);if(n===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:n};let s=ve(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 be(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:ve(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 P(this.host,e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?P(this.host,t):void 0)??[]}tagsForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t);return(s!=null?P(this.host,s):void 0)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e),n=t!=null?this.host.bundle.scenes[t]:void 0;return b(n?.gameData)??{}}gameDataForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t),s=n!=null?this.host.blockById.get(n):void 0;return b(s?.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}}extensionsForSnippet(e){let t=this.host.nodeIndex.get(e);return t?.type==="snippet"?{...t.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)tt(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 tt(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,...nt(e.gameData),...D(e.extensions),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...y(t)?{gameId:y(t)}:{},name:t.name,...nt(t.gameData),...D(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=P(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[re(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[se(e.qualifier)]??this.host.qualifierDisplay.get(e.qualifier);i!==void 0&&(n.qualifierName=i)}let s=oe(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??O,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=b(e.gameData)),e.extensions&&Object.keys(e.extensions).length&&(n.extensions=e.extensions),t&&t.length&&(n.tags=t),n}tagsField(e){let t=P(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 K(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:V,...t.snapshot(),...Qt(e,this.host.registry.save())}}restoreFlow(e,t){this.refuseInCheckpoint("restoreFlow");let n=t.version;if(n!==3&&n!==V)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(Jt(e,t),{keepParked:!0,refit:!0});let o=new N(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(this.creationOptions.hostScopes??{}))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);lt(this.host,n,s,t,null);let 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 A(this.ladderOf(e),t)}compareStages(e,t,n){return B(this.ladderOf(e),t,n)}nextStage(e,t){return F(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitShared(e),s=Wt(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:L(e),writable:e.writable??!0}))}splitShared(e){let t=pt(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}saveGame(){let e={};for(let[t,n]of this.flowsById)e[t]=n.snapshot();return{version:V,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:at(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==3&&t!==V)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.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 ct(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new N(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}this.host.reportRefits()}},N=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=M(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 W(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(n);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:W(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(n)}return this.registryQualities?.(e,t)??W(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}begin(e,t){let n=this.host.bundle.start;if(e===void 0&&t===void 0&&n&&this.host.bundle.scenes[n.scene]&&(e=n.scene,n.block!==void 0&&this.host.blockIndex.get(n.block)?.sceneId===n.scene&&(t=n.block)),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 s=this.host.blockIndex.get(t);if(!s)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(s.sceneId),this.stack=[{sceneId:s.sceneId,containerId:t,index:0}],this.enter(t)}else{let s=e??Object.keys(this.host.bundle.scenes)[0],i=s?this.host.bundle.scenes[s]:void 0;if(!i)throw new Error(s?`unknown scene: ${s}`:"no scenes in bundle");this.enterSceneSetup(s);let o=i.blocks[0];o&&(this.stack=[{sceneId:s,containerId:o.id,index:0}],this.enter(o.id))}this.settle()}reset(e,t){this.begin(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=be(this.host,e);if(n===void 0)return!1;let s;return t!==void 0&&(s=ve(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,g=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:g})})}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.offer(this.pendingChoice)}: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=Vt(this.host.bundle).get(e);return t?this.beatResult(t):void 0}getChoices(){return this.pendingChoice?this.offer(this.pendingChoice):[]}offer(e){return e.options.map(t=>{let n=e.byId.get(t.id),s=this.promptFor(n);return e.shown.set(t.id,s),{id:t.id,prompt:s,eligible:t.eligible,gameData:b(t.gameData),...D(n.extensions)}})}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let n=t.options.find(a=>a.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,o=i?t.shown.has(e)?t.shown.get(e):this.promptFor(s):void 0;this.pendingPrompt=i&&o?{beat:i,shown:{...o}}: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 A(this.ladderOf(e),t)}compareStages(e,t,n){return B(this.ladderOf(e),t,n)}nextStage(e,t){return F(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);if(n){if(s==="scene"&&this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);lt(this.host,s,i,t,this.currentSceneId)}let 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(){let e=Gt(this.host.bundle),t=this.activeSnippet?.beats?.[this.beatIndex]?.id;return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(n=>{let s=this.childrenOf(n.containerId)?.[n.index],i=e.get(n.containerId);return{...n,...s?{nextId:s.id}:{},...i!==void 0?{choiceId:i}:{}}}),activeSnippetId:this.activeSnippet?.id??null,...t!==void 0?{beatId:t}:{},pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(n=>({id:n.id,eligible:n.eligible,...n.gameData!==void 0?{gameData:b(n.gameData)}:{}}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:_t(this.pendingPrompt.shown)}:{},selectors:at(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");let t=this.restoreCursor(e);this.releaseBags(!0),this.mountLocal();for(let n of new Set([this.currentSceneId,...this.stack.map(s=>s.sceneId)]))n!==null&&this.host.bundle.scenes[n]&&this.ensureSceneBags(n);t&&(this.report({flow:this.id,kind:"save",node:t.node,message:`the save\'s place has gone from this build: ${t.instead}`}),t.reoffer&&this.setupChoice(t.reoffer))}restoreCursor(e){this.rngState=M(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;this.started=!0,this.flowEnded=t.flowEnded,this.currentSceneId=t.currentSceneId;let n=null;this.stack=[];for(let s of t.stack){let{nextId:i,choiceId:o,...a}=s,c=this.childrenOf(a.containerId),l=!c&&o!==void 0?this.host.nodeIndex.get(o):void 0;if(l?.type==="group"&&l.selector==="choice"){n={node:t.activeSnippetId??t.pendingChoice?.groupId??a.containerId,instead:`option ${a.containerId} has gone, so choice ${l.id} is offered again`,reoffer:l};break}let d=i!==void 0?c?.findIndex(g=>g.id===i)??-1:-1;this.stack.push(d>=0?{...a,index:d}:{...a})}if(this.activeSnippet=null,this.beatIndex=0,!n?.reoffer&&t.activeSnippetId!==null){let s=this.host.nodeIndex.get(t.activeSnippetId);if(s&&s.type==="snippet"){let i=s.beats??[];if(this.activeSnippet=s,t.beatId!==void 0){let o=i.findIndex(a=>a.id===t.beatId);this.beatIndex=o>=0?o:i.length,o<0&&(n={node:t.activeSnippetId,instead:`beat ${t.beatId} has gone from snippet ${t.activeSnippetId}, so the snippet ends there`})}else this.beatIndex=t.beatIndex??i.length}else n={node:t.activeSnippetId,instead:`snippet ${t.activeSnippetId} has gone, so play carries on after it`}}if(this.selectors=ct(t.selectors),this.pendingChoice=null,!n?.reoffer&&t.pendingChoice!==null){let s=new Map,i=[];for(let o of t.pendingChoice.options){let a=this.host.nodeIndex.get(o.id);a&&(s.set(o.id,a),i.push({id:o.id,eligible:o.eligible,...o.gameData!==void 0?{gameData:o.gameData}:{}}))}if(i.length>0)this.pendingChoice={groupId:t.pendingChoice.groupId,options:i,byId:s,shown:new Map};else{let o=this.host.nodeIndex.get(t.pendingChoice.groupId);n=o?.type==="group"&&o.selector==="choice"?{node:t.pendingChoice.groupId,instead:`none of choice ${o.id}\'s options is left, so it\'s offered again`,reoffer:o}:{node:t.pendingChoice.groupId,instead:`choice ${t.pendingChoice.groupId} has gone, so play carries on after it`}}}if(this.pendingPrompt=null,this.pendingPromptOwnerId=n?.reoffer?null:t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let s=this.host.nodeIndex.get(this.pendingPromptOwnerId),i=s?this.authoredPromptOf(s):void 0,o=t.pendingPrompt,a=o?.kind==="line"&&o.speakerLabel===void 0?oe(this.host,o.character,o.qualifier):void 0;i?this.pendingPrompt={beat:i,...o?{shown:{...o,padAfter:o.padAfter??this.padOf(i.id),...a!==void 0?{speakerLabel:a}:{}}}:{}}:this.pendingPromptOwnerId=null}return n}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,eligible:a,...o.gameData!==void 0?{gameData:o.gameData}:{}}),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,shown:new Map};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 g=c[d];return n.bag.splice(n.bag.indexOf(g),1),n.last=g,e.find(f=>f.id===g)}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(g=>g.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 Ne(e,i=>{try{return ot(Y(i,this.context(),me))}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 ot(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.report({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i})}report(e){this.host.onError(e),this.host.tracing&&this.emit({type:"diagnostic",kind:e.kind,node:e.node,...e.source?{source:e.source}:{},message:e.message})}evalExpr(e){return Y(this.conditionAst(e),this.context(),me)}conditionAst(e){let t=Je.get(e);return t||(t=I(e.ast),Je.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=ue(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=P(this.host,e.id),n=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:b(e.gameData),...D(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:b(e.gameData),...D(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:oe(this.host,e.character,e.qualifier),padAfter:this.padOf(e.id),gameData:b(e.gameData),...D(e.extensions),...n}}}}interpolate(e){return $e(e,t=>this.getProperty(t))}stripCaptions(e){return Me(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:oe(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=P(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:b(e.gameData),...D(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:b(e.gameData),...D(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=re(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}padOf(e){return this.host.padIndex.get(e)?.resolved??O}resolveQualifierName(e){if(e===void 0||this.host.emitIds)return;let t=se(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.qualifierDisplay.get(e)}splitRef(e){return pt(this.host,e)}newLocal(){return new E(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=w.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:U}),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 E(n.filter(l=>!t.has(l.name.toLowerCase())),{pathPrefix:"@scene."}),a=w.flowScene(this.id,e),c=rt(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 E(n.filter(c=>t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),a=rt(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,L(n))}}};function tt(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 oe(r,e,t,n=!1){if(!e||r.emitIds)return;let s=c=>(n?void 0:r.strings[c])??r.defaultStrings[c],i=s(re(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(Ve(e,t))??o;let a=s(se(t))??r.qualifierDisplay.get(t)??t;return He(s(qe)??r.speakerPattern,i,a)}function nt(r){return r&&Object.keys(r).length?{gameData:b(r)}:{}}function D(r){return r&&Object.keys(r).length?{extensions:r}:{}}function _t(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 at(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 ct(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 rt(r,e,t,n){let s=n?JSON.stringify(t.save()):"";return r.mountOwned(e,t,{owner:U}),n&&JSON.stringify(t.save())!==s}function st(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default}}var Ht=new Set(["rng","seed","locale","hostScopes","registry","replayPromptOnChoose","closedCaptions","onDryChoice","onError","onRefit","log"]);function we(r,e){let t=r.locales.default;if(e===void 0)return{locale:t,table:t};if(r.localisation?.mode==="ids")return{locale:e,table:e};let n=[...new Set([t,...r.locales.included,...Object.keys(r.strings)])];if(n.includes(e))return{locale:e,table:e};let s=c=>c.toLowerCase().replace(/_/g,"-"),i=n.find(c=>s(c)===s(e));if(i!==void 0)return{locale:i,table:i,problem:`the locale "${e}" is spelled "${i}" in this bundle, so it\'s using "${i}"`};let o=s(e).split("-")[0],a=n.find(c=>s(c)===o);return a!==void 0?{locale:a,table:a,problem:`this bundle has no locale "${e}", so it\'s using its language, "${a}"`}:{locale:t,problem:`this bundle has no locale "${e}", so it\'s using the default, "${t}", with every string marked untranslated`}}function it(r,e){return e.table!==void 0?r[e.table]??{}:{}}function lt(r,e,t,n,s){let i=c=>c?.find(l=>l.name.toLowerCase()===t),o;if(e==="patter")o=i(r.bundle.properties);else if(e==="scene")o=i(s!==null?r.bundle.scenes[s]?.sceneProps:void 0);else{let c=r.bundle.scopeRegistry?.scopes.find(l=>l.token===e);if(!c||c.declarations===void 0)return;o=i(c.declarations)}let a=`@${e}.${t}`;if(o===void 0)throw new Error(`no property at "${a}": the content declares no ${a}`);if(!$(o,n,{noneChosen:o.type==="enum"&&o.default===""}))throw new Error(`"${a}" takes ${Ut(o)}; ${JSON.stringify(n)} doesn\'t fit`)}function Ut(r){let e=t=>(t??[]).map(n=>JSON.stringify(n)).join(", ");switch(r.type){case"number":return"a number";case"boolean":return"true or false";case"enum":return`one of ${e(r.values)}`;case"flags":return r.values!==void 0&&r.values.length>0?`a list of flags from ${e(r.values)}`:"a list of flags";case"quality":return`one of its stages ${e(r.stages)}`;default:return"text"}}function W(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 Wt(r,e,t){let n=t.toLowerCase();return e==="patter"?W(r,"patter",()=>[...r.patterSharedDecls,...r.patterLocalDecls]).get(n):r.registry.toEvalContext().qualities?.(e,t)??W(r,`host/${e}`,()=>r.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}function dt(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default,writable:r.writable}}function Kt(r,e){let t=dt(r),n=r.writable??e;return n===void 0?t:{...t,writable:n}}function be(r,e){return ne(r.bundle.scenes,e,r.sceneGameIdToId)}function ve(r,e,t){return r.blockGameIdToId.get(e)?.get(t)??(r.blockIndex.get(t)?.sceneId===e?t:void 0)}function pt(r,e){r.refSplitRevision!==r.registry.revision&&(r.refSplitCache.clear(),r.refSplitRevision=r.registry.revision);let t=r.refSplitCache.get(e);return t||(t=je(e,n=>n==="scene"||r.registry.has(n)||(r.bundle.externalScopes?.includes(n)??!1)),r.refSplitCache.set(e,t)),t}function Jt(r,e){let t={};e.scopes?.patter&&(t[w.flowGlobals(r)]=e.scopes.patter);for(let[n,s]of Object.entries(e.sceneBags??{}))t[w.flowScene(r,n)]=s;return t}function Qt(r,e){let t={scopes:{},sceneBags:{}},n=w.flow(r);for(let[s,i]of Object.entries(e)){if(!s.startsWith(n))continue;let o=s.slice(n.length);o==="patter"?t.scopes.patter=i:o.startsWith("scene/")&&(t.sceneBags[qt(o.slice(6))]=i)}return t}function ot(r){return typeof r=="boolean"?r:typeof r=="number"?r!==0:typeof r=="string"?r!=="":r.length>0}var zt=(r,e)=>r.shared??e;function xe(r,e){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.default!==void 0?{default:r.default}:{},shared:zt(r,e)}}function Yt(r){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.values?{values:[...r.values]}:{},...r.purpose?{purpose:r.purpose}:{}}}function Xt(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 ut(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)Xt(c,e);o.sceneProps?.length&&n.push({gameId:a,properties:o.sceneProps.map(c=>xe(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=>xe(a,!0))})),i=Object.entries(r.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Yt)}));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=>xe(o,!0)),scene:n},gameData:i,counts:e}}var J=(r,e)=>Object.prototype.hasOwnProperty.call(r,e);function gt(r,e){return r.gameDataFields?.[e]??[]}function ke(r,e,t){return e&&J(e,t)?e[t]:r.find(n=>n.name===t)?.default}function ft(r,e){let t={};for(let n of r){let s=ke(r,e,n.name);s!==void 0&&(t[n.name]=s)}for(let[n,s]of Object.entries(e??{}))J(t,n)||(t[n]=s);return t}function ht(r,e,t){let n=r.extensions!==void 0&&J(r.extensions,e)?r.extensions[e]:void 0;return n!==void 0&&J(n.fields,t)?n.fields[t]??[]:[]}function mt(r,e,t=""){let n=o=>({...o?.extensions??{}}),s=Object.values(r.scenes??{}),i=o=>(J(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 g=(d.beats??[]).find(f=>f.id===t);if(g!==void 0){g.kind===e&&(o=g);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 Dt(Zt);})();\n';
2633
- 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';
2707
+ var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var pe=Object.defineProperty;var xt=Object.getOwnPropertyDescriptor;var kt=Object.getOwnPropertyNames;var Et=Object.prototype.hasOwnProperty;var Dt=(r,e)=>{for(var t in e)pe(r,t,{get:e[t],enumerable:!0})},Ct=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of kt(e))!Et.call(r,s)&&s!==t&&pe(r,s,{get:()=>e[s],enumerable:!(n=xt(e,s))||n.enumerable});return r};var It=r=>Ct(pe({},"__esModule",{value:!0}),r);var rn={};Dt(rn,{Checkpoint:()=>K,DEFAULT_PAD_AFTER:()=>O,Engine:()=>ce,Flow:()=>A,buildTagIndex:()=>oe,compareStages:()=>F,describeBundle:()=>gt,effectiveGameData:()=>ht,estimateLineDuration:()=>Ve,extensionDecls:()=>mt,extensionsOf:()=>yt,gameDataFields:()=>ft,gameDataValue:()=>Ee,nextStage:()=>L,resolveSceneRef:()=>re,sceneAddressIndex:()=>Se,stageIndex:()=>B});function M(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 Y(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=M(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):M(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"&&!M(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=ue(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=M(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 g=s(i.left);if(typeof g!="boolean")throw new u(`\'and\' requires boolean operands, left is ${typeof g}`);if(!g)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 g=s(i.left);if(typeof g!="boolean")throw new u(`\'or\' requires boolean operands, left is ${typeof g}`);if(g)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=ue(i.left,e),l=ue(i.right,e),d=c??l;if(d!==void 0){if(c&&l&&!Rt(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 Oe(o,a);case"!=":return!Oe(o,a);case">":return P(o,a,">"),o>a;case">=":return P(o,a,">="),o>=a;case"<":return P(o,a,"<"),o<a;case"<=":return P(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 P(o,a,"-"),o-a;case"*":return P(o,a,"*"),o*a;case"/":if(P(o,a,"/"),a===0)throw new u("division by zero");return o/a}}}};return s(r)}function Oe(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 P(r,e,t){if(typeof r!="number"||typeof e!="number")throw new u(`\'${t}\' requires numeric operands, got ${typeof r} and ${typeof e}`)}function ue(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 Rt=(r,e)=>r.length===e.length&&r.every((t,n)=>t===e[n]);function j(r){if(Number.isNaN(r)||!Number.isFinite(r))return 0;let e=Math.trunc(r)%4294967296;return e<0?e+4294967296:e}function ge(r){let e=j(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 he=r=>Array.isArray(r)?r:r.stages??[],Pt=r=>Array.isArray(r)?"flags":r==null?"nothing":typeof r;function B(r,e){return typeof e=="string"?he(r).indexOf(e):-1}function fe(r,e){if(typeof e!="string")throw new u(`a stage is a name, got ${Pt(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 F(r,e,t){let n=he(r),s=fe(n,e)-fe(n,t);return s<0?-1:s>0?1:0}function L(r,e){let t=he(r);return t[Math.min(fe(t,e)+1,t.length-1)]}function $(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 Ot={name:"check_flags",count:r=>Math.max(1,r.args.length-1)},Nt=[Ot];function Ne(r,e,t){let n=t?.countingCalls??Nt;return Z(r,t?.want??!0,e,n)}function Z(r,e,t,n){if(r.kind==="binary"&&(r.op==="and"||r.op==="or")){let s=Z(r.left,e,t,n),i=Z(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 Z(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 E=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??T(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])=>Be(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);$(o,a,{noneChosen:o.type==="enum"&&o.default===""})||(this.values[i]=structuredClone(T(o)),s.push({name:i,value:a}))}return s}};function Be(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??T(r),...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:t??r.writable??!0}}var At=r=>r.toLowerCase(),me=1,ee=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 E(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??At,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,...Be(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(Bt(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 Ae(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=Ae(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:me,scopes:this.save()}}loadFragment(e){if(e.version!==me)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${me})`);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 Ae(r){return r.kind==="foreign"?[...r.decls.entries()]:r.bag.declarations().map(e=>[r.bag.normalise(e.name),e])}function Bt(r){return r.kind==="foreign"?r.norm:e=>r.bag.normalise(e)}function T(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 Fe=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function G(r){return r.ctx.host??{}}var ye={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=G(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:Te("check_flags"),eval(r,e){let t=Le(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:Te("set_flags"),eval(r,e){let t=[...Le(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:ne("visits"),eval:(r,e)=>G(e).visits?.(te(r,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:ne("seen"),eval:(r,e)=>(G(e).visits?.(te(r,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:ne("patter_visits"),eval:(r,e)=>G(e).patterVisits?.(te(r,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:ne("patter_seen"),eval:(r,e)=>(G(e).patterVisits?.(te(r,e,"patter_seen"))??0)>0}}},An=Fe.map(r=>r.token).filter(r=>r!=="patter"&&r!=="scene");function Me(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 te(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 ne(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 Le(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 Te(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 Ft=/^@[A-Za-z0-9_.]+$/;function*Lt(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:Ft.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 Tt(r){return Array.isArray(r)?r.join(", "):typeof r=="boolean"?r?"true":"false":String(r)}function Mt(r){return r===" "||r===" "||r===`\n`||r==="\\r"||r==="\\f"||r==="\\v"}function jt(r){let e="",t=!1;for(let n of r){if(Mt(n)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=n}return e}function je(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?jt(n):r}function $e(r,e){if(r.indexOf("{")<0)return r;let t="";for(let n of Lt(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?"":Tt(s)}return t}function Ge(r){return r.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function D(r,e){for(let t of r){e(t);let n=t.children;n&&D(n,e)}}function y(r){let e=r.gameId?.trim();return e||Ge(r.name)}function Se(r){let e=new Map;for(let[t,n]of Object.entries(r))e.set(y(n),t);return e}function re(r,e,t){return(t??Se(r)).get(e)??(Object.hasOwn(r,e)?e:void 0)}var O=.6;var $t=14,Gt=1.5,Vt=18;function Ve(r,e){let t=0;for(let n of r??"")n!=="\\xAD"&&t++;return Math.min(Math.max(t/$t,Gt),Vt)}function se(r){return`cast:${r}`}function ie(r){return`qualifier:${r}`}function qe(r,e){return`speaker:${r}:${e}`}var _e="speaker:pattern",He="{name} ({qualifier})";function Ue(r,e,t){return r.split("{name}").map(n=>n.split("{qualifier}").join(t)).join(e)}var we={open:"[",close:"]"},We="SFX";var V=4;function q(r){let e=new Set,t=[];for(let n of r)e.has(n)||(e.add(n),t.push(n));return t}function oe(r){let e=new Map,t=(n,s)=>{let i=q([...s,...n.tags??[]]);if(e.set(n.id,i),n.type==="group"&&n.prompt&&e.set(n.prompt.id,q([...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,q([...i,...o.tags??[]]))};for(let n of Object.values(r.scenes)){let s=q(n.tags??[]);e.set(n.id,s);for(let i of n.blocks){let o=q([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var Ke=r=>r.kind==="line"||r.kind==="text",_=r=>typeof r=="number"&&Number.isFinite(r)?r:void 0;function Je(r){let e=new Map,t=(i,o,a)=>{let c=i.kind==="gameEvent"?void 0:_(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=_(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,g)=>{Ke(d)&&(l=g)}),c.forEach((d,g)=>{Ke(d)&&t(d,a,g===l)})},s=_(r.padAfterDefault)??O;for(let i of Object.values(r.scenes)){let o=_(i.padAfterDefault)??s;for(let a of i.blocks){let c=_(a.padAfterDefault)??o;for(let l of a.children)n(l,c)}}return e}var Qe=new WeakMap,ze=new WeakMap;function qt(r){let e=ze.get(r);return e||(e=oe(r),ze.set(r,e)),e}var Xe=new WeakMap;function _t(r){let e=Xe.get(r);return e||(e=Je(r),Xe.set(r,e)),e}var Ye=new WeakMap;function Ht(r){let e=Ye.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.selector==="choice")for(let i of s.children)e.set(i.id,s.id)});return Ye.set(r,e),e}var Ze=new WeakMap;function Ut(r){let e=Ze.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 Ze.set(r,e),e}function b(r){if(r===void 0)return;let e={};for(let[t,n]of Object.entries(r))e[t]=Array.isArray(n)?[...n]:n;return e}function N(r,e){return r.tagIndex.get(e)?.slice()}var U="Patter",H=r=>r.replace(/%/g,"%25").replace(/\\//g,"%2F"),Wt=r=>r.replace(/%2F/g,"/").replace(/%25/g,"%"),et=Symbol("ownsRegistry"),tt=Symbol("setupReported"),w={stage:r=>`patter/scene/${H(r)}`,flow:r=>`patter/flow/${H(r)}/`,flowGlobals:r=>`patter/flow/${H(r)}/patter`,flowScene:(r,e)=>`patter/flow/${H(r)}/scene/${H(e)}`},K=class{constructor(e){this.journal=e}journal},ce=class r{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;requestedLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let n=[];for(let p of Object.keys(t))Jt.has(p)||n.push({node:p,message:p==="world"?\'the engine option "world" has gone: bind the scope as hostScopes: { world }\':`"${p}" isn\'t an engine option, so it\'s ignored`});let s=e.strings;this.allStrings=s,this.requestedLocale=t.locale,this.currentLocale=be(e,t.locale),this.currentLocale.problem&&n.push({node:t.locale,message:this.currentLocale.problem});let i=ot(s,this.currentLocale),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 g=new Map;for(let p of e.cast??[])for(let[m,h]of Object.entries(p.qualifierLabels??{}))typeof h=="string"&&g.set(`${p.name}\\0${m}`,h);let f=e.speakerPattern??He;this.defaultSeed=(t.seed??2654435769)>>>0;let I=new Map,Q=new Map,De=new Map;for(let[p,m]of Object.entries(e.scenes)){this.sceneGameIdToId.set(y(m),p);let h=new Map;for(let S of m.blocks)Q.set(S.id,{sceneId:p}),De.set(S.id,S),h.set(y(S),S.id),D(S.children,k=>I.set(k.id,k));this.blockGameIdToId.set(p,h)}let Ce=e.properties??[],le=Ce.filter(p=>p.shared??!0).map(it),St=Ce.filter(p=>!(p.shared??!0)).map(it),wt=new Set(le.map(p=>p.name.toLowerCase())),x=t.registry??new ee,Ie=!t.registry||t[et]===!0,Re=new E(le,{pathPrefix:"@patter."}),de=[],z=[];try{x.mountOwned("patter",Re,{owner:U}),z.push("patter");let p=new Set,m=e.scopeRegistry?.scopes??[];for(let[h,S]of Object.entries(t.hostScopes??{})){let k=m.find(X=>X.token===h);if(!k&&!(e.externalScopes??[]).includes(h)){let X=m.find(vt=>vt.token.toLowerCase()===h.toLowerCase());n.push({node:h,message:X?`hostScopes binds "${h}", which this bundle doesn\'t declare: it declares "${X.token}", and a scope\'s token is case-sensitive`:`hostScopes binds "${h}", which this bundle doesn\'t declare`})}let bt=(k?.declarations??[]).map(pt);x.defineForeign(h,S,bt,{writable:k?.writable??!0,owner:U}),z.push(h),p.add(h)}for(let h of Ie?e.scopeRegistry?.scopes??[]:[]){if(p.has(h.token)||x.has(h.token))continue;let S=(h.declarations??[]).map(k=>Xt(k,h.writable));x.defineOwned(h.token,S,{owner:U}),z.push(h.token),de.push(h.token)}}catch(p){for(let m of z)x.remove(m,{keep:!0});throw p}let Pe=new Map;for(let[p,m]of Object.entries(e.scenes)){let h=new Set((m.sceneProps??[]).filter(S=>S.shared??!1).map(S=>S.name.toLowerCase()));Pe.set(p,h)}if(this.host={logEnabled:t.log??!1,tracing:t.log??!1,emitEngine:(p,m,h)=>this.emitEngine(p,m,h),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,qualifierDisplay:d,speakerLabels:g,speakerPattern:f,nodeIndex:I,blockIndex:Q,blockById:De,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:qt(e),padIndex:_t(e),registry:x,ownsRegistry:Ie,patterBag:Re,hostScopes:de,patterSharedDecls:le,patterLocalDecls:St,patterSharedNames:wt,sceneSharedNames:Pe,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(p.kind==="setup"?`Patterplay: ${p.message}`:p.kind==="save"?`Patterplay: flow \'${p.flow}\': ${p.message}`:`Patterplay: ${p.kind} on ${p.node} in flow \'${p.flow}\' failed, played through: ${p.message}`)),reportRefits:()=>{let p=x.takeMisfits(m=>m==="patter"||m.startsWith("patter/")||de.includes(m));if(p.length!==0)if(t.onRefit)t.onRefit(p);else for(let m of p)console.warn(`Patterplay: the save\'s ${m.scope}.${m.name} was ${JSON.stringify(m.value)}, which no longer fits its declaration; it is back at its default`)},replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??we).open,captionClose:(e.closedCaptions??we).close,captionCharacter:e.closedCaptions?.character||We,refSplitCache:new Map,refSplitRevision:x.revision},this.host.reportRefits(),t[tt]!==!0)for(let p of n)this.reportSetup(p.node,p.message)}reportSetup(e,t){this.host.onError({flow:"",kind:"setup",node:e,message:t}),this.host.tracing&&this.emitEngine("",{type:"diagnostic",kind:"setup",node:e,message:t})}get locale(){return this.currentLocale.locale}get locales(){let{default:e,included:t}=this.host.bundle.locales;return{default:e,included:[...t]}}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.requestedLocale=e,this.useLocale(be(this.host.bundle,e)),this.currentLocale.problem&&this.reportSetup(e,this.currentLocale.problem)}useLocale(e){this.currentLocale=e,this.host.strings=ot(this.allStrings,e)}replaceStrings(e){this.allStrings=e.strings;let t=this.currentLocale.problem;this.useLocale(be(e,this.requestedLocale)),this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{};let n=this.currentLocale.problem;n&&n!==t&&this.reportSetup(this.requestedLocale,n)}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),n=l=>(l.setClosedCaptions(this.host.captionsOn),l),s=this.requestedLocale,i=(l=!1)=>new r(e,{...this.creationOptions,...s!==void 0?{locale:s}:{},registry:this.host.registry,[et]:this.host.ownsRegistry,[tt]:l}),o=new Set(["patter",...this.host.hostScopes]),a=this.host.ownsRegistry?void 0:Object.fromEntries(Object.entries(this.host.registry.save()).filter(([l])=>o.has(l)||l.startsWith("patter/")));this.release(!0),a&&this.host.registry.load(a,{keepParked:!0,refit:!0});let c=i();try{return c.loadGame(t),n(c)}catch{c.release(!0);let l=this.host.ownsRegistry?t.registry:a;l&&this.host.registry.load(l,{keepParked:!0,refit:!0});let d=i(!0);for(let[g,f]of Object.entries(t.flows)){let I=f.cursor.currentSceneId;try{d.openFlow(g,I!==null?{scene:I}:{})}catch{}}return n(d)}}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 A(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=ve(this.host,e);if(n===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:n};let s=xe(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 ve(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:xe(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 N(this.host,e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?N(this.host,t):void 0)??[]}tagsForBlock(e,t){let n=this.resolveSceneRef(e),s=this.resolveBlockRef(n,t);return(s!=null?N(this.host,s):void 0)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e),n=t!=null?this.host.bundle.scenes[t]:void 0;return b(n?.gameData)??{}}gameDataForBlock(e,t){let n=this.resolveBlockRef(this.resolveSceneRef(e),t),s=n!=null?this.host.blockById.get(n):void 0;return b(s?.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}}extensionsForSnippet(e){let t=this.host.nodeIndex.get(e);return t?.type==="snippet"?{...t.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)nt(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 nt(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,...rt(e.gameData),...C(e.extensions),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...y(t)?{gameId:y(t)}:{},name:t.name,...rt(t.gameData),...C(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=N(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[se(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[ie(e.qualifier)]??this.host.qualifierDisplay.get(e.qualifier);i!==void 0&&(n.qualifierName=i)}let s=ae(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??O,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=b(e.gameData)),e.extensions&&Object.keys(e.extensions).length&&(n.extensions=e.extensions),t&&t.length&&(n.tags=t),n}tagsField(e){let t=N(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 K(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:V,...t.snapshot(),...Zt(e,this.host.registry.save())}}restoreFlow(e,t){this.refuseInCheckpoint("restoreFlow");let n=t.version;if(n!==3&&n!==V)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(Yt(e,t),{keepParked:!0,refit:!0});let o=new A(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(this.creationOptions.hostScopes??{}))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);dt(this.host,n,s,t,null);let 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 B(this.ladderOf(e),t)}compareStages(e,t,n){return F(this.ladderOf(e),t,n)}nextStage(e,t){return L(this.ladderOf(e),t)}ladderOf(e){let{scope:t,name:n}=this.splitShared(e),s=zt(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:T(e),writable:e.writable??!0}))}splitShared(e){let t=ut(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}saveGame(){let e={};for(let[t,n]of this.flowsById)e[t]=n.snapshot();return{version:V,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:ct(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==3&&t!==V)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.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 lt(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new A(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}this.host.reportRefits()}},A=class{id;host;local;registered=new Set;rngState;started=!1;flowEnded=!1;dryChoice=null;ranDry=null;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=j(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 W(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(n);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:W(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(n)}return this.registryQualities?.(e,t)??W(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}begin(e,t){let n=this.host.bundle.start;if(e===void 0&&t===void 0&&n&&this.host.bundle.scenes[n.scene]&&(e=n.scene,n.block!==void 0&&this.host.blockIndex.get(n.block)?.sceneId===n.scene&&(t=n.block)),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.dryChoice=null,this.ranDry=null,this.activeSnippet=null,this.beatIndex=0,this.clearPending(),this.started=!0,t){let s=this.host.blockIndex.get(t);if(!s)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(s.sceneId),this.stack=[{sceneId:s.sceneId,containerId:t,index:0}],this.enter(t)}else{let s=e??Object.keys(this.host.bundle.scenes)[0],i=s?this.host.bundle.scenes[s]:void 0;if(!i)throw new Error(s?`unknown scene: ${s}`:"no scenes in bundle");this.enterSceneSetup(s);let o=i.blocks[0];o&&(this.stack=[{sceneId:s,containerId:o.id,index:0}],this.enter(o.id))}this.settle()}reset(e,t){this.begin(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=[],this.dryChoice=null,this.ranDry=null,!0;let n=ve(this.host,e);if(n===void 0)return!1;let s;return t!==void 0&&(s=xe(this.host,n,t),s===void 0)?!1:this.started?(this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.dryChoice=null,this.ranDry=null,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,dryChoice:i,ranDry:o,currentSceneId:a,activeSnippet:c,beatIndex:l,pendingChoice:d,pendingPrompt:g,pendingPromptOwnerId:f}=this,I=this.stack.map(Q=>({...Q}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:n,flowEnded:s,dryChoice:i,ranDry:o,currentSceneId:a,activeSnippet:c,beatIndex:l,pendingChoice:d,pendingPrompt:g,pendingPromptOwnerId:f,stack:I})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end",reason:"ended"};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&&!this.activeSnippet&&!this.pendingChoice&&(this.flowEnded=!0),this.flowEnded?this.endResult():(this.dryChoice=null,this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.offer(this.pendingChoice)}:this.beatResult(this.activeSnippet.beats[this.beatIndex++]))}endResult(){if(this.dryChoice!==null){let e=this.dryChoice;this.dryChoice=null,this.ranDry=e,this.report({flow:this.id,kind:"dry-choice",node:e,message:`choice \'${e}\' ran dry (no option left to take, and no fallback) with nothing after it, so the flow ended there`})}return{type:"end",reason:this.ranDry!==null?"ran-dry":"ended"}}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=Ut(this.host.bundle).get(e);return t?this.beatResult(t):void 0}getChoices(){return this.pendingChoice?this.offer(this.pendingChoice):[]}offer(e){return e.options.map(t=>{let n=e.byId.get(t.id),s=this.promptFor(n);return e.shown.set(t.id,s),{id:t.id,prompt:s,eligible:t.eligible,gameData:b(t.gameData),...C(n.extensions)}})}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let n=t.options.find(a=>a.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,o=i?t.shown.has(e)?t.shown.get(e):this.promptFor(s):void 0;this.pendingPrompt=i&&o?{beat:i,shown:{...o}}: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 B(this.ladderOf(e),t)}compareStages(e,t,n){return F(this.ladderOf(e),t,n)}nextStage(e,t){return L(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);if(n){if(s==="scene"&&this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);dt(this.host,s,i,t,this.currentSceneId)}let 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(){let e=Ht(this.host.bundle),t=this.activeSnippet?.beats?.[this.beatIndex]?.id;return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,...this.ranDry!==null?{ranDry:this.ranDry}:{},currentSceneId:this.currentSceneId,stack:this.stack.map(n=>{let s=this.childrenOf(n.containerId)?.[n.index],i=e.get(n.containerId);return{...n,...s?{nextId:s.id}:{},...i!==void 0?{choiceId:i}:{}}}),activeSnippetId:this.activeSnippet?.id??null,...t!==void 0?{beatId:t}:{},pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(n=>({id:n.id,eligible:n.eligible,...n.gameData!==void 0?{gameData:b(n.gameData)}:{}}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:Kt(this.pendingPrompt.shown)}:{},selectors:ct(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");let t=this.restoreCursor(e);this.releaseBags(!0),this.mountLocal();for(let n of new Set([this.currentSceneId,...this.stack.map(s=>s.sceneId)]))n!==null&&this.host.bundle.scenes[n]&&this.ensureSceneBags(n);t&&(this.report({flow:this.id,kind:"save",node:t.node,message:`the save\'s place has gone from this build: ${t.instead}`}),t.reoffer&&this.setupChoice(t.reoffer))}restoreCursor(e){this.rngState=j(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;this.started=!0,this.flowEnded=t.flowEnded,this.dryChoice=null,this.ranDry=t.flowEnded&&typeof t.ranDry=="string"?t.ranDry:null,this.currentSceneId=t.currentSceneId;let n=null;this.stack=[];for(let s of t.stack){let{nextId:i,choiceId:o,...a}=s,c=this.childrenOf(a.containerId),l=!c&&o!==void 0?this.host.nodeIndex.get(o):void 0;if(l?.type==="group"&&l.selector==="choice"){n={node:t.activeSnippetId??t.pendingChoice?.groupId??a.containerId,instead:`option ${a.containerId} has gone, so choice ${l.id} is offered again`,reoffer:l};break}let d=i!==void 0?c?.findIndex(g=>g.id===i)??-1:-1;this.stack.push(d>=0?{...a,index:d}:{...a})}if(this.activeSnippet=null,this.beatIndex=0,!n?.reoffer&&t.activeSnippetId!==null){let s=this.host.nodeIndex.get(t.activeSnippetId);if(s&&s.type==="snippet"){let i=s.beats??[];if(this.activeSnippet=s,t.beatId!==void 0){let o=i.findIndex(a=>a.id===t.beatId);this.beatIndex=o>=0?o:i.length,o<0&&(n={node:t.activeSnippetId,instead:`beat ${t.beatId} has gone from snippet ${t.activeSnippetId}, so the snippet ends there`})}else this.beatIndex=t.beatIndex??i.length}else n={node:t.activeSnippetId,instead:`snippet ${t.activeSnippetId} has gone, so play carries on after it`}}if(this.selectors=lt(t.selectors),this.pendingChoice=null,!n?.reoffer&&t.pendingChoice!==null){let s=new Map,i=[];for(let o of t.pendingChoice.options){let a=this.host.nodeIndex.get(o.id);a&&(s.set(o.id,a),i.push({id:o.id,eligible:o.eligible,...o.gameData!==void 0?{gameData:o.gameData}:{}}))}if(i.length>0)this.pendingChoice={groupId:t.pendingChoice.groupId,options:i,byId:s,shown:new Map};else{let o=this.host.nodeIndex.get(t.pendingChoice.groupId);n=o?.type==="group"&&o.selector==="choice"?{node:t.pendingChoice.groupId,instead:`none of choice ${o.id}\'s options is left, so it\'s offered again`,reoffer:o}:{node:t.pendingChoice.groupId,instead:`choice ${t.pendingChoice.groupId} has gone, so play carries on after it`}}}if(this.pendingPrompt=null,this.pendingPromptOwnerId=n?.reoffer?null:t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let s=this.host.nodeIndex.get(this.pendingPromptOwnerId),i=s?this.authoredPromptOf(s):void 0,o=t.pendingPrompt,a=o?.kind==="line"&&o.speakerLabel===void 0?ae(this.host,o.character,o.qualifier):void 0;i?this.pendingPrompt={beat:i,...o?{shown:{...o,padAfter:o.padAfter??this.padOf(i.id),...a!==void 0?{speakerLabel:a}:{}}}:{}}:this.pendingPromptOwnerId=null}return n}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,eligible:a,...o.gameData!==void 0?{gameData:o.gameData}:{}}),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,shown:new Map};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),this.dryChoice=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 g=c[d];return n.bag.splice(n.bag.indexOf(g),1),n.last=g,e.find(f=>f.id===g)}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(g=>g.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 Ne(e,i=>{try{return at(Y(i,this.context(),ye))}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 at(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.report({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i})}report(e){this.host.onError(e),this.host.tracing&&this.emit({type:"diagnostic",kind:e.kind,node:e.node,...e.source?{source:e.source}:{},message:e.message})}evalExpr(e){return Y(this.conditionAst(e),this.context(),ye)}conditionAst(e){let t=Qe.get(e);return t||(t=R(e.ast),Qe.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=ge(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=N(this.host,e.id),n=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:b(e.gameData),...C(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:b(e.gameData),...C(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:ae(this.host,e.character,e.qualifier),padAfter:this.padOf(e.id),gameData:b(e.gameData),...C(e.extensions),...n}}}}interpolate(e){return $e(e,t=>this.getProperty(t))}stripCaptions(e){return je(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:ae(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=N(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:b(e.gameData),...C(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:b(e.gameData),...C(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=se(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}padOf(e){return this.host.padIndex.get(e)?.resolved??O}resolveQualifierName(e){if(e===void 0||this.host.emitIds)return;let t=ie(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.qualifierDisplay.get(e)}splitRef(e){return ut(this.host,e)}newLocal(){return new E(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=w.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:U}),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 E(n.filter(l=>!t.has(l.name.toLowerCase())),{pathPrefix:"@scene."}),a=w.flowScene(this.id,e),c=st(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 E(n.filter(c=>t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),a=st(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,T(n))}}};function nt(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 ae(r,e,t,n=!1){if(!e||r.emitIds)return;let s=c=>(n?void 0:r.strings[c])??r.defaultStrings[c],i=s(se(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(qe(e,t))??o;let a=s(ie(t))??r.qualifierDisplay.get(t)??t;return Ue(s(_e)??r.speakerPattern,i,a)}function rt(r){return r&&Object.keys(r).length?{gameData:b(r)}:{}}function C(r){return r&&Object.keys(r).length?{extensions:r}:{}}function Kt(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 ct(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 lt(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 st(r,e,t,n){let s=n?JSON.stringify(t.save()):"";return r.mountOwned(e,t,{owner:U}),n&&JSON.stringify(t.save())!==s}function it(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default}}var Jt=new Set(["rng","seed","locale","hostScopes","registry","replayPromptOnChoose","closedCaptions","onDryChoice","onError","onRefit","log"]);function be(r,e){let t=r.locales.default;if(e===void 0)return{locale:t,table:t};if(r.localisation?.mode==="ids")return{locale:e,table:e};let n=[...new Set([t,...r.locales.included,...Object.keys(r.strings)])];if(n.includes(e))return{locale:e,table:e};let s=c=>c.toLowerCase().replace(/_/g,"-"),i=n.find(c=>s(c)===s(e));if(i!==void 0)return{locale:i,table:i,problem:`the locale "${e}" is spelled "${i}" in this bundle, so it\'s using "${i}"`};let o=s(e).split("-")[0],a=n.find(c=>s(c)===o);return a!==void 0?{locale:a,table:a,problem:`this bundle has no locale "${e}", so it\'s using its language, "${a}"`}:{locale:t,problem:`this bundle has no locale "${e}", so it\'s using the default, "${t}", with every string marked untranslated`}}function ot(r,e){return e.table!==void 0?r[e.table]??{}:{}}function dt(r,e,t,n,s){let i=c=>c?.find(l=>l.name.toLowerCase()===t),o;if(e==="patter")o=i(r.bundle.properties);else if(e==="scene")o=i(s!==null?r.bundle.scenes[s]?.sceneProps:void 0);else{let c=r.bundle.scopeRegistry?.scopes.find(l=>l.token===e);if(!c||c.declarations===void 0)return;o=i(c.declarations)}let a=`@${e}.${t}`;if(o===void 0)throw new Error(`no property at "${a}": the content declares no ${a}`);if(!$(o,n,{noneChosen:o.type==="enum"&&o.default===""}))throw new Error(`"${a}" takes ${Qt(o)}; ${JSON.stringify(n)} doesn\'t fit`)}function Qt(r){let e=t=>(t??[]).map(n=>JSON.stringify(n)).join(", ");switch(r.type){case"number":return"a number";case"boolean":return"true or false";case"enum":return`one of ${e(r.values)}`;case"flags":return r.values!==void 0&&r.values.length>0?`a list of flags from ${e(r.values)}`:"a list of flags";case"quality":return`one of its stages ${e(r.stages)}`;default:return"text"}}function W(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 zt(r,e,t){let n=t.toLowerCase();return e==="patter"?W(r,"patter",()=>[...r.patterSharedDecls,...r.patterLocalDecls]).get(n):r.registry.toEvalContext().qualities?.(e,t)??W(r,`host/${e}`,()=>r.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(n)}function pt(r){return{name:r.name,type:r.type,values:r.values,stages:r.stages,default:r.default,writable:r.writable}}function Xt(r,e){let t=pt(r),n=r.writable??e;return n===void 0?t:{...t,writable:n}}function ve(r,e){return re(r.bundle.scenes,e,r.sceneGameIdToId)}function xe(r,e,t){return r.blockGameIdToId.get(e)?.get(t)??(r.blockIndex.get(t)?.sceneId===e?t:void 0)}function ut(r,e){r.refSplitRevision!==r.registry.revision&&(r.refSplitCache.clear(),r.refSplitRevision=r.registry.revision);let t=r.refSplitCache.get(e);return t||(t=Me(e,n=>n==="scene"||r.registry.has(n)||(r.bundle.externalScopes?.includes(n)??!1)),r.refSplitCache.set(e,t)),t}function Yt(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 Zt(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[Wt(o.slice(6))]=i)}return t}function at(r){return typeof r=="boolean"?r:typeof r=="number"?r!==0:typeof r=="string"?r!=="":r.length>0}var en=(r,e)=>r.shared??e;function ke(r,e){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.default!==void 0?{default:r.default}:{},shared:en(r,e)}}function tn(r){return{name:r.name,type:r.type,hasDefault:r.default!==void 0,...r.values?{values:[...r.values]}:{},...r.purpose?{purpose:r.purpose}:{}}}function nn(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 gt(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)nn(c,e);o.sceneProps?.length&&n.push({gameId:a,properties:o.sceneProps.map(c=>ke(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=>ke(a,!0))})),i=Object.entries(r.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(tn)}));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=>ke(o,!0)),scene:n},gameData:i,counts:e}}var J=(r,e)=>Object.prototype.hasOwnProperty.call(r,e);function ft(r,e){return r.gameDataFields?.[e]??[]}function Ee(r,e,t){return e&&J(e,t)?e[t]:r.find(n=>n.name===t)?.default}function ht(r,e){let t={};for(let n of r){let s=Ee(r,e,n.name);s!==void 0&&(t[n.name]=s)}for(let[n,s]of Object.entries(e??{}))J(t,n)||(t[n]=s);return t}function mt(r,e,t){let n=r.extensions!==void 0&&J(r.extensions,e)?r.extensions[e]:void 0;return n!==void 0&&J(n.fields,t)?n.fields[t]??[]:[]}function yt(r,e,t=""){let n=o=>({...o?.extensions??{}}),s=Object.values(r.scenes??{}),i=o=>(J(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 g=(d.beats??[]).find(f=>f.id===t);if(g!==void 0){g.kind===e&&(o=g);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 It(rn);})();\n';
2634
2708
 
2635
2709
  // src/play-timeline.ts
2636
2710
  function createTimeline(defaultPad) {
@@ -2696,8 +2770,8 @@ var PLAYER_JS = String.raw`
2696
2770
  function add(cls, html) { var d = document.createElement("div"); d.className = cls; d.innerHTML = html; stage.appendChild(d); return d; }
2697
2771
  function esc(s) { return String(s == null ? "" : s).replace(/[&<>]/g, function (c) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]; }); }
2698
2772
 
2699
- // Pacing (line padding): a source-only reader carries no audio, so each line is timed by the estimate
2700
- // Patterstage uses for a line with no recording (game-subtitles' estimateDuration), and the step's padAfter
2773
+ // Pacing (line padding): a source-only reader carries no audio, so each line is timed by Patter's own rule
2774
+ // for a line with no recording (estimateLineDuration, from the runtime), and the step's padAfter
2701
2775
  // says when the next one starts: after a pause, at once, or cutting in before this one ends. The player
2702
2776
  // rules are play-timeline.ts's, inlined below by their source: the first line starts at once, as does the
2703
2777
  // reply to a choice; a cut-in never starts before the line it cuts into; a game event is never held here,
@@ -2708,6 +2782,10 @@ var PLAYER_JS = String.raw`
2708
2782
  // is stored under the key Patterpad's Play window uses, so the two keep one choice; the old reading paces
2709
2783
  // carry over (slow is half speed, fast is double).
2710
2784
  // A run token cancels an in-flight wait the moment the player restarts, loads, or picks a choice.
2785
+ // Continue, as in Patterpad's Play window and under its stored key: on, the story plays through at the chosen
2786
+ // speed; off, each beat waits for the reader (a click or tap on the page, Space or Enter, or the Next button).
2787
+ // A reader's own choice holds; with none, an unvoiced story starts with it off, since nothing in it is meant to
2788
+ // be heard at speaking pace, and a voiced one starts with it on.
2711
2789
  var createTimeline = (${createTimeline.toString()});
2712
2790
  var createClock = (${createClock.toString()});
2713
2791
  var DEFAULT_PAD = ${DEFAULT_PAD_AFTER}; // the built-in pause, for a bundle from before padding
@@ -2716,18 +2794,79 @@ var PLAYER_JS = String.raw`
2716
2794
  var speedKey = (function () { try { return localStorage.getItem("patter.playSpeed") || "normal"; } catch (e) { return "normal"; } })();
2717
2795
  if (own(OLD_SPEED, speedKey)) speedKey = OLD_SPEED[speedKey];
2718
2796
  if (!own(SPEED_RATE, speedKey)) speedKey = "normal";
2719
- var estimate = window.GameSubtitles.estimateDuration;
2720
- var clock = null, wake = null; // the run's clock, and a hook that re-times the wait in flight
2797
+ var estimate = window.Patterplay.estimateLineDuration;
2798
+ var clock = null, wake = null, timeline = null; // the run's clock and timeline, and a hook that re-times the wait in flight
2799
+ var continueMode = (function () { try { var v = localStorage.getItem("patter.playContinue"); if (v !== null) return v !== "0"; } catch (e) {} return !!BUNDLE.voiced; })();
2800
+ var pendingStep = null; // with Continue off: shows the beat the reader is waiting on
2721
2801
  // Call then() when script time "at" comes on the run's clock. A change of speed wakes the wait, which
2722
2802
  // works out the time again; a superseded run's wait just stops.
2723
2803
  function waitUntil(at, token, then) {
2724
2804
  if (token !== runToken) return;
2805
+ if (!continueMode) { wake = null; waitForReader(token, then); return; }
2725
2806
  var ms = clock.at(at) - Date.now();
2726
2807
  if (ms <= 0) { wake = null; then(); return; }
2727
2808
  var t = setTimeout(function () { waitUntil(at, token, then); }, ms);
2728
2809
  wake = function () { clearTimeout(t); waitUntil(at, token, then); };
2729
2810
  }
2730
2811
  function scrollDown() { window.scrollTo(0, document.body.scrollHeight); }
2812
+
2813
+ // With Continue off, the next beat waits behind a Next button (and any click or key that means "go on").
2814
+ function waitForReader(token, then) {
2815
+ clearNext();
2816
+ var b = document.createElement("button");
2817
+ b.className = "next"; b.id = "next"; b.textContent = "Next";
2818
+ pendingStep = function () { if (token !== runToken) return; clearNext(); then(); };
2819
+ b.onclick = function (e) { e.stopPropagation(); if (pendingStep) pendingStep(); };
2820
+ controls.appendChild(b);
2821
+ scrollDown();
2822
+ }
2823
+ function clearNext() { pendingStep = null; var b = document.getElementById("next"); if (b) b.remove(); }
2824
+ document.addEventListener("click", function (e) {
2825
+ if (!pendingStep) return;
2826
+ var t = e.target;
2827
+ if (t && t.closest && t.closest("button, a, select, input, label, .bar")) return; // the bar and other controls keep their own clicks
2828
+ pendingStep();
2829
+ });
2830
+ document.addEventListener("keydown", function (e) {
2831
+ if (!pendingStep || (e.key !== " " && e.key !== "Enter")) return;
2832
+ var t = e.target;
2833
+ if (t && t.closest && t.closest("button, select, input, textarea")) return; // a focused control answers its own key
2834
+ e.preventDefault();
2835
+ pendingStep();
2836
+ });
2837
+
2838
+ // The Continue toggle. A page published before it existed has no such button, so it's added here.
2839
+ var continueBtn = document.getElementById("continue");
2840
+ if (!continueBtn) {
2841
+ var inner = document.querySelector(".bar .inner");
2842
+ if (inner) {
2843
+ continueBtn = document.createElement("button");
2844
+ continueBtn.id = "continue";
2845
+ continueBtn.textContent = "Continue";
2846
+ inner.insertBefore(continueBtn, document.querySelector(".bar .speedlabel") || document.getElementById("flash"));
2847
+ }
2848
+ }
2849
+ function reflectContinue() {
2850
+ if (!continueBtn) return;
2851
+ continueBtn.setAttribute("aria-pressed", String(continueMode));
2852
+ continueBtn.title = continueMode
2853
+ ? "Continue is on: the story plays through at the chosen speed. Click to step through it yourself."
2854
+ : "Continue is off: click the page, or press Space, for each line. Click to play it through instead.";
2855
+ }
2856
+ if (continueBtn) {
2857
+ reflectContinue();
2858
+ continueBtn.onclick = function () {
2859
+ continueMode = !continueMode;
2860
+ try { localStorage.setItem("patter.playContinue", continueMode ? "1" : "0"); } catch (e) {}
2861
+ reflectContinue();
2862
+ if (continueMode && pendingStep) {
2863
+ // Playing on from a beat the reader was waiting on: the timing starts again from now.
2864
+ timeline = createTimeline(DEFAULT_PAD);
2865
+ clock = createClock(Date.now(), SPEED_RATE[speedKey]);
2866
+ pendingStep();
2867
+ } else if (!continueMode && wake) wake(); // the wait in flight becomes a wait for the reader
2868
+ };
2869
+ }
2731
2870
  var speedSel = document.getElementById("speed");
2732
2871
  if (speedSel) {
2733
2872
  speedSel.value = speedKey;
@@ -2757,11 +2896,18 @@ var PLAYER_JS = String.raw`
2757
2896
  function run() {
2758
2897
  controls.innerHTML = "";
2759
2898
  var token = ++runToken;
2760
- var timeline = createTimeline(DEFAULT_PAD);
2899
+ clearNext();
2900
+ timeline = createTimeline(DEFAULT_PAD);
2761
2901
  clock = createClock(Date.now(), SPEED_RATE[speedKey]);
2902
+ var first = true;
2762
2903
  // A step read ahead appears when its time comes: a line or text when its pause is over, the choices
2763
2904
  // as the last line starts, the end as the last line still playing ends.
2764
2905
  function next(s) {
2906
+ // The first beat (of a run, or the reply to a choice) starts at once. With Continue off, so do the
2907
+ // choices and the end: the reader has nothing to wait for once the last line is up.
2908
+ var atOnce = first || (!continueMode && (!s || s.type === "end" || s.type === "choice"));
2909
+ first = false;
2910
+ if (atOnce && !continueMode) { show(s); return; }
2765
2911
  var at = !s || s.type === "end" ? timeline.endAt() : s.type === "choice" ? timeline.choicesAt() : timeline.startOf();
2766
2912
  waitUntil(at, token, function () { show(s); });
2767
2913
  }
@@ -2843,6 +2989,10 @@ var STYLE = String.raw`
2843
2989
  .bar .speedlabel { font: 0.8rem ui-sans-serif, system-ui, sans-serif; color: #9a8f78; display: flex; align-items: center; gap: 0.3rem; }
2844
2990
  .bar #speed { font: 0.85rem ui-sans-serif, system-ui, sans-serif; background: #fff; border: 1px solid #d9cfbb; border-radius: 6px; padding: 0.3rem 0.4rem; cursor: pointer; color: #2b2620; }
2845
2991
  .bar #speed:hover { border-color: #b8956a; }
2992
+ .bar #continue[aria-pressed="true"] { background: #efe4cf; border-color: #b8956a; }
2993
+ .next { align-self: flex-start; font: 0.9rem ui-sans-serif, system-ui, sans-serif; background: #fff; border: 1px solid #d9cfbb; border-radius: 999px; padding: 0.35rem 1rem; cursor: pointer; color: #6b5f48; animation: fade 0.3s ease-out both; }
2994
+ .next::after { content: " \25B8"; }
2995
+ .next:hover { border-color: #b8956a; background: #fffaf0; }
2846
2996
  #flash { margin-left: auto; color: #9a8f78; font: 0.8rem ui-sans-serif, system-ui, sans-serif; opacity: 0; transition: opacity .2s; }
2847
2997
  `;
2848
2998
  function sourceStory(loaded) {
@@ -2875,6 +3025,7 @@ ${head}
2875
3025
  <button id="restart">\u21BA Restart</button>
2876
3026
  <button id="save">Save</button>
2877
3027
  <button id="load">Load</button>
3028
+ <button id="continue" aria-pressed="false">Continue</button>
2878
3029
  <label class="speedlabel">Speed
2879
3030
  <select id="speed" title="How fast the scene plays: its timing at half speed, as written, at double speed, or with no waits">
2880
3031
  <option value="half">Half</option>
@@ -2897,7 +3048,6 @@ function runExportHtml(loaded) {
2897
3048
  lang,
2898
3049
  `<style>${STYLE}</style>`,
2899
3050
  `<script>${PLAYABLE_RUNTIME_JS}</script>
2900
- <script>${PLAYABLE_TIMING_JS}</script>
2901
3051
  <script>window.PATTER_BUNDLE=${bundleJson};</script>
2902
3052
  <script>${PLAYER_JS}</script>`
2903
3053
  );
@@ -2916,7 +3066,6 @@ function runExportWeb(loaded) {
2916
3066
  styleCss: `${STYLE.trim()}
2917
3067
  `,
2918
3068
  patterplayJs: `${PLAYABLE_RUNTIME_JS}
2919
- ${PLAYABLE_TIMING_JS}
2920
3069
  ${PLAYER_JS}`,
2921
3070
  storyJs: `window.PATTER_BUNDLE=${bundleJson};
2922
3071
  `
@@ -3324,6 +3473,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
3324
3473
  seed: Math.floor(rng() * 4294967296),
3325
3474
  onDryChoice: (groupId) => dryThisRun.add(groupId),
3326
3475
  onError: (e) => {
3476
+ if (e.kind === "dry-choice") return;
3327
3477
  const key = `${e.kind}|${e.node}|${e.message}`;
3328
3478
  if (!errorsThisRun.has(key)) errorsThisRun.set(key, { kind: e.kind, node: e.node, ...e.source ? { source: e.source } : {}, message: e.message });
3329
3479
  },
@@ -3791,6 +3941,7 @@ function runReport(loaded, recordingOverride) {
3791
3941
  return w !== void 0 && writingIndex.has(w) ? w : stub;
3792
3942
  };
3793
3943
  const rungIdxOf = (id) => writingIndex.get(wsOf(id)) ?? 0;
3944
+ const folders = sceneFolderPaths(loaded);
3794
3945
  for (const scene of loaded.scenes) {
3795
3946
  const sceneCut = cutSet.has(scene.id);
3796
3947
  const sceneAction = textIsAction2(project, scene);
@@ -3864,6 +4015,7 @@ function runReport(loaded, recordingOverride) {
3864
4015
  const report = {
3865
4016
  sceneId: scene.id,
3866
4017
  name: scene.name,
4018
+ ...folders.has(scene.id) ? { folder: folders.get(scene.id) } : {},
3867
4019
  file: loaded.sceneFiles[scene.id],
3868
4020
  status,
3869
4021
  estimated: true,
@@ -3917,6 +4069,7 @@ function runReport(loaded, recordingOverride) {
3917
4069
  reports.push({
3918
4070
  sceneId: scene.id,
3919
4071
  name: scene.name,
4072
+ ...folders.has(scene.id) ? { folder: folders.get(scene.id) } : {},
3920
4073
  file: loaded.sceneFiles[scene.id],
3921
4074
  status,
3922
4075
  estimated: false,
@@ -4056,7 +4209,9 @@ async function runReportXlsx(data) {
4056
4209
  const rec = data.recordingTracked;
4057
4210
  const scenes = wb.addWorksheet("Scenes");
4058
4211
  const action = data.totals.action.count > 0;
4212
+ const filed = data.scenes.some((s) => s.folder);
4059
4213
  scenes.columns = [
4214
+ ...filed ? [{ header: "Folder", key: "folder", width: 22 }] : [],
4060
4215
  { header: "Scene", key: "name", width: 24 },
4061
4216
  { header: "Status", key: "status", width: 10 },
4062
4217
  ...v ? [{ header: "Voiced", key: "vCount", width: 8 }] : [],
@@ -4074,6 +4229,7 @@ async function runReportXlsx(data) {
4074
4229
  ];
4075
4230
  const sceneRow = (name, s) => ({
4076
4231
  name,
4232
+ folder: s.folder ?? "",
4077
4233
  status: s.status,
4078
4234
  vCount: s.voiced.count,
4079
4235
  wCount: s.written.count,
@@ -4323,7 +4479,7 @@ function cellText(v) {
4323
4479
  if ("result" in o) return cellText(o.result);
4324
4480
  return "";
4325
4481
  }
4326
- async function catalogToXlsx(catalog) {
4482
+ async function catalogToXlsx(catalog, opts = {}) {
4327
4483
  const { default: ExcelJS } = await import("exceljs");
4328
4484
  const wb = new ExcelJS.Workbook();
4329
4485
  wb.creator = "patter";
@@ -4342,7 +4498,9 @@ async function catalogToXlsx(catalog) {
4342
4498
  // Hidden: the translator has no use for it, and it is the import's only faithful record of the scene.
4343
4499
  { header: "Scene", key: "scene", width: 22, hidden: true },
4344
4500
  // After the hidden Scene column, so the columns an older reader finds by position stay where they were.
4345
- { header: "Qualifier", key: "qualifier", width: 11 }
4501
+ { header: "Qualifier", key: "qualifier", width: 11 },
4502
+ // Scene folders: the scene's folder, last of all, when the project has any. Never read back.
4503
+ ...opts.folders?.size ? [{ header: "Folder", key: "folder", width: 22 }] : []
4346
4504
  ];
4347
4505
  ws.getRow(1).font = { bold: true };
4348
4506
  ws.views = [{ state: "frozen", ySplit: 1 }];
@@ -4355,7 +4513,8 @@ async function catalogToXlsx(catalog) {
4355
4513
  status: e.stale ? "stale" : e.translation ? "translated" : "",
4356
4514
  gender: e.context?.gender ?? "",
4357
4515
  scene,
4358
- qualifier: e.context?.qualifier ?? ""
4516
+ qualifier: e.context?.qualifier ?? "",
4517
+ folder: opts.folders?.get(scene) ?? ""
4359
4518
  });
4360
4519
  }
4361
4520
  }
@@ -4441,12 +4600,16 @@ function runVoiceScript(loaded, opts = {}) {
4441
4600
  const voClasses = classesForChannel(project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES3, "vo");
4442
4601
  const ownVo = (id) => (docsOf.get(id) ?? []).filter((d) => d.type !== void 0 && voClasses.has(d.type)).map((d) => d.text);
4443
4602
  const lines = [];
4603
+ const folders = sceneFolderPaths(loaded);
4604
+ let sceneNow = "";
4444
4605
  const emitLine = (beat, scope, leadingVo) => {
4445
4606
  if (beat.kind !== "line" || cutSet.has(beat.id)) return false;
4446
4607
  const ws = writingOf.get(beat.id) ?? stub;
4447
4608
  if (!everything && recordThreshold !== -1 && (writingIndex.get(ws) ?? 0) < recordThreshold) return false;
4448
4609
  const own = ownVo(beat.id);
4610
+ const folder = folders.get(sceneNow);
4449
4611
  lines.push({
4612
+ ...folder ? { folder } : {},
4450
4613
  scope: scope.join(" \u203A "),
4451
4614
  id: beat.id,
4452
4615
  character: beat.character ?? "",
@@ -4478,6 +4641,7 @@ function runVoiceScript(loaded, opts = {}) {
4478
4641
  };
4479
4642
  for (const scene of loaded.scenes) {
4480
4643
  if (cutSet.has(scene.id)) continue;
4644
+ sceneNow = scene.id;
4481
4645
  const sceneVo = ownVo(scene.id);
4482
4646
  for (const block of scene.blocks) {
4483
4647
  if (cutSet.has(block.id)) continue;
@@ -4495,7 +4659,9 @@ async function voiceScriptToXlsx(data) {
4495
4659
  const wb = new ExcelJS.Workbook();
4496
4660
  wb.creator = "patter";
4497
4661
  const ws = wb.addWorksheet("Voice Script");
4662
+ const filed = data.lines.some((l) => l.folder);
4498
4663
  ws.columns = [
4664
+ ...filed ? [{ header: "Folder", key: "folder", width: 22 }] : [],
4499
4665
  { header: "Scope", key: "scope", width: 28 },
4500
4666
  { header: "Line ID", key: "id", width: 18 },
4501
4667
  { header: "Character", key: "character", width: 16 },
@@ -4510,6 +4676,7 @@ async function voiceScriptToXlsx(data) {
4510
4676
  ws.views = [{ state: "frozen", ySplit: 1 }];
4511
4677
  for (const l of data.lines) {
4512
4678
  const row = ws.addRow({
4679
+ folder: l.folder ?? "",
4513
4680
  scope: l.scope,
4514
4681
  id: l.id,
4515
4682
  character: l.character,
@@ -7158,6 +7325,7 @@ export {
7158
7325
  actionTextIds,
7159
7326
  applyLoc,
7160
7327
  applyResolutions,
7328
+ applySceneNavOrder,
7161
7329
  applySceneOrder,
7162
7330
  applySuggestionDecisions,
7163
7331
  applyWrites,
@@ -7281,6 +7449,7 @@ export {
7281
7449
  runVoiceScript,
7282
7450
  scanAudio,
7283
7451
  scanAudioStatus,
7452
+ sceneFolderPaths,
7284
7453
  sceneIdForShard,
7285
7454
  sceneTextIds,
7286
7455
  scriptFontLicencePath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/ops",
3
- "version": "0.25.1",
3
+ "version": "0.26.0",
4
4
  "description": "The shared operations layer: every project operation (load, validate, export, format, play, ...) as a pure function, consumed by the patter CLI and Patterpad alike.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,11 +33,11 @@
33
33
  "@fontsource/noto-sans": "^5.3.0",
34
34
  "@fontsource/noto-sans-math": "^5.3.0",
35
35
  "@ibm/plex-mono": "1.0.0",
36
- "@patterkit/compiler": "0.15.0",
37
- "@patterkit/core": "0.10.0",
38
- "@patterkit/dialect": "0.2.18",
39
- "@patterkit/model": "0.21.0",
40
- "@patterkit/runtime": "0.35.0",
36
+ "@patterkit/compiler": "0.15.2",
37
+ "@patterkit/core": "0.11.0",
38
+ "@patterkit/dialect": "0.2.20",
39
+ "@patterkit/model": "0.23.0",
40
+ "@patterkit/runtime": "0.37.0",
41
41
  "@wildwinter/expr": "^0.6.0",
42
42
  "@wildwinter/toolkit": "^0.4.0",
43
43
  "@xmldom/xmldom": "^0.8.15",
@@ -51,7 +51,6 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/pdfkit": "^0.17.6",
54
- "@wildwinter/game-subtitles": "^0.4.0",
55
54
  "@wildwinter/scoperegistry": ">=0.9.0 <1.0.0",
56
55
  "esbuild": "^0.28.2"
57
56
  }