@patterkit/ops 0.4.0 → 0.6.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 +25 -13
- package/dist/index.js +372 -74
- package/package.json +6 -5
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ProjectFile, Scene, LocaleFile, AuthoringFile, Bundle, GameData, Covera
|
|
|
2
2
|
import { ValidationIssue } from '@patterkit/core';
|
|
3
3
|
import { ConditionIssue } from '@patterkit/compiler';
|
|
4
4
|
import { ChoiceOption } from '@patterkit/runtime';
|
|
5
|
+
export { isUnsafeEntry } from '@wildwinter/toolkit/archive';
|
|
5
6
|
|
|
6
7
|
interface LoadedProject {
|
|
7
8
|
root: string;
|
|
@@ -80,6 +81,10 @@ interface ValidateResult {
|
|
|
80
81
|
hygiene: HygieneIssue[];
|
|
81
82
|
/** Committed `.patterc` bundles whose hash no longer matches source (spec §11). */
|
|
82
83
|
staleBundles: HygieneIssue[];
|
|
84
|
+
/** Conditions provably unsatisfiable over monotonic latches (reachability.ts). WARNINGS, and
|
|
85
|
+
* deliberately NOT part of `ok`: content is written in pieces, so a gate whose writer has not been
|
|
86
|
+
* authored yet is the normal state mid-work and must never fail a build. */
|
|
87
|
+
reachability: ConditionIssue[];
|
|
83
88
|
/** Lingering `.patterconflict` sidecars - an unresolved merge (patter-merge.md §3.6). */
|
|
84
89
|
unresolvedMerges: HygieneIssue[];
|
|
85
90
|
/** Patter shards on disk that the project does NOT contain - see `orphanShards`. */
|
|
@@ -89,6 +94,12 @@ interface ValidateResult {
|
|
|
89
94
|
/** Run structural + expression + interpolation + hygiene + bundle-staleness + merge validation. */
|
|
90
95
|
declare function runValidate(loaded: LoadedProject): ValidateResult;
|
|
91
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Conditions that can never hold, as warnings. Empty for a project with no monotonic latches, which is
|
|
99
|
+
* most of them: this only speaks when it can prove an ordering.
|
|
100
|
+
*/
|
|
101
|
+
declare function reachabilityIssues(loaded: LoadedProject, compiled?: Bundle): ConditionIssue[];
|
|
102
|
+
|
|
92
103
|
declare function runExport(loaded: LoadedProject): Bundle;
|
|
93
104
|
/** Compile WITHOUT the localisation-mode transform - the full bundle with every locale inline. Used where
|
|
94
105
|
* the host needs all strings regardless of build mode (e.g. Patterpad's Play window + Export Localisation). */
|
|
@@ -211,6 +222,19 @@ interface CoverageBeat {
|
|
|
211
222
|
/** Set on a never-reached beat that is gated on a host-scope ref (`@world.x`) nothing writes and no
|
|
212
223
|
* driver provides, i.e. it may just need an input, not be truly dead. Lists the offending refs. */
|
|
213
224
|
needsInput?: string[];
|
|
225
|
+
/** Set on a never-reached beat gated on a ref that IS written, but ONLY by content that was itself
|
|
226
|
+
* never reached: the beat is dead at one remove, and the gate is not the real question. Names the
|
|
227
|
+
* ref and the beats that witness its only writers, so two mysteries collapse into one. Absent
|
|
228
|
+
* whenever the chain cannot be refuted (see `writerSites` in the analysis). */
|
|
229
|
+
blockedBy?: BlockedGate[];
|
|
230
|
+
}
|
|
231
|
+
/** A gate on a never-reached beat whose every writer was itself never reached. */
|
|
232
|
+
interface BlockedGate {
|
|
233
|
+
/** The gating ref, at the granularity the condition reads it: `@world.alarm`, or `@world.mood:armed`
|
|
234
|
+
* for a single flag of a flags property. */
|
|
235
|
+
ref: string;
|
|
236
|
+
/** Beat ids witnessing the writers: content that would have to play for this gate to be written. */
|
|
237
|
+
writers: string[];
|
|
214
238
|
}
|
|
215
239
|
/** A choice that ran DRY during the coverage run: at some point it had no takeable option and no eligible
|
|
216
240
|
* fallback, so it fell through silently and the flow carried on past it. This is easy to author by
|
|
@@ -884,18 +908,6 @@ interface UnpackMergeResult {
|
|
|
884
908
|
* v1 - safe, no data loss).
|
|
885
909
|
*/
|
|
886
910
|
declare function runUnpackMerge(returnedBytes: Buffer | Uint8Array, baseBytes: Buffer | Uint8Array, projectDir: string): Promise<UnpackMergeResult>;
|
|
887
|
-
/** True if a document entry is an absolute path or would escape the target dir.
|
|
888
|
-
*
|
|
889
|
-
* A screen on the NAME, and the cheap half of the answer. `containedWrite` below is the check that
|
|
890
|
-
* actually holds, because a name is not a path: this cannot know what the name resolves to once it is
|
|
891
|
-
* joined to a target, and `normalize` does not treat `\` as a separator away from Windows.
|
|
892
|
-
*
|
|
893
|
-
* Note what normalises, because the comment here used to credit the wrong half: it is JSZip's READER,
|
|
894
|
-
* not its writer, that collapses `..`. A traversal entry does not survive `loadAsync` however the zip
|
|
895
|
-
* was produced, so neither this nor `containedWrite` can currently be reached through it. They are
|
|
896
|
-
* kept for a different zip library or a future JSZip, and `pack.test.ts` pins the normalisation so
|
|
897
|
-
* that assumption fails loudly rather than silently. */
|
|
898
|
-
declare function isUnsafeEntry(name: string): boolean;
|
|
899
911
|
|
|
900
912
|
/** The class names whose notes deliver to `channel` (a class with `deliver: "*"` matches every channel). */
|
|
901
913
|
declare function classesForChannel(classes: DocumentationClass[], channel: string): Set<string>;
|
|
@@ -907,4 +919,4 @@ declare function classesForChannel(classes: DocumentationClass[], channel: strin
|
|
|
907
919
|
*/
|
|
908
920
|
declare function resolveDocumentation(loaded: LoadedProject, channel: string): Map<string, DocLine[]>;
|
|
909
921
|
|
|
910
|
-
export { AUTHORING_HANDLED, type ApplyStats, type BundlePosture, CONFLICT_SIDECAR, type CharacterReport, type Conflict, type ConflictKind, type CoverageAsyncHooks, type CoverageBeat, type CoverageHooks, type CoverageOptions, type CoverageReport, type DocumentManifest, type FormatResult, type HygieneIssue, type InitOptions, type InitResult, type InitVcs, type LoadedProject, type LocCatalog, type LocEntry, type LocaleReport, type MergeFileType, type MergeResult, type MergedShard, type PlannedWrite, type PlayEvent, type PlayOptions, type PlayOutcome, type PlayResult, type ProvenanceCheck, type ReplaceHit, type ReplaceOptions, type ReplacePlan, type ReportData, type ReportTotals, type ResolveEntry, SHARD_EXTENSIONS, type SceneReport, type ScriptDoc, type ScriptElement, type SearchFocus, type UnpackMergeResult, UnsafeEntryError, UnsupportedMergeError, type ValidateResult, type VoiceLine, type VoiceScript, type VoicedCounts, type WebExport, type WrittenCounts, applyLoc, applySceneOrder, applyWrites, bundleOutputPath, catalogToJson, catalogToPo, catalogToXlsx, classesForChannel, detectMergeType, extractLoc, findProjectFile,
|
|
922
|
+
export { AUTHORING_HANDLED, type ApplyStats, type BundlePosture, CONFLICT_SIDECAR, type CharacterReport, type Conflict, type ConflictKind, type CoverageAsyncHooks, type CoverageBeat, type CoverageHooks, type CoverageOptions, type CoverageReport, type DocumentManifest, type FormatResult, type HygieneIssue, type InitOptions, type InitResult, type InitVcs, type LoadedProject, type LocCatalog, type LocEntry, type LocaleReport, type MergeFileType, type MergeResult, type MergedShard, type PlannedWrite, type PlayEvent, type PlayOptions, type PlayOutcome, type PlayResult, type ProvenanceCheck, type ReplaceHit, type ReplaceOptions, type ReplacePlan, type ReportData, type ReportTotals, type ResolveEntry, SHARD_EXTENSIONS, type SceneReport, type ScriptDoc, type ScriptElement, type SearchFocus, type UnpackMergeResult, UnsafeEntryError, UnsupportedMergeError, type ValidateResult, type VoiceLine, type VoiceScript, type VoicedCounts, type WebExport, type WrittenCounts, applyLoc, applySceneOrder, applyWrites, bundleOutputPath, catalogToJson, catalogToPo, catalogToXlsx, classesForChannel, detectMergeType, extractLoc, findProjectFile, jsonToCatalog, listProjectTags, loadProject, loadProjectLanding, poToCatalog, proposeCoverageDrivers, reachabilityIssues, renderCoverageText, renderPlay, renderReportText, resolveDocumentation, resolveStart, runCoverage, runCoverageAsync, runExport, runExportFull, runExportHtml, runExportWeb, runFormat, runInit, runMerge, runPack, runPlay, runPropertyUsage, runReplace, runReport, runReportXlsx, runResolve, runScriptDoc, runSearch, runStatusBrowse, runTagBrowse, runUnpack, runUnpackMerge, runValidate, runVoiceScript, scanAudioStatus, sceneIdForShard, scriptToDocx, scriptToPdf, sidecarIssues, vcsConfigWrites, voiceScriptToXlsx, xlsxToCatalog };
|
package/dist/index.js
CHANGED
|
@@ -571,7 +571,257 @@ var arrEq = (a, b) => a.length === b.length && a.every((x, i) => x === b[i]);
|
|
|
571
571
|
|
|
572
572
|
// src/validate.ts
|
|
573
573
|
import { validateProject, parseSource as parseSource2 } from "@patterkit/core";
|
|
574
|
-
import { validateConditions, validateInterpolation, exportBundle, hostScopesToSpec } from "@patterkit/compiler";
|
|
574
|
+
import { validateConditions, validateInterpolation, exportBundle as exportBundle2, hostScopesToSpec } from "@patterkit/compiler";
|
|
575
|
+
|
|
576
|
+
// src/reachability.ts
|
|
577
|
+
import { exportBundle } from "@patterkit/compiler";
|
|
578
|
+
|
|
579
|
+
// ../../../expr/packages/expr/src/ast.ts
|
|
580
|
+
function deserialiseAst(node) {
|
|
581
|
+
switch (node[0]) {
|
|
582
|
+
case "b":
|
|
583
|
+
return { kind: "bool", value: node[1] };
|
|
584
|
+
case "n":
|
|
585
|
+
return { kind: "number", value: node[1] };
|
|
586
|
+
case "s":
|
|
587
|
+
return { kind: "string", value: node[1] };
|
|
588
|
+
case "sv":
|
|
589
|
+
return { kind: "scopedvar", scope: node[1], name: node[2] };
|
|
590
|
+
case "u":
|
|
591
|
+
return { kind: "unary", op: node[1], operand: deserialiseAst(node[2]) };
|
|
592
|
+
case "bin":
|
|
593
|
+
return { kind: "binary", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };
|
|
594
|
+
case "call": {
|
|
595
|
+
const args = node.slice(2).map(deserialiseAst);
|
|
596
|
+
return { kind: "call", name: node[1], args };
|
|
597
|
+
}
|
|
598
|
+
case "fd":
|
|
599
|
+
return { kind: "flagdelta", sign: node[1], name: node[2] };
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ../../../expr/packages/expr/src/prng.ts
|
|
604
|
+
function toUint32(seed) {
|
|
605
|
+
if (Number.isNaN(seed) || !Number.isFinite(seed)) return 0;
|
|
606
|
+
const modded = Math.trunc(seed) % 4294967296;
|
|
607
|
+
return modded < 0 ? modded + 4294967296 : modded;
|
|
608
|
+
}
|
|
609
|
+
function makePrng(seed) {
|
|
610
|
+
let s = toUint32(seed);
|
|
611
|
+
return {
|
|
612
|
+
next() {
|
|
613
|
+
s = s + 1831565813 >>> 0;
|
|
614
|
+
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
615
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
616
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
617
|
+
},
|
|
618
|
+
state() {
|
|
619
|
+
return s;
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ../../../expr/packages/expr/src/latches.ts
|
|
625
|
+
var scopedRef = (ast) => Array.isArray(ast) && ast[0] === "sv" ? `@${String(ast[1])}.${String(ast[2])}` : void 0;
|
|
626
|
+
function latchOf(ast, owner, keyOf2) {
|
|
627
|
+
const direct = scopedRef(ast);
|
|
628
|
+
if (direct !== void 0) return keyOf2(direct, owner);
|
|
629
|
+
if (!Array.isArray(ast)) return void 0;
|
|
630
|
+
if (ast[0] === "bin" && ast[1] === "==") {
|
|
631
|
+
const [, , l, r] = ast;
|
|
632
|
+
for (const [a, b] of [[l, r], [r, l]]) {
|
|
633
|
+
const ref = scopedRef(a);
|
|
634
|
+
if (ref !== void 0 && Array.isArray(b) && b[0] === "b" && b[1] === true) return keyOf2(ref, owner);
|
|
635
|
+
}
|
|
636
|
+
return void 0;
|
|
637
|
+
}
|
|
638
|
+
if (ast[0] === "call" && ast[1] === "check_flags") {
|
|
639
|
+
const ref = scopedRef(ast[2]);
|
|
640
|
+
const args = ast.slice(3);
|
|
641
|
+
if (ref === void 0 || args.length !== 1) return void 0;
|
|
642
|
+
const arg = args[0];
|
|
643
|
+
if (Array.isArray(arg) && arg[0] === "fd" && arg[1] === "+") {
|
|
644
|
+
return `${keyOf2(ref, owner)}:${String(arg[2])}`;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return void 0;
|
|
648
|
+
}
|
|
649
|
+
function disjuncts(ast) {
|
|
650
|
+
if (Array.isArray(ast) && ast[0] === "bin" && ast[1] === "or") {
|
|
651
|
+
const [, , l, r] = ast;
|
|
652
|
+
return [...disjuncts(l), ...disjuncts(r)];
|
|
653
|
+
}
|
|
654
|
+
return [ast];
|
|
655
|
+
}
|
|
656
|
+
function terms(ast, owner, keyOf2, negated = false, into = []) {
|
|
657
|
+
const latch = latchOf(ast, owner, keyOf2);
|
|
658
|
+
if (latch !== void 0) {
|
|
659
|
+
into.push({ key: latch, negated });
|
|
660
|
+
return into;
|
|
661
|
+
}
|
|
662
|
+
if (!Array.isArray(ast)) return into;
|
|
663
|
+
if (ast[0] === "u" && ast[1] === "not") return terms(ast[2], owner, keyOf2, !negated, into);
|
|
664
|
+
if (ast[0] === "bin" && ast[1] === "and" && !negated) {
|
|
665
|
+
terms(ast[2], owner, keyOf2, false, into);
|
|
666
|
+
terms(ast[3], owner, keyOf2, false, into);
|
|
667
|
+
}
|
|
668
|
+
return into;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/reachability.ts
|
|
672
|
+
var normaliseRef = (ref) => ref.includes(".") ? ref : `@patter.${ref.slice(1)}`;
|
|
673
|
+
var SEP = "\0";
|
|
674
|
+
var keyOf = (ref, sceneId) => ref.startsWith("@scene.") ? `${sceneId}${SEP}${ref}` : ref;
|
|
675
|
+
var shown = (key) => {
|
|
676
|
+
const bare = key.includes(SEP) ? key.slice(key.indexOf(SEP) + 1) : key;
|
|
677
|
+
const at = bare.indexOf(":");
|
|
678
|
+
return at < 0 ? bare : `${bare.slice(0, at)} +${bare.slice(at + 1)}`;
|
|
679
|
+
};
|
|
680
|
+
var termsOfAst = (ast, sceneId) => terms(ast, sceneId, keyOf);
|
|
681
|
+
var requirementsOf = (expr, sceneId) => expr === void 0 ? [] : termsOfAst(expr.ast, sceneId);
|
|
682
|
+
var asFlags = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
|
|
683
|
+
function reachabilityIssues(loaded, compiled) {
|
|
684
|
+
const bundle = compiled ?? exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales });
|
|
685
|
+
const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
|
|
686
|
+
const preset = /* @__PURE__ */ new Set();
|
|
687
|
+
const notALatch = (decl, key) => {
|
|
688
|
+
if (decl.temporary) {
|
|
689
|
+
preset.add(key);
|
|
690
|
+
for (const v of decl.values ?? []) preset.add(`${key}:${v}`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (decl.type === "boolean" && decl.default === true) preset.add(key);
|
|
694
|
+
if (decl.type === "flags") for (const v of asFlags(decl.default)) preset.add(`${key}:${v}`);
|
|
695
|
+
};
|
|
696
|
+
for (const decl of loaded.project.properties ?? []) notALatch(decl, `@patter.${decl.name}`);
|
|
697
|
+
for (const scene of loaded.scenes) {
|
|
698
|
+
for (const decl of scene.sceneProps ?? []) notALatch(decl, `${scene.id}${SEP}@scene.${decl.name}`);
|
|
699
|
+
}
|
|
700
|
+
const latched = /* @__PURE__ */ new Set();
|
|
701
|
+
const broken = /* @__PURE__ */ new Set();
|
|
702
|
+
const writers = /* @__PURE__ */ new Map();
|
|
703
|
+
const noteWrite = (key, requires) => {
|
|
704
|
+
latched.add(key);
|
|
705
|
+
(writers.get(key) ?? writers.set(key, []).get(key)).push({ requires });
|
|
706
|
+
};
|
|
707
|
+
const breakKey = (key) => {
|
|
708
|
+
broken.add(key);
|
|
709
|
+
for (const k of latched) if (k.startsWith(`${key}:`)) broken.add(k);
|
|
710
|
+
};
|
|
711
|
+
const scanEffects = (effects, sceneId, need) => {
|
|
712
|
+
for (const e of effects ?? []) {
|
|
713
|
+
const ref = normaliseRef(e.target);
|
|
714
|
+
const key = keyOf(ref, sceneId);
|
|
715
|
+
const ast = e.value.ast;
|
|
716
|
+
if (Array.isArray(ast) && ast[0] === "b" && ast[1] === true) {
|
|
717
|
+
noteWrite(key, need);
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (Array.isArray(ast) && ast[0] === "call" && ast[1] === "set_flags" && scopedRef(ast[2]) === ref) {
|
|
721
|
+
let clean = true;
|
|
722
|
+
for (const arg of ast.slice(3)) {
|
|
723
|
+
if (Array.isArray(arg) && arg[0] === "fd" && arg[1] === "+") noteWrite(`${key}:${String(arg[2])}`, need);
|
|
724
|
+
else clean = false;
|
|
725
|
+
}
|
|
726
|
+
if (clean) continue;
|
|
727
|
+
}
|
|
728
|
+
breakKey(key);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
const walk = (nodes, sceneId, gate) => {
|
|
732
|
+
for (const node of nodes) {
|
|
733
|
+
const here = [...gate, ...requirementsOf(node.condition, sceneId)];
|
|
734
|
+
if (node.type === "group") walk(node.children, sceneId, here);
|
|
735
|
+
else {
|
|
736
|
+
scanEffects(node.onEnter, sceneId, here);
|
|
737
|
+
scanEffects(node.onExit, sceneId, here);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
742
|
+
scanEffects(scene.onEntry, scene.id, []);
|
|
743
|
+
for (const block of scene.blocks) walk(block.children, scene.id, []);
|
|
744
|
+
}
|
|
745
|
+
for (const key of [...latched]) {
|
|
746
|
+
const at = key.indexOf(":");
|
|
747
|
+
if (at > 0 && broken.has(key.slice(0, at))) broken.add(key);
|
|
748
|
+
}
|
|
749
|
+
const hostDriven = (key) => hostTokens.has(key.slice(1, key.indexOf(".")));
|
|
750
|
+
const monotonic = (key) => latched.has(key) && !broken.has(key) && !preset.has(key) && !hostDriven(key);
|
|
751
|
+
const cache = /* @__PURE__ */ new Map();
|
|
752
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
753
|
+
const mustHold = (key) => {
|
|
754
|
+
const done = cache.get(key);
|
|
755
|
+
if (done !== void 0) return { need: done, cut: false };
|
|
756
|
+
if (inFlight.has(key)) return { need: /* @__PURE__ */ new Set(), cut: true };
|
|
757
|
+
const routes = writers.get(key);
|
|
758
|
+
if (routes === void 0 || routes.length === 0) return { need: /* @__PURE__ */ new Set(), cut: false };
|
|
759
|
+
inFlight.add(key);
|
|
760
|
+
let shared;
|
|
761
|
+
let cut = false;
|
|
762
|
+
for (const route of routes) {
|
|
763
|
+
const need = /* @__PURE__ */ new Set();
|
|
764
|
+
for (const t of route.requires) {
|
|
765
|
+
if (t.negated || !monotonic(t.key)) continue;
|
|
766
|
+
need.add(t.key);
|
|
767
|
+
const deeper = mustHold(t.key);
|
|
768
|
+
cut ||= deeper.cut;
|
|
769
|
+
for (const k of deeper.need) need.add(k);
|
|
770
|
+
}
|
|
771
|
+
shared = shared === void 0 ? need : new Set([...shared].filter((k) => need.has(k)));
|
|
772
|
+
}
|
|
773
|
+
inFlight.delete(key);
|
|
774
|
+
const result = shared ?? /* @__PURE__ */ new Set();
|
|
775
|
+
if (!cut) cache.set(key, result);
|
|
776
|
+
return { need: result, cut };
|
|
777
|
+
};
|
|
778
|
+
const refute = (gate) => {
|
|
779
|
+
for (const no of gate.filter((t) => t.negated)) {
|
|
780
|
+
if (!monotonic(no.key)) continue;
|
|
781
|
+
if (gate.some((t) => !t.negated && t.key === no.key)) {
|
|
782
|
+
return `it asks for ${shown(no.key)} to be both set and not set`;
|
|
783
|
+
}
|
|
784
|
+
for (const yes of gate.filter((t) => !t.negated && t.key !== no.key)) {
|
|
785
|
+
if (!monotonic(yes.key)) continue;
|
|
786
|
+
const chain = mustHold(yes.key);
|
|
787
|
+
if (!chain.cut && chain.need.has(no.key)) {
|
|
788
|
+
return `${shown(yes.key)} can only become true after ${shown(no.key)}, which nothing sets back, so this condition can never hold`;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return void 0;
|
|
793
|
+
};
|
|
794
|
+
const issues = [];
|
|
795
|
+
const report = (nodes, sceneId, gate) => {
|
|
796
|
+
for (const node of nodes) {
|
|
797
|
+
const own = node.condition;
|
|
798
|
+
const branches = own ? disjuncts(own.ast) : [void 0];
|
|
799
|
+
let reason;
|
|
800
|
+
const refuted = branches.every((b) => {
|
|
801
|
+
const r = refute([...gate, ...b ? termsOfAst(b, sceneId) : []]);
|
|
802
|
+
reason ??= r;
|
|
803
|
+
return r !== void 0;
|
|
804
|
+
});
|
|
805
|
+
if (refuted && reason !== void 0 && own) {
|
|
806
|
+
issues.push({
|
|
807
|
+
nodeId: node.id,
|
|
808
|
+
field: "condition",
|
|
809
|
+
src: own.src,
|
|
810
|
+
severity: "warning",
|
|
811
|
+
message: `this can never run: ${reason}`
|
|
812
|
+
});
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (node.type === "group") report(node.children, sceneId, [...gate, ...requirementsOf(own, sceneId)]);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
for (const scene of Object.values(bundle.scenes)) {
|
|
819
|
+
for (const block of scene.blocks) report(block.children, scene.id, []);
|
|
820
|
+
}
|
|
821
|
+
return issues;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/validate.ts
|
|
575
825
|
function runValidate(loaded) {
|
|
576
826
|
const { project, scenes, locales } = loaded;
|
|
577
827
|
const structural = validateProject({ project, scenes, authoring: loaded.authoring });
|
|
@@ -582,10 +832,12 @@ function runValidate(loaded) {
|
|
|
582
832
|
const staleBundles = checkBundles(loaded);
|
|
583
833
|
const unresolvedMerges = sidecarIssues(walkFiles(loaded.root, CONFLICT_SIDECAR));
|
|
584
834
|
const orphans = orphanShards(loaded);
|
|
835
|
+
const reachability = structural.length === 0 && conditions.length === 0 ? reachabilityIssues(loaded) : [];
|
|
585
836
|
return {
|
|
586
837
|
structural,
|
|
587
838
|
conditions,
|
|
588
839
|
interpolation,
|
|
840
|
+
reachability,
|
|
589
841
|
hygiene,
|
|
590
842
|
staleBundles,
|
|
591
843
|
unresolvedMerges,
|
|
@@ -621,7 +873,7 @@ function checkBundles(loaded) {
|
|
|
621
873
|
if (bundles.length === 0) return issues;
|
|
622
874
|
let fresh;
|
|
623
875
|
try {
|
|
624
|
-
fresh =
|
|
876
|
+
fresh = exportBundle2({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales }).content.hash;
|
|
625
877
|
} catch {
|
|
626
878
|
return issues;
|
|
627
879
|
}
|
|
@@ -675,7 +927,7 @@ function checkHygiene(files) {
|
|
|
675
927
|
|
|
676
928
|
// src/export.ts
|
|
677
929
|
import { basename as basename2, isAbsolute, join as join2 } from "path";
|
|
678
|
-
import { exportBundle as
|
|
930
|
+
import { exportBundle as exportBundle3 } from "@patterkit/compiler";
|
|
679
931
|
import { isContentlessBeat } from "@patterkit/model";
|
|
680
932
|
function compileScenes(loaded) {
|
|
681
933
|
const def = loaded.project.locales.default;
|
|
@@ -707,7 +959,7 @@ function refuseUnresolvedMerge(loaded) {
|
|
|
707
959
|
function runExport(loaded) {
|
|
708
960
|
refuseUnresolvedMerge(loaded);
|
|
709
961
|
const { project, locales } = loaded;
|
|
710
|
-
const full =
|
|
962
|
+
const full = exportBundle3({ project, scenes: compileScenes(loaded), locales });
|
|
711
963
|
const loc = project.export?.localisation;
|
|
712
964
|
if (!loc || loc.mode === "embedded") return full;
|
|
713
965
|
const strings = loc.sourceDebug ? { [full.locales.default]: full.strings[full.locales.default] ?? {} } : {};
|
|
@@ -716,7 +968,7 @@ function runExport(loaded) {
|
|
|
716
968
|
function runExportFull(loaded) {
|
|
717
969
|
refuseUnresolvedMerge(loaded);
|
|
718
970
|
const { project, locales } = loaded;
|
|
719
|
-
return
|
|
971
|
+
return exportBundle3({ project, scenes: compileScenes(loaded), locales });
|
|
720
972
|
}
|
|
721
973
|
function bundleOutputPath(loaded) {
|
|
722
974
|
const override = loaded.project.export?.bundle;
|
|
@@ -729,7 +981,7 @@ function bundleOutputPath(loaded) {
|
|
|
729
981
|
import { canonicalStringify as canonicalStringify2 } from "@patterkit/core";
|
|
730
982
|
|
|
731
983
|
// src/playable-runtime.ts
|
|
732
|
-
var PLAYABLE_RUNTIME_JS = '"use strict";var Patterplay=(()=>{var G=Object.defineProperty;var Se=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var ve=Object.prototype.hasOwnProperty;var be=(n,e)=>{for(var t in e)G(n,t,{get:e[t],enumerable:!0})},we=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of ye(e))!ve.call(n,s)&&s!==t&&G(n,s,{get:()=>e[s],enumerable:!(r=Se(e,s))||r.enumerable});return n};var xe=n=>we(G({},"__esModule",{value:!0}),n);var Me={};be(Me,{Engine:()=>N,Flow:()=>k,buildTagIndex:()=>P,describeBundle:()=>ue,effectiveGameData:()=>fe,gameDataFields:()=>ge,gameDataValue:()=>U});function v(n){switch(n[0]){case"b":return{kind:"bool",value:n[1]};case"n":return{kind:"number",value:n[1]};case"s":return{kind:"string",value:n[1]};case"sv":return{kind:"scopedvar",scope:n[1],name:n[2]};case"u":return{kind:"unary",op:n[1],operand:v(n[2])};case"bin":return{kind:"binary",op:n[1],left:v(n[2]),right:v(n[3])};case"call":{let e=n.slice(2).map(v);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var l=class extends Error{constructor(e){super(e),this.name="EvalError"}};function D(n,e,t){let r=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),s=i=>{switch(i.kind){case"bool":return i.value;case"number":return i.value;case"string":return i.value;case"scopedvar":{let o=e.scopes[i.scope];if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):o[i.name];if(a===void 0){if(r.get(i.scope)==="throw")throw new l(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!t.functions[i.name]){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new l(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=T(a,e);if(c===void 0)throw new l("advance() needs a quality reference (@scope.name of a quality property)");let d=S(s(a),c,"advance");return c[Math.min(d+1,c.length-1)]}let o=t.functions[i.name];if(!o)throw new l(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new l("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=s(i.operand);if(typeof a!="boolean")throw new l(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new l(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let g=s(i.left);if(typeof g!="boolean")throw new l(`\'and\' requires boolean operands, left is ${typeof g}`);if(!g)return!1;let h=s(i.right);if(typeof h!="boolean")throw new l(`\'and\' requires boolean operands, right is ${typeof h}`);return h}if(i.op==="or"){let g=s(i.left);if(typeof g!="boolean")throw new l(`\'or\' requires boolean operands, left is ${typeof g}`);if(g)return!0;let h=s(i.right);if(typeof h!="boolean")throw new l(`\'or\' requires boolean operands, right is ${typeof h}`);return h}let o=s(i.left),a=s(i.right),c=T(i.left,e),d=T(i.right,e),p=c??d;if(p!==void 0){if(c&&d&&!ke(c,d)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new l(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return S(o,p,">")>S(a,p,">");case">=":return S(o,p,">=")>=S(a,p,">=");case"<":return S(o,p,"<")<S(a,p,"<");case"<=":return S(o,p,"<=")<=S(a,p,"<=");case"+":case"-":case"*":case"/":throw new l(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return X(o,a);case"!=":return!X(o,a);case">":return b(o,a,">"),o>a;case">=":return b(o,a,">="),o>=a;case"<":return b(o,a,"<"),o<a;case"<=":return b(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new l(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return b(o,a,"-"),o-a;case"*":return b(o,a,"*"),o*a;case"/":if(b(o,a,"/"),a===0)throw new l("division by zero");return o/a}}}};return s(n)}function X(n,e){if(Array.isArray(n)||Array.isArray(e)){if(!Array.isArray(n)||!Array.isArray(e)||n.length!==e.length)return!1;for(let t=0;t<n.length;t++)if(n[t]!==e[t])return!1;return!0}return n===e}function b(n,e,t){if(typeof n!="number"||typeof e!="number")throw new l(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}function T(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function S(n,e,t){if(typeof n!="string")throw new l(`\'${t}\' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new l(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var ke=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);var Ce={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},Ee=[Ce];function Y(n,e,t){let r=t?.countingCalls??Ee;return I(n,t?.want??!0,e,r)}function I(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=I(n.left,e,t,r),i=I(n.right,e,t,r);return n.op==="and"===e?s>0&&i>0?s+i:0:Math.max(s,i)}if(n.kind==="unary"&&n.op==="not")return I(n.operand,!e,t,r);if(n.kind==="call"){let s=r.find(i=>i.name===n.name);if(s){let i=s.count(n),o=t(n);return e?o?i:0:o?0:1}}return t(n)===e?1:0}var M=class n{values={};decls=new Map;subscribers=new Set;auditors=new Set;norm;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),this.seed(e)}seed(e){for(let t of e){let r=this.norm(t.name);this.decls.set(r,t),this.values[r]=structuredClone(t.default??Q(t))}}get(e){return this.values[this.norm(e)]}set(e,t,r){let s=this.norm(e);if(this.decls.get(s)?.writable===!1)throw new Error(`\'${e}\' is read-only`);let i={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let o of this.auditors)o(i);if(!i.silent)for(let o of this.subscribers)o(i);return i}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>Z(t,this.get(e),void 0,e))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function Z(n,e,t,r){return{name:r??n.name.toLowerCase(),type:n.type,value:e,default:n.default??Q(n),...n.values!==void 0?{values:n.values}:{},writable:t??n.writable??!0}}var V=1,C=class{scopes=new Map;defineOwned(e,t){return this.mountOwned(e,new M(t))}mountOwned(e,t){return this.assertFree(e),this.scopes.set(e,{kind:"owned",bag:t}),this}ownedBag(e){let t=this.scopes.get(e);if(!t||t.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);return t.bag}reseedOwned(e,t){return this.ownedBag(e).reseed(t),this}defineForeign(e,t,r=[],s=!0){this.assertFree(e);let i=new Map;for(let o of r)i.set(o.name.toLowerCase(),o);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:i,scopeWritable:s}),this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(r)return r.kind==="owned"?r.bag.get(t):r.resolver.get(t.toLowerCase())}set(e,t,r){let s=this.scopes.get(e);if(!s)throw new Error(`unknown scope \'@${e}\'`);if(s.kind==="owned"){try{s.bag.set(t,r)}catch{throw new Error(`\'@${e}.${t}\' is read-only`)}return}let i=t.toLowerCase();if(!this.foreignWritable(s,i))throw new Error(`\'@${e}.${t}\' is read-only`);s.resolver.set(i,r)}foreignWritable(e,t){return e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}listProperties(){let e=[];for(let[t,r]of this.scopes)if(r.kind==="owned")for(let s of r.bag.rows())e.push({scope:t,...s});else for(let s of r.decls.values())e.push({scope:t,...Z(s,r.resolver.get(s.name.toLowerCase()),this.foreignWritable(r,s.name.toLowerCase()))});return e}toEvalContext(e){let t={};for(let[s,i]of this.scopes)t[s]=i.kind==="owned"?i.bag.values:i.resolver;let r=this.qualityLadders();return r.size===0?{scopes:t,host:e}:{scopes:t,host:e,qualities:(s,i)=>r.get(s)?.get(i.toLowerCase())}}qualityLadders(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];for(let i of s){if(i.type!=="quality"||i.stages===void 0)continue;let o=e.get(t);o||(o=new Map,e.set(t,o)),o.set(i.name.toLowerCase(),i.stages)}}return e}toSchema(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];if(s.length===0)continue;let i=new Map;for(let o of s)i.set(o.name.toLowerCase(),{type:o.type,enumValues:o.values,...o.stages!==void 0?{stages:o.stages}:{}});e.set(t,i)}return{properties:e}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());return e}load(e){for(let[t,r]of Object.entries(e)){let s=this.scopes.get(t);s?.kind==="owned"&&s.bag.load(r)}}saveFragment(){return{version:V,scopes:this.save()}}loadFragment(e){if(e.version!==V)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${V})`);this.load(e.scopes)}assertFree(e){if(this.scopes.has(e))throw new Error(`scope \'@${e}\' is already registered`)}};function Q(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return n.values?.[0]??"";case"flags":return[];case"quality":return n.stages?.[0]??""}}function E(n){return n.ctx.host??{}}var L={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new l("random(a, b) requires exactly 2 arguments");let t=E(e).nextRandom;if(!t)throw new l("random() called without a PRNG in context");let r=e.evaluate(n[0]),s=e.evaluate(n[1]);if(typeof r!="number"||typeof s!="number")throw new l("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new l("random(a, b) arguments must be integers");let i=Math.min(r,s),o=Math.max(r,s);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:te("check_flags"),eval(n,e){let t=ee(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new l("check_flags() flag args must be +flagName or -flagName");if(s.sign==="+"?!t.includes(s.name):t.includes(s.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:te("set_flags"),eval(n,e){let t=[...ee(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new l("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let i=t.indexOf(s.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:B("visits"),eval:(n,e)=>E(e).visits?.(R(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:B("seen"),eval:(n,e)=>(E(e).visits?.(R(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:B("patter_visits"),eval:(n,e)=>E(e).patterVisits?.(R(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:B("patter_seen"),eval:(n,e)=>(E(e).patterVisits?.(R(n,e,"patter_seen"))??0)>0}}};function j(n,e){let t=n.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function R(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new l(`${t}(id) requires a string node id`);return r}function B(n){return(e,t)=>{let r=e[0];r&&r.kind!=="string"&&t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(id): the argument must be a string id literal (a scene / block / node id)`})}}function ee(n,e,t){if(!n)throw new l(`${t}() requires at least one argument (the flags variable)`);let r=e.evaluate(n);if(Array.isArray(r))return r;if(r===!1||r===null||r===void 0)return[];throw new l(`${t}() first argument must be a flags property`)}function te(n){return(e,t)=>{if(e.length===0)return;let r=e[0];if(r.kind!=="scopedvar"){t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(): first argument must be a flags property reference (@name or @scope.name)`});return}let s=t.schema.properties.get(r.scope)?.get(r.name);if(s&&s.type!=="flags"){let i=r.scope===t.defaultScope?r.name:`${r.scope}.${r.name}`;t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(): \'@${i}\' is not a flags property (got ${s.type})`});return}for(let i=1;i<e.length;i++){let o=e[i];o.kind!=="flagdelta"?t.report({path:[...t.path,"args",i],kind:"wrong-arg-type",severity:"error",message:`${n}(): argument ${i+1} must be +flagName or -flagName`}):s?.type==="flags"&&s.enumValues&&!s.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${n}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var De=/^@[A-Za-z0-9_.]+$/;function*Ie(n){let e="",t=0;for(;t<n.length;){let r=n[t];if(r==="{"&&n[t+1]==="{"){e+="{",t+=2;continue}if(r==="}"&&n[t+1]==="}"){e+="}",t+=2;continue}if(r==="{"){let s=n.indexOf("}",t+1);if(s!==-1){let i=n.slice(t,s+1),o=n.slice(t+1,s).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:De.test(o)?o:void 0},t=s+1;continue}e+=i,t=s+1;continue}}e+=r,t+=1}e&&(yield{kind:"text",value:e})}function Re(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function Be(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function Oe(n){let e="",t=!1;for(let r of n){if(Be(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function re(n,e,t){if(e.length===0||n.indexOf(e)<0)return n;let r="",s=0,i=!1;for(;s<n.length;){if(n.startsWith(e,s)){let o=n.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}r+=n.slice(s);break}r+=n[s],s+=1}return i?Oe(r):n}function ne(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of Ie(n)){if(r.kind==="text"){t+=r.value;continue}if(!r.ref){t+=r.raw;continue}let s=e(r.ref);t+=s===void 0?"":Re(s)}return t}function x(n,e){for(let t of n){e(t);let r=t.children;r&&x(r,e)}}function Pe(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function f(n){let e=n.gameId?.trim();return e||Pe(n.name)}function $(n){return`cast:${n}`}var _={open:"[",close:"]"},se="SFX";function O(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function P(n){let e=new Map,t=(r,s)=>{let i=O([...s,...r.tags??[]]);if(e.set(r.id,i),r.type==="group")for(let o of r.children)t(o,i);else for(let o of r.beats??[])e.set(o.id,O([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=O(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=O([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var ie=new WeakMap,N=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=r;let i=s[r]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let d=new Map;for(let u of e.cast??[])u.displayName&&d.set(u.name,u.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let p=new Map,g=new Map,h=new Map;for(let[u,y]of Object.entries(e.scenes)){this.sceneGameIdToId.set(f(y),u);let w=new Map;for(let m of y.blocks)g.set(m.id,{sceneId:u}),h.set(m.id,m),w.set(f(m),m.id),x(m.children,J=>p.set(J.id,J));this.blockGameIdToId.set(u,w)}let W=e.properties??[],A=W.filter(u=>u.shared??!0).map(ae),he=W.filter(u=>!(u.shared??!0)).map(ae),me=new Set(A.map(u=>u.name.toLowerCase())),F=new C().defineOwned("patter",A),K=new Set;if(t.world){let u=e.scopeRegistry?.scopes.find(w=>w.token==="world"),y=(u?.declarations??[]).map(ce);F.defineForeign("world",t.world,y,u?.writable??!0),K.add("world")}for(let u of e.scopeRegistry?.scopes??[]){if(K.has(u.token))continue;let y=(u.declarations??[]).map(ce);F.defineForeign(u.token,Fe(u.declarations??[]),y,u.writable??!0)}let z=new Map;for(let[u,y]of Object.entries(e.scenes)){let w=new Set((y.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));z.set(u,w)}this.host={bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:d,nodeIndex:p,blockIndex:g,blockById:h,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:P(e),shared:F,patterSharedDecls:A,patterLocalDecls:he,patterSharedNames:me,sceneSharedNames:z,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??_).open,captionClose:(e.closedCaptions??_).close,captionCharacter:e.closedCaptions?.character||se,refSplitCache:new Map}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){let t=this.saveGame(),r=i=>(i.setLocale(this.currentLocale),i.setClosedCaptions(this.host.captionsOn),i),s=new n(e,this.creationOptions);try{return s.loadGame(t),r(s)}catch{let i=new n(e,this.creationOptions);for(let[o,a]of Object.entries(t.flows)){let c=a.cursor.currentSceneId;try{i.openFlow(o,c!==null?{scene:c}:{})}catch{}}return r(i)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){let r=this.resolveSceneRef(t.scene),s=this.resolveBlockRef(r,t.block);this.flowsById.get(e)?.close();let i=new k(e,this.host,t.seed??this.defaultSeed);return this.flowsById.set(e,i),i.start(r,s),i}runFlow(e,t,r){let s=this.flowsById.get(e);if(!s)return this.openFlow(e,{scene:t,block:r}).advanceToStop().played;if(!s.goto(t,r))throw new Error(`runFlow: address not found: ${t}${r===void 0?"":` / ${r}`}`);return s.advanceToStop().played}resolveSceneRef(e){if(e!=null)return this.host.bundle.scenes[e]?e:this.sceneGameIdToId.get(e)??e}resolveBlockRef(e,t){if(t!=null){if(this.host.blockById.has(t))return t;if(e!=null){let r=this.blockGameIdToId.get(e)?.get(t);if(r)return r}return t}}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?f(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?f(t):void 0}tagsForBeat(e){return this.host.tagIndex.get(e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?this.host.tagIndex.get(t):void 0)??[]}tagsForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t);return(s!=null?this.host.tagIndex.get(s):void 0)??[]}getCast(){let e=[];for(let t of this.host.bundle.cast??[])t?.name&&e.push(t.name);return e}castForScene(e){let t=this.resolveSceneRef(e),r=t!=null?this.host.bundle.scenes[t]:void 0;if(!r)return[];let s=new Set;for(let i of r.blocks)oe(i.children,s);return[...s]}castForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t),i=s!=null?this.host.blockById.get(s):void 0;if(!i)return[];let o=new Set;return oe(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...f(e)?{gameId:f(e)}:{},name:e.name,...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...f(t)?{gameId:f(t)}:{},name:t.name,...this.tagsField(t.id),children:t.children.map(r=>this.outlineNode(r))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let r of t.blocks)x(r.children,s=>{if(s.type==="snippet")for(let i of s.beats??[])e.push({sceneId:t.id,blockId:r.id,snippetId:s.id,beat:this.beatInfo(i)})});return e}outlineNode(e){return e.type==="group"?{type:"group",id:e.id,...this.tagsField(e.id),...e.selector?{selector:e.selector}:{},...e.prompt?{prompt:this.beatInfo(e.prompt)}:{},children:e.children.map(t=>this.outlineNode(t))}:{type:"snippet",id:e.id,...this.tagsField(e.id),beats:(e.beats??[]).map(t=>this.beatInfo(t)),...e.jump?{jumpTo:e.jump.to,...e.jump.mode?{jumpMode:e.jump.mode}:{}}:{}}}beatInfo(e){let t=this.host.tagIndex.get(e.id),r={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){r.character=e.character;let s=this.host.defaultStrings[$(e.character)]??this.host.castDisplay.get(e.character);s!==void 0&&(r.characterName=s)}e.direction!==void 0&&(r.direction=e.direction)}if(e.kind==="line"||e.kind==="text"){let s=this.host.defaultStrings[e.id];s!==void 0&&(r.text=s)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=this.host.tagIndex.get(e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.shared.reseedOwned("patter",this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear(),this.host.stageBags.clear()}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e);this.host.shared.set(r,s,t)}listProperties(){return this.host.patterSharedDecls.map(e=>({ref:`@${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:Ne(e)}))}splitShared(e){let t=this.host.refSplitCache.get(e);if(t||(t=j(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return this.host.shared.save()}load(e){this.host.shared.load(e)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:2,shared:this.host.shared.save(),sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:pe(this.host.sharedSelectors),stageBags:Object.fromEntries([...this.host.stageBags].map(([t,r])=>[t,{...r}])),flows:e}}loadGame(e){if(e.version!==2)throw new Error(`unsupported save version: ${e.version}`);this.host.shared.load(e.shared),this.host.sharedVisits.clear();for(let[t,r]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(t,r);this.host.sharedSelectors.clear();for(let[t,r]of de(e.sharedSelectors))this.host.sharedSelectors.set(t,r);this.host.stageBags.clear();for(let[t,r]of Object.entries(e.stageBags??{}))this.host.stageBags.set(t,{...r});this.flowsById.clear();for(let[t,r]of Object.entries(e.flows)){let s=new k(t,this.host,this.defaultSeed);s.restore(r),this.flowsById.set(t,s)}}},k=class{id;host;local;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPromptBeat=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.shared.get("patter",e):this.local.get("patter",e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.shared.set("patter",e,t):this.local.set("patter",e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;return t===null?void 0:(this.host.sceneSharedNames.get(t)?.has(e)?this.host.stageBags.get(t):this.sceneBags.get(t))?.[e]},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.host.sceneSharedNames.get(r)?.has(e)?this.host.stageBags.get(r):this.sceneBags.get(r);s&&(s[e]=t)}};evalCtx;constructor(e,t,r){this.id=e,this.host=t,this.rngState=r>>>0,this.local=this.freshLocal();let s={...t.shared.toEvalContext().scopes};s.patter=this.patterResolver,s.scene=this.sceneResolver,this.evalCtx={scopes:s,host:{nextRandom:this.rng,visits:i=>this.visitCounts.get(i)??0,patterVisits:i=>this.host.sharedVisits.get(i)??0},qualities:(i,o)=>this.stagesFor(i,o)}}stagesFor(e,t){let r=t.toLowerCase(),s=i=>i?.find(o=>o.name.toLowerCase()===r&&o.type==="quality")?.stages;if(e==="patter")return s(this.host.patterSharedDecls)??s(this.host.patterLocalDecls);if(e==="scene"){let i=this.currentSceneId!=null?this.host.bundle.scenes[this.currentSceneId]:void 0;return s(i?.sceneProps)}return s(this.host.bundle.scopeRegistry?.scopes.find(i=>i.token===e)?.declarations)}start(e,t){if(this.sceneBags.clear(),this.local=this.freshLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.started=!0,t){let r=this.host.blockIndex.get(t);if(!r)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(r.sceneId),this.stack=[{sceneId:r.sceneId,containerId:t,index:0}],this.enter(t)}else{let r=e??Object.keys(this.host.bundle.scenes)[0],s=r?this.host.bundle.scenes[r]:void 0;if(!s)throw new Error(r?`unknown scene: ${r}`:"no scenes in bundle");this.enterSceneSetup(r);let i=s.blocks[0];i&&(this.stack=[{sceneId:r,containerId:i.id,index:0}],this.enter(i.id))}this.settle()}reset(e,t){this.start(e,t)}goto(e,t){if(this.closed)return!1;if(e==="END")return this.started=!0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=this.host.sceneGameIdToId.get(e)??(this.host.bundle.scenes[e]?e:void 0);if(r===void 0)return!1;let s;return t!==void 0&&(s=this.host.blockGameIdToId.get(r)?.get(t)??(this.host.blockIndex.get(t)?.sceneId===r?t:void 0),s===void 0)?!1:this.started?(this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??r,"jump"),this.settle(),!0):(this.start(r,s),!0)}close(){this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.pendingPromptBeat){let e=this.pendingPromptBeat;return this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.beatResult(e)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit);let s=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(s);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let r=t.options.find(i=>i.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);let s=t.byId.get(e);this.pendingChoice=null,this.pendingPromptBeat=this.host.replayPromptOnChoose?this.promptBeatOf(s)??null:null,this.pendingPromptOwnerId=this.pendingPromptBeat?s.id:null,this.enterChild(s)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:r}=this.splitRef(e);return t==="patter"?this.patterResolver.get(r):t==="scene"?this.sceneResolver.get(r):this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitRef(e);if(r==="patter")this.patterResolver.set(s,t);else if(r==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);this.sceneResolver.set(s,t)}else this.host.shared.set(r,s,t)}snapshot(){return{scopes:this.local.save(),sceneBags:Object.fromEntries([...this.sceneBags].map(([e,t])=>[e,{...t}])),rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,selectors:pe(this.selectors)}}}restore(e){this.rngState=e.rngState>>>0,this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(r=>{let{nextId:s,...i}=r;if(s!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===s)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.sceneBags=new Map(Object.entries(e.sceneBags??{}).map(([r,s])=>[r,{...s}])),this.local=this.freshLocal(),this.local.load(e.scopes),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=de(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let r=new Map,s=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);o&&(r.set(i.id,o),s.push({...i}))}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:r})}if(this.pendingPromptBeat=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId);this.pendingPromptBeat=r?this.promptBeatOf(r)??null:null,this.pendingPromptBeat||(this.pendingPromptOwnerId=null)}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let r=this.selectChild(e);r&&this.enterChild(r)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let r=this.host.nodeIndex.get(e);if(r&&r.type==="group")return r.children}beginSnippet(e){this.runEffects(e.onEnter),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],r=new Map,s=[];for(let o of e.children){if(o.fallback===!0){s.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),c=o.secretUntilEligible===!0;!a&&c||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData}),r.set(o.id,o))}if(t.length>0){this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(e==="END"){this.flowEnded=!0,this.stack=[];return}let r,s,i=this.host.bundle.scenes[e];if(i){this.enterSceneSetup(e);let a=i.blocks[0];if(!a){t==="jump"&&(this.stack=[]);return}r=e,s=a.id}else{let a=this.host.blockIndex.get(e);if(!a)throw new Error(`jump target not found: ${e}`);a.sceneId!==this.currentSceneId&&this.enterSceneSetup(a.sceneId),r=a.sceneId,s=e}this.enter(s);let o={sceneId:r,containerId:s,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.filter(s=>this.eligible(s));if(t.length===0)return null;let r=this.selectorState(e);switch(e.selector){case"branch":return t[0];case"sequence":{let s=e.options?.order??"sequential",i=e.options?.exhaust??"once";return s==="shuffle"?this.pickShuffle(t,i,r):s==="specificity"?this.pickSpecificity(t,i,r):this.pickSequential(t,i,r)}default:return null}}pickSequential(e,t,r){let s=e.length,i=r.seq??0;return r.seq=i+1,t==="repeat"?e[i%s]:i<s?e[i]:t==="stick"?e[s-1]:null}pickShuffle(e,t,r){let s=e.length,i=t==="stick",o=()=>(i?e.slice(0,s-1):e).map(g=>g.id);if(r.bag===void 0&&(r.bag=o()),r.bag.length===0){if(t==="once")return null;if(i){let g=e[s-1];return r.last=g.id,g}r.bag=o()}let a=r.bag,c=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,d=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&d>=c&&d++;let p=a[d];return a.splice(d,1),r.last=p,e.find(g=>g.id===p)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(p=>p.id));let d=new Set(r.bag);if(s=e.filter(p=>d.has(p.id)),s.length===0)return t==="stick"&&r.last!==void 0?e.find(p=>p.id===r.last)??null:null}let i=-1,a=s.map(d=>{let p=this.specScore(d);return p>i&&(i=p),{c:d,s:p}}).filter(d=>d.s===i).map(d=>d.c),c;if(a.length===1)c=a[0];else{let d=r.last!==void 0?a.findIndex(g=>g.id===r.last):-1,p=Math.floor(this.rng()*(d>=0?a.length-1:a.length));d>=0&&p>=d&&p++,c=a[p]}return t!=="repeat"&&(r.bag=r.bag.filter(d=>d!==c.id)),r.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0):0}matchedSpec(e,t){return Y(e,s=>le(D(s,this.evalCtx,L)),{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=t.get(e.id);return r||(r={},t.set(e.id,r)),r}runEffects(e){for(let t of e??[])this.setProperty(t.target,this.evalExpr(t.value))}eligible(e){return e.condition?le(this.evalExpr(e.condition)):!0}evalExpr(e){return D(this.conditionAst(e),this.evalCtx,L)}conditionAst(e){let t=ie.get(e);return t||(t=v(e.ast),ie.set(e,t)),t}enter(e){this.visitCounts.set(e,(this.visitCounts.get(e)??0)+1),this.host.sharedVisits.set(e,(this.host.sharedVisits.get(e)??0)+1)}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=this.rngState+1831565813|0;this.rngState=e;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296};beatResult(e){let t=this.host.tagIndex.get(e.id),r=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...r};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),gameData:e.gameData,...r};case"line":{let s=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?s:this.interpolate(s)),c=i&&a.length===0;return{type:"line",id:e.id,text:a,character:c?void 0:e.character,characterName:c?void 0:this.resolveCharacterName(e.character),direction:c?void 0:e.direction,gameData:e.gameData,...r}}}}interpolate(e){return ne(e,t=>this.getProperty(t))}stripCaptions(e){return re(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let r=this.interpolate(this.resolveString(t.id));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(r=>r.kind==="line"||r.kind==="text")}firstTextSnippetIn(e){let t;return x(e,r=>{!t&&r.type==="snippet"&&(r.beats??[]).some(s=>s.kind==="line"||s.kind==="text")&&(t=r)}),t}resolveString(e){if(this.host.emitIds)return e;let t=this.host.strings[e];if(t!==void 0)return t;let r=this.host.defaultStrings[e];return r!==void 0?`<Untranslated: ${e}> ${r}`:e}resolveCharacterName(e){if(e===void 0||this.host.emitIds)return;let t=$(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){let t=this.host.refSplitCache.get(e);return t||(t=j(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t}freshLocal(){return new C().defineOwned("patter",this.host.patterLocalDecls)}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;if(!this.sceneBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)||(r[i]=q(s))}this.sceneBags.set(e.id,r)}if(!this.host.stageBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)&&(r[i]=q(s))}this.host.stageBags.set(e.id,r)}for(let r of e.sceneProps??[]){if(!r.temporary)continue;let s=r.name.toLowerCase(),i=t.has(s)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);i&&(i[s]=q(r))}}};function oe(n,e){x(n,t=>{if(t.type==="group"){t.prompt?.kind==="line"&&t.prompt.character&&e.add(t.prompt.character);return}for(let r of t.beats??[])r.kind==="line"&&r.character&&e.add(r.character)})}function pe(n){let e={};for(let[t,r]of n){let s={};r.seq!==void 0&&(s.seq=r.seq),r.bag&&(s.bag=[...r.bag]),r.last!==void 0&&(s.last=r.last),e[t]=s}return e}function de(n){let e=new Map;for(let[t,r]of Object.entries(n??{})){let s={};r.seq!==void 0&&(s.seq=r.seq),r.bag&&(s.bag=[...r.bag]),r.last!==void 0&&(s.last=r.last),e.set(t,s)}return e}function ae(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Ne(n){if(n.default!==void 0)return n.default;switch(n.type){case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??"";default:return!1}}function ce(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function Ae(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??""}}function Fe(n){let e=r=>r.toLowerCase(),t=new Map;for(let r of n)t.set(e(r.name),Ae(r));return{get:r=>t.get(e(r)),set:(r,s)=>{t.set(e(r),s)}}}function q(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??""}}function le(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Ge=(n,e)=>n.shared??e;function H(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:Ge(n,e)}}function Te(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function Ve(n,e){e.blocks++;let t=[...n.children];for(;t.length;){let r=t.pop();if(r.type==="group"){e.groups++,r.prompt&&e.prompts++,t.push(...r.children);continue}e.snippets++;for(let s of r.beats??[])e.beats++,s.kind==="gameEvent"&&e.gameEvents++}}function ue(n){let e={scenes:0,blocks:0,groups:0,snippets:0,beats:0,prompts:0,gameEvents:0,cast:n.cast?.length??0},t=[],r=[];for(let o of Object.values(n.scenes)){e.scenes++;let a=f(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:f(c),name:c.name}))});for(let c of o.blocks)Ve(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>H(c,!1))})}let s=(n.scopeRegistry?.scopes??[]).map(o=>({token:o.token,writable:o.writable??!0,opaque:o.declarations===void 0,properties:(o.declarations??[]).map(a=>H(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Te)}));return{identity:{schema:n.schema,project:n.content.project,...n.content.version!==void 0?{version:n.content.version}:{},...n.content.hash!==void 0?{hash:n.content.hash}:{},...n.content.structureHash!==void 0?{structureHash:n.content.structureHash}:{},voiced:n.voiced,defaultLocale:n.locales.default,locales:[...n.locales.included],localisation:n.localisation?.mode??"embedded",sourceDebug:n.localisation?.sourceDebug??!1},addresses:t,hostScopes:s,properties:{patter:(n.properties??[]).map(o=>H(o,!0)),scene:r},gameData:i,counts:e}}function ge(n,e){return n.gameDataFields?.[e]??[]}function U(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function fe(n,e){let t={};for(let r of n){let s=U(n,e,r.name);s!==void 0&&(t[r.name]=s)}for(let[r,s]of Object.entries(e??{}))r in t||(t[r]=s);return t}return xe(Me);})();\n//# sourceMappingURL=patterplay.min.js.map';
|
|
984
|
+
var PLAYABLE_RUNTIME_JS = '"use strict";var Patterplay=(()=>{var G=Object.defineProperty;var ve=Object.getOwnPropertyDescriptor;var be=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var xe=(n,e)=>{for(var t in e)G(n,t,{get:e[t],enumerable:!0})},ke=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of be(e))!we.call(n,s)&&s!==t&&G(n,s,{get:()=>e[s],enumerable:!(r=ve(e,s))||r.enumerable});return n};var Ce=n=>ke(G({},"__esModule",{value:!0}),n);var Le={};xe(Le,{Engine:()=>A,Flow:()=>k,buildTagIndex:()=>N,describeBundle:()=>fe,effectiveGameData:()=>me,gameDataFields:()=>he,gameDataValue:()=>K});function v(n){switch(n[0]){case"b":return{kind:"bool",value:n[1]};case"n":return{kind:"number",value:n[1]};case"s":return{kind:"string",value:n[1]};case"sv":return{kind:"scopedvar",scope:n[1],name:n[2]};case"u":return{kind:"unary",op:n[1],operand:v(n[2])};case"bin":return{kind:"binary",op:n[1],left:v(n[2]),right:v(n[3])};case"call":{let e=n.slice(2).map(v);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var l=class extends Error{constructor(e){super(e),this.name="EvalError"}};function I(n,e,t){let r=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),s=i=>{switch(i.kind){case"bool":return i.value;case"number":return i.value;case"string":return i.value;case"scopedvar":{let o=e.scopes[i.scope];if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):o[i.name];if(a===void 0){if(r.get(i.scope)==="throw")throw new l(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!t.functions[i.name]){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new l(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=V(a,e);if(c===void 0)throw new l("advance() needs a quality reference (@scope.name of a quality property)");let d=S(s(a),c,"advance");return c[Math.min(d+1,c.length-1)]}let o=t.functions[i.name];if(!o)throw new l(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new l("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=s(i.operand);if(typeof a!="boolean")throw new l(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new l(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let g=s(i.left);if(typeof g!="boolean")throw new l(`\'and\' requires boolean operands, left is ${typeof g}`);if(!g)return!1;let h=s(i.right);if(typeof h!="boolean")throw new l(`\'and\' requires boolean operands, right is ${typeof h}`);return h}if(i.op==="or"){let g=s(i.left);if(typeof g!="boolean")throw new l(`\'or\' requires boolean operands, left is ${typeof g}`);if(g)return!0;let h=s(i.right);if(typeof h!="boolean")throw new l(`\'or\' requires boolean operands, right is ${typeof h}`);return h}let o=s(i.left),a=s(i.right),c=V(i.left,e),d=V(i.right,e),p=c??d;if(p!==void 0){if(c&&d&&!Ee(c,d)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new l(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return S(o,p,">")>S(a,p,">");case">=":return S(o,p,">=")>=S(a,p,">=");case"<":return S(o,p,"<")<S(a,p,"<");case"<=":return S(o,p,"<=")<=S(a,p,"<=");case"+":case"-":case"*":case"/":throw new l(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return Z(o,a);case"!=":return!Z(o,a);case">":return b(o,a,">"),o>a;case">=":return b(o,a,">="),o>=a;case"<":return b(o,a,"<"),o<a;case"<=":return b(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new l(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return b(o,a,"-"),o-a;case"*":return b(o,a,"*"),o*a;case"/":if(b(o,a,"/"),a===0)throw new l("division by zero");return o/a}}}};return s(n)}function Z(n,e){if(Array.isArray(n)||Array.isArray(e)){if(!Array.isArray(n)||!Array.isArray(e)||n.length!==e.length)return!1;let t=[...n].sort(),r=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==r[s])return!1;return!0}return n===e}function b(n,e,t){if(typeof n!="number"||typeof e!="number")throw new l(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}function V(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function S(n,e,t){if(typeof n!="string")throw new l(`\'${t}\' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new l(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var Ee=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);function C(n){if(Number.isNaN(n)||!Number.isFinite(n))return 0;let e=Math.trunc(n)%4294967296;return e<0?e+4294967296:e}function M(n){let e=C(n);return{next(){e=e+1831565813>>>0;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296},state(){return e}}}var De={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},Ie=[De];function Q(n,e,t){let r=t?.countingCalls??Ie;return R(n,t?.want??!0,e,r)}function R(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=R(n.left,e,t,r),i=R(n.right,e,t,r);return n.op==="and"===e?s>0&&i>0?s+i:0:Math.max(s,i)}if(n.kind==="unary"&&n.op==="not")return R(n.operand,!e,t,r);if(n.kind==="call"){let s=r.find(i=>i.name===n.name);if(s){let i=s.count(n),o=t(n);return e?o?i:0:o?0:1}}return t(n)===e?1:0}var L=class n{values={};decls=new Map;subscribers=new Set;auditors=new Set;norm;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),this.seed(e)}seed(e){for(let t of e){let r=this.norm(t.name);this.decls.set(r,t),this.values[r]=structuredClone(t.default??te(t))}}get(e){return this.values[this.norm(e)]}set(e,t,r){let s=this.norm(e);if(this.decls.get(s)?.writable===!1)throw new Error(`\'${e}\' is read-only`);let i={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let o of this.auditors)o(i);if(!i.silent)for(let o of this.subscribers)o(i);return i}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>ee(t,this.get(e),void 0,e))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function ee(n,e,t,r){return{name:r??n.name.toLowerCase(),type:n.type,value:e,default:n.default??te(n),...n.values!==void 0?{values:n.values}:{},writable:t??n.writable??!0}}var j=1,E=class{scopes=new Map;defineOwned(e,t){return this.mountOwned(e,new L(t))}mountOwned(e,t){return this.assertFree(e),this.scopes.set(e,{kind:"owned",bag:t}),this}ownedBag(e){let t=this.scopes.get(e);if(!t||t.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);return t.bag}reseedOwned(e,t){return this.ownedBag(e).reseed(t),this}defineForeign(e,t,r=[],s=!0){this.assertFree(e);let i=new Map;for(let o of r)i.set(o.name.toLowerCase(),o);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:i,scopeWritable:s}),this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(r)return r.kind==="owned"?r.bag.get(t):r.resolver.get(t.toLowerCase())}set(e,t,r){let s=this.scopes.get(e);if(!s)throw new Error(`unknown scope \'@${e}\'`);if(s.kind==="owned"){try{s.bag.set(t,r)}catch{throw new Error(`\'@${e}.${t}\' is read-only`)}return}let i=t.toLowerCase();if(!this.foreignWritable(s,i))throw new Error(`\'@${e}.${t}\' is read-only`);s.resolver.set(i,r)}foreignWritable(e,t){return e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}listProperties(){let e=[];for(let[t,r]of this.scopes)if(r.kind==="owned")for(let s of r.bag.rows())e.push({scope:t,...s});else for(let s of r.decls.values())e.push({scope:t,...ee(s,r.resolver.get(s.name.toLowerCase()),this.foreignWritable(r,s.name.toLowerCase()))});return e}toEvalContext(e){let t={};for(let[s,i]of this.scopes)t[s]=i.kind==="owned"?i.bag.values:i.resolver;let r=this.qualityLadders();return r.size===0?{scopes:t,host:e}:{scopes:t,host:e,qualities:(s,i)=>r.get(s)?.get(i.toLowerCase())}}qualityLadders(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];for(let i of s){if(i.type!=="quality"||i.stages===void 0)continue;let o=e.get(t);o||(o=new Map,e.set(t,o)),o.set(i.name.toLowerCase(),i.stages)}}return e}toSchema(){let e=new Map;for(let[t,r]of this.scopes){let s=r.kind==="owned"?r.bag.declarations():[...r.decls.values()];if(s.length===0)continue;let i=new Map;for(let o of s)i.set(o.name.toLowerCase(),{type:o.type,enumValues:o.values,...o.stages!==void 0?{stages:o.stages}:{}});e.set(t,i)}return{properties:e}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());return e}load(e){for(let[t,r]of Object.entries(e)){let s=this.scopes.get(t);s?.kind==="owned"&&s.bag.load(r)}}saveFragment(){return{version:j,scopes:this.save()}}loadFragment(e){if(e.version!==j)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${j})`);this.load(e.scopes)}assertFree(e){if(this.scopes.has(e))throw new Error(`scope \'@${e}\' is already registered`)}};function te(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return n.values?.[0]??"";case"flags":return[];case"quality":return n.stages?.[0]??""}}function D(n){return n.ctx.host??{}}var $={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new l("random(a, b) requires exactly 2 arguments");let t=D(e).nextRandom;if(!t)throw new l("random() called without a PRNG in context");let r=e.evaluate(n[0]),s=e.evaluate(n[1]);if(typeof r!="number"||typeof s!="number")throw new l("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new l("random(a, b) arguments must be integers");let i=Math.min(r,s),o=Math.max(r,s);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:ne("check_flags"),eval(n,e){let t=re(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new l("check_flags() flag args must be +flagName or -flagName");if(s.sign==="+"?!t.includes(s.name):t.includes(s.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:ne("set_flags"),eval(n,e){let t=[...re(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new l("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let i=t.indexOf(s.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:O("visits"),eval:(n,e)=>D(e).visits?.(B(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:O("seen"),eval:(n,e)=>(D(e).visits?.(B(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:O("patter_visits"),eval:(n,e)=>D(e).patterVisits?.(B(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:O("patter_seen"),eval:(n,e)=>(D(e).patterVisits?.(B(n,e,"patter_seen"))??0)>0}}};function q(n,e){let t=n.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function B(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new l(`${t}(id) requires a string node id`);return r}function O(n){return(e,t)=>{let r=e[0];r&&r.kind!=="string"&&t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(id): the argument must be a string id literal (a scene / block / node id)`})}}function re(n,e,t){if(!n)throw new l(`${t}() requires at least one argument (the flags variable)`);let r=e.evaluate(n);if(Array.isArray(r))return r;if(r===!1||r===null||r===void 0)return[];throw new l(`${t}() first argument must be a flags property`)}function ne(n){return(e,t)=>{if(e.length===0)return;let r=e[0];if(r.kind!=="scopedvar"){t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(): first argument must be a flags property reference (@name or @scope.name)`});return}let s=t.schema.properties.get(r.scope)?.get(r.name);if(s&&s.type!=="flags"){let i=r.scope===t.defaultScope?r.name:`${r.scope}.${r.name}`;t.report({path:[...t.path,"args",0],kind:"wrong-arg-type",severity:"error",message:`${n}(): \'@${i}\' is not a flags property (got ${s.type})`});return}for(let i=1;i<e.length;i++){let o=e[i];o.kind!=="flagdelta"?t.report({path:[...t.path,"args",i],kind:"wrong-arg-type",severity:"error",message:`${n}(): argument ${i+1} must be +flagName or -flagName`}):s?.type==="flags"&&s.enumValues&&!s.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${n}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var Re=/^@[A-Za-z0-9_.]+$/;function*Be(n){let e="",t=0;for(;t<n.length;){let r=n[t];if(r==="{"&&n[t+1]==="{"){e+="{",t+=2;continue}if(r==="}"&&n[t+1]==="}"){e+="}",t+=2;continue}if(r==="{"){let s=n.indexOf("}",t+1);if(s!==-1){let i=n.slice(t,s+1),o=n.slice(t+1,s).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:Re.test(o)?o:void 0},t=s+1;continue}e+=i,t=s+1;continue}}e+=r,t+=1}e&&(yield{kind:"text",value:e})}function Oe(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function Pe(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function Ne(n){let e="",t=!1;for(let r of n){if(Pe(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function se(n,e,t){if(e.length===0||n.indexOf(e)<0)return n;let r="",s=0,i=!1;for(;s<n.length;){if(n.startsWith(e,s)){let o=n.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}r+=n.slice(s);break}r+=n[s],s+=1}return i?Ne(r):n}function ie(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of Be(n)){if(r.kind==="text"){t+=r.value;continue}if(!r.ref){t+=r.raw;continue}let s=e(r.ref);t+=s===void 0?"":Oe(s)}return t}function x(n,e){for(let t of n){e(t);let r=t.children;r&&x(r,e)}}function Ae(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function f(n){let e=n.gameId?.trim();return e||Ae(n.name)}function _(n){return`cast:${n}`}var H={open:"[",close:"]"},oe="SFX";function P(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function N(n){let e=new Map,t=(r,s)=>{let i=P([...s,...r.tags??[]]);if(e.set(r.id,i),r.type==="group")for(let o of r.children)t(o,i);else for(let o of r.beats??[])e.set(o.id,P([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=P(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=P([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var ae=new WeakMap,A=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=r;let i=s[r]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let d=new Map;for(let u of e.cast??[])u.displayName&&d.set(u.name,u.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let p=new Map,g=new Map,h=new Map;for(let[u,y]of Object.entries(e.scenes)){this.sceneGameIdToId.set(f(y),u);let w=new Map;for(let m of y.blocks)g.set(m.id,{sceneId:u}),h.set(m.id,m),w.set(f(m),m.id),x(m.children,X=>p.set(X.id,X));this.blockGameIdToId.set(u,w)}let J=e.properties??[],F=J.filter(u=>u.shared??!0).map(le),Se=J.filter(u=>!(u.shared??!0)).map(le),ye=new Set(F.map(u=>u.name.toLowerCase())),T=new E().defineOwned("patter",F),z=new Set;if(t.world){let u=e.scopeRegistry?.scopes.find(w=>w.token==="world"),y=(u?.declarations??[]).map(pe);T.defineForeign("world",t.world,y,u?.writable??!0),z.add("world")}for(let u of e.scopeRegistry?.scopes??[]){if(z.has(u.token))continue;let y=(u.declarations??[]).map(pe);T.defineForeign(u.token,Ge(u.declarations??[]),y,u.writable??!0)}let Y=new Map;for(let[u,y]of Object.entries(e.scenes)){let w=new Set((y.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));Y.set(u,w)}this.host={bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:d,nodeIndex:p,blockIndex:g,blockById:h,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:N(e),shared:T,patterSharedDecls:F,patterLocalDecls:Se,patterSharedNames:ye,sceneSharedNames:Y,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??H).open,captionClose:(e.closedCaptions??H).close,captionCharacter:e.closedCaptions?.character||oe,refSplitCache:new Map}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){let t=this.saveGame(),r=i=>(i.setLocale(this.currentLocale),i.setClosedCaptions(this.host.captionsOn),i),s=new n(e,this.creationOptions);try{return s.loadGame(t),r(s)}catch{let i=new n(e,this.creationOptions);for(let[o,a]of Object.entries(t.flows)){let c=a.cursor.currentSceneId;try{i.openFlow(o,c!==null?{scene:c}:{})}catch{}}return r(i)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){let r=this.resolveSceneRef(t.scene),s=this.resolveBlockRef(r,t.block);this.flowsById.get(e)?.close();let i=new k(e,this.host,t.seed??this.defaultSeed);return this.flowsById.set(e,i),i.start(r,s),i}runFlow(e,t,r){let s=this.flowsById.get(e);if(!s)return this.openFlow(e,{scene:t,block:r}).advanceToStop().played;if(!s.goto(t,r))throw new Error(`runFlow: address not found: ${t}${r===void 0?"":` / ${r}`}`);return s.advanceToStop().played}resolveSceneRef(e){if(e!=null)return this.host.bundle.scenes[e]?e:this.sceneGameIdToId.get(e)??e}resolveBlockRef(e,t){if(t!=null){if(this.host.blockById.has(t))return t;if(e!=null){let r=this.blockGameIdToId.get(e)?.get(t);if(r)return r}return t}}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?f(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?f(t):void 0}tagsForBeat(e){return this.host.tagIndex.get(e)??[]}tagsForScene(e){let t=this.resolveSceneRef(e);return(t!=null?this.host.tagIndex.get(t):void 0)??[]}tagsForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t);return(s!=null?this.host.tagIndex.get(s):void 0)??[]}getCast(){let e=[];for(let t of this.host.bundle.cast??[])t?.name&&e.push(t.name);return e}castForScene(e){let t=this.resolveSceneRef(e),r=t!=null?this.host.bundle.scenes[t]:void 0;if(!r)return[];let s=new Set;for(let i of r.blocks)ce(i.children,s);return[...s]}castForBlock(e,t){let r=this.resolveSceneRef(e),s=this.resolveBlockRef(r,t),i=s!=null?this.host.blockById.get(s):void 0;if(!i)return[];let o=new Set;return ce(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...f(e)?{gameId:f(e)}:{},name:e.name,...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...f(t)?{gameId:f(t)}:{},name:t.name,...this.tagsField(t.id),children:t.children.map(r=>this.outlineNode(r))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let r of t.blocks)x(r.children,s=>{if(s.type==="snippet")for(let i of s.beats??[])e.push({sceneId:t.id,blockId:r.id,snippetId:s.id,beat:this.beatInfo(i)})});return e}outlineNode(e){return e.type==="group"?{type:"group",id:e.id,...this.tagsField(e.id),...e.selector?{selector:e.selector}:{},...e.prompt?{prompt:this.beatInfo(e.prompt)}:{},children:e.children.map(t=>this.outlineNode(t))}:{type:"snippet",id:e.id,...this.tagsField(e.id),beats:(e.beats??[]).map(t=>this.beatInfo(t)),...e.jump?{jumpTo:e.jump.to,...e.jump.mode?{jumpMode:e.jump.mode}:{}}:{}}}beatInfo(e){let t=this.host.tagIndex.get(e.id),r={id:e.id,kind:e.kind};if(e.kind==="line"){if(e.character!==void 0){r.character=e.character;let s=this.host.defaultStrings[_(e.character)]??this.host.castDisplay.get(e.character);s!==void 0&&(r.characterName=s)}e.direction!==void 0&&(r.direction=e.direction)}if(e.kind==="line"||e.kind==="text"){let s=this.host.defaultStrings[e.id];s!==void 0&&(r.text=s)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=this.host.tagIndex.get(e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.shared.reseedOwned("patter",this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear(),this.host.stageBags.clear()}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e);this.host.shared.set(r,s,t)}listProperties(){return this.host.patterSharedDecls.map(e=>({ref:`@${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:Fe(e)}))}splitShared(e){let t=this.host.refSplitCache.get(e);if(t||(t=q(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return this.host.shared.save()}load(e){this.host.shared.load(e)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:2,shared:this.host.shared.save(),sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:ue(this.host.sharedSelectors),stageBags:Object.fromEntries([...this.host.stageBags].map(([t,r])=>[t,{...r}])),flows:e}}loadGame(e){if(e.version!==2)throw new Error(`unsupported save version: ${e.version}`);this.host.shared.load(e.shared),this.host.sharedVisits.clear();for(let[t,r]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(t,r);this.host.sharedSelectors.clear();for(let[t,r]of ge(e.sharedSelectors))this.host.sharedSelectors.set(t,r);this.host.stageBags.clear();for(let[t,r]of Object.entries(e.stageBags??{}))this.host.stageBags.set(t,{...r});this.flowsById.clear();for(let[t,r]of Object.entries(e.flows)){let s=new k(t,this.host,this.defaultSeed);s.restore(r),this.flowsById.set(t,s)}}},k=class{id;host;local;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPromptBeat=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.shared.get("patter",e):this.local.get("patter",e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.shared.set("patter",e,t):this.local.set("patter",e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;return t===null?void 0:(this.host.sceneSharedNames.get(t)?.has(e)?this.host.stageBags.get(t):this.sceneBags.get(t))?.[e]},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.host.sceneSharedNames.get(r)?.has(e)?this.host.stageBags.get(r):this.sceneBags.get(r);s&&(s[e]=t)}};evalCtx;constructor(e,t,r){this.id=e,this.host=t,this.rngState=C(r),this.local=this.freshLocal();let s={...t.shared.toEvalContext().scopes};s.patter=this.patterResolver,s.scene=this.sceneResolver,this.evalCtx={scopes:s,host:{nextRandom:this.rng,visits:i=>this.visitCounts.get(i)??0,patterVisits:i=>this.host.sharedVisits.get(i)??0},qualities:(i,o)=>this.stagesFor(i,o)}}stagesFor(e,t){let r=t.toLowerCase(),s=i=>i?.find(o=>o.name.toLowerCase()===r&&o.type==="quality")?.stages;if(e==="patter")return s(this.host.patterSharedDecls)??s(this.host.patterLocalDecls);if(e==="scene"){let i=this.currentSceneId!=null?this.host.bundle.scenes[this.currentSceneId]:void 0;return s(i?.sceneProps)}return s(this.host.bundle.scopeRegistry?.scopes.find(i=>i.token===e)?.declarations)}start(e,t){if(this.sceneBags.clear(),this.local=this.freshLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.started=!0,t){let r=this.host.blockIndex.get(t);if(!r)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(r.sceneId),this.stack=[{sceneId:r.sceneId,containerId:t,index:0}],this.enter(t)}else{let r=e??Object.keys(this.host.bundle.scenes)[0],s=r?this.host.bundle.scenes[r]:void 0;if(!s)throw new Error(r?`unknown scene: ${r}`:"no scenes in bundle");this.enterSceneSetup(r);let i=s.blocks[0];i&&(this.stack=[{sceneId:r,containerId:i.id,index:0}],this.enter(i.id))}this.settle()}reset(e,t){this.start(e,t)}goto(e,t){if(this.closed)return!1;if(e==="END")return this.started=!0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=this.host.sceneGameIdToId.get(e)??(this.host.bundle.scenes[e]?e:void 0);if(r===void 0)return!1;let s;return t!==void 0&&(s=this.host.blockGameIdToId.get(r)?.get(t)??(this.host.blockIndex.get(t)?.sceneId===r?t:void 0),s===void 0)?!1:this.started?(this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??r,"jump"),this.settle(),!0):(this.start(r,s),!0)}close(){this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.pendingPromptBeat){let e=this.pendingPromptBeat;return this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.beatResult(e)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit);let s=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(s);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let r=t.options.find(i=>i.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);let s=t.byId.get(e);this.pendingChoice=null,this.pendingPromptBeat=this.host.replayPromptOnChoose?this.promptBeatOf(s)??null:null,this.pendingPromptOwnerId=this.pendingPromptBeat?s.id:null,this.enterChild(s)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:r}=this.splitRef(e);return t==="patter"?this.patterResolver.get(r):t==="scene"?this.sceneResolver.get(r):this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitRef(e);if(r==="patter")this.patterResolver.set(s,t);else if(r==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);this.sceneResolver.set(s,t)}else this.host.shared.set(r,s,t)}snapshot(){return{scopes:this.local.save(),sceneBags:Object.fromEntries([...this.sceneBags].map(([e,t])=>[e,{...t}])),rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,selectors:ue(this.selectors)}}}restore(e){this.rngState=C(e.rngState),this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(r=>{let{nextId:s,...i}=r;if(s!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===s)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.sceneBags=new Map(Object.entries(e.sceneBags??{}).map(([r,s])=>[r,{...s}])),this.local=this.freshLocal(),this.local.load(e.scopes),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=ge(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let r=new Map,s=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);o&&(r.set(i.id,o),s.push({...i}))}s.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:s,byId:r})}if(this.pendingPromptBeat=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId);this.pendingPromptBeat=r?this.promptBeatOf(r)??null:null,this.pendingPromptBeat||(this.pendingPromptOwnerId=null)}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let r=this.selectChild(e);r&&this.enterChild(r)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let r=this.host.nodeIndex.get(e);if(r&&r.type==="group")return r.children}beginSnippet(e){this.runEffects(e.onEnter),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],r=new Map,s=[];for(let o of e.children){if(o.fallback===!0){s.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),c=o.secretUntilEligible===!0;!a&&c||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData}),r.set(o.id,o))}if(t.length>0){this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(e==="END"){this.flowEnded=!0,this.stack=[];return}let r,s,i=this.host.bundle.scenes[e];if(i){this.enterSceneSetup(e);let a=i.blocks[0];if(!a){t==="jump"&&(this.stack=[]);return}r=e,s=a.id}else{let a=this.host.blockIndex.get(e);if(!a)throw new Error(`jump target not found: ${e}`);a.sceneId!==this.currentSceneId&&this.enterSceneSetup(a.sceneId),r=a.sceneId,s=e}this.enter(s);let o={sceneId:r,containerId:s,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.filter(s=>this.eligible(s));if(t.length===0)return null;let r=this.selectorState(e);switch(e.selector){case"branch":return t[0];case"sequence":{let s=e.options?.order??"sequential",i=e.options?.exhaust??"once";return s==="shuffle"?this.pickShuffle(t,i,r):s==="specificity"?this.pickSpecificity(t,i,r):this.pickSequential(t,i,r)}default:return null}}pickSequential(e,t,r){let s=e.length,i=r.seq??0;return r.seq=i+1,t==="repeat"?e[i%s]:i<s?e[i]:t==="stick"?e[s-1]:null}pickShuffle(e,t,r){let s=e.length,i=t==="stick",o=()=>(i?e.slice(0,s-1):e).map(g=>g.id);if(r.bag===void 0&&(r.bag=o()),r.bag.length===0){if(t==="once")return null;if(i){let g=e[s-1];return r.last=g.id,g}r.bag=o()}let a=r.bag,c=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,d=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&d>=c&&d++;let p=a[d];return a.splice(d,1),r.last=p,e.find(g=>g.id===p)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(p=>p.id));let d=new Set(r.bag);if(s=e.filter(p=>d.has(p.id)),s.length===0)return t==="stick"&&r.last!==void 0?e.find(p=>p.id===r.last)??null:null}let i=-1,a=s.map(d=>{let p=this.specScore(d);return p>i&&(i=p),{c:d,s:p}}).filter(d=>d.s===i).map(d=>d.c),c;if(a.length===1)c=a[0];else{let d=r.last!==void 0?a.findIndex(g=>g.id===r.last):-1,p=Math.floor(this.rng()*(d>=0?a.length-1:a.length));d>=0&&p>=d&&p++,c=a[p]}return t!=="repeat"&&(r.bag=r.bag.filter(d=>d!==c.id)),r.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0):0}matchedSpec(e,t){return Q(e,s=>de(I(s,this.evalCtx,$)),{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=t.get(e.id);return r||(r={},t.set(e.id,r)),r}runEffects(e){for(let t of e??[])this.setProperty(t.target,this.evalExpr(t.value))}eligible(e){return e.condition?de(this.evalExpr(e.condition)):!0}evalExpr(e){return I(this.conditionAst(e),this.evalCtx,$)}conditionAst(e){let t=ae.get(e);return t||(t=v(e.ast),ae.set(e,t)),t}enter(e){this.visitCounts.set(e,(this.visitCounts.get(e)??0)+1),this.host.sharedVisits.set(e,(this.host.sharedVisits.get(e)??0)+1)}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=M(this.rngState),t=e.next();return this.rngState=e.state(),t};beatResult(e){let t=this.host.tagIndex.get(e.id),r=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...r};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),gameData:e.gameData,...r};case"line":{let s=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?s:this.interpolate(s)),c=i&&a.length===0;return{type:"line",id:e.id,text:a,character:c?void 0:e.character,characterName:c?void 0:this.resolveCharacterName(e.character),direction:c?void 0:e.direction,gameData:e.gameData,...r}}}}interpolate(e){return ie(e,t=>this.getProperty(t))}stripCaptions(e){return se(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let r=this.interpolate(this.resolveString(t.id));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(r=>r.kind==="line"||r.kind==="text")}firstTextSnippetIn(e){let t;return x(e,r=>{!t&&r.type==="snippet"&&(r.beats??[]).some(s=>s.kind==="line"||s.kind==="text")&&(t=r)}),t}resolveString(e){if(this.host.emitIds)return e;let t=this.host.strings[e];if(t!==void 0)return t;let r=this.host.defaultStrings[e];return r!==void 0?`<Untranslated: ${e}> ${r}`:e}resolveCharacterName(e){if(e===void 0||this.host.emitIds)return;let t=_(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){let t=this.host.refSplitCache.get(e);return t||(t=q(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t}freshLocal(){return new E().defineOwned("patter",this.host.patterLocalDecls)}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;if(!this.sceneBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)||(r[i]=U(s))}this.sceneBags.set(e.id,r)}if(!this.host.stageBags.has(e.id)){let r={};for(let s of e.sceneProps??[]){let i=s.name.toLowerCase();t.has(i)&&(r[i]=U(s))}this.host.stageBags.set(e.id,r)}for(let r of e.sceneProps??[]){if(!r.temporary)continue;let s=r.name.toLowerCase(),i=t.has(s)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);i&&(i[s]=U(r))}}};function ce(n,e){x(n,t=>{if(t.type==="group"){t.prompt?.kind==="line"&&t.prompt.character&&e.add(t.prompt.character);return}for(let r of t.beats??[])r.kind==="line"&&r.character&&e.add(r.character)})}function ue(n){let e={};for(let[t,r]of n){let s={};r.seq!==void 0&&(s.seq=r.seq),r.bag&&(s.bag=[...r.bag]),r.last!==void 0&&(s.last=r.last),e[t]=s}return e}function ge(n){let e=new Map;for(let[t,r]of Object.entries(n??{})){let s={};r.seq!==void 0&&(s.seq=r.seq),r.bag&&(s.bag=[...r.bag]),r.last!==void 0&&(s.last=r.last),e.set(t,s)}return e}function le(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Fe(n){if(n.default!==void 0)return n.default;switch(n.type){case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??"";default:return!1}}function pe(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function Te(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??""}}function Ge(n){let e=r=>r.toLowerCase(),t=new Map;for(let r of n)t.set(e(r.name),Te(r));return{get:r=>t.get(e(r)),set:(r,s)=>{t.set(e(r),s)}}}function U(n){if(n.default!==void 0)return n.default;switch(n.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return n.values?.[0]??"";case"quality":return n.stages?.[0]??""}}function de(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Ve=(n,e)=>n.shared??e;function W(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:Ve(n,e)}}function Me(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function je(n,e){e.blocks++;let t=[...n.children];for(;t.length;){let r=t.pop();if(r.type==="group"){e.groups++,r.prompt&&e.prompts++,t.push(...r.children);continue}e.snippets++;for(let s of r.beats??[])e.beats++,s.kind==="gameEvent"&&e.gameEvents++}}function fe(n){let e={scenes:0,blocks:0,groups:0,snippets:0,beats:0,prompts:0,gameEvents:0,cast:n.cast?.length??0},t=[],r=[];for(let o of Object.values(n.scenes)){e.scenes++;let a=f(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:f(c),name:c.name}))});for(let c of o.blocks)je(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>W(c,!1))})}let s=(n.scopeRegistry?.scopes??[]).map(o=>({token:o.token,writable:o.writable??!0,opaque:o.declarations===void 0,properties:(o.declarations??[]).map(a=>W(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Me)}));return{identity:{schema:n.schema,project:n.content.project,...n.content.version!==void 0?{version:n.content.version}:{},...n.content.hash!==void 0?{hash:n.content.hash}:{},...n.content.structureHash!==void 0?{structureHash:n.content.structureHash}:{},voiced:n.voiced,defaultLocale:n.locales.default,locales:[...n.locales.included],localisation:n.localisation?.mode??"embedded",sourceDebug:n.localisation?.sourceDebug??!1},addresses:t,hostScopes:s,properties:{patter:(n.properties??[]).map(o=>W(o,!0)),scene:r},gameData:i,counts:e}}function he(n,e){return n.gameDataFields?.[e]??[]}function K(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function me(n,e){let t={};for(let r of n){let s=K(n,e,r.name);s!==void 0&&(t[r.name]=s)}for(let[r,s]of Object.entries(e??{}))r in t||(t[r]=s);return t}return Ce(Le);})();\n//# sourceMappingURL=patterplay.min.js.map';
|
|
733
985
|
|
|
734
986
|
// src/export-html.ts
|
|
735
987
|
var esc = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
@@ -953,7 +1205,7 @@ function scanAudioStatus(loaded) {
|
|
|
953
1205
|
}
|
|
954
1206
|
|
|
955
1207
|
// src/play.ts
|
|
956
|
-
import { exportBundle as
|
|
1208
|
+
import { exportBundle as exportBundle4 } from "@patterkit/compiler";
|
|
957
1209
|
import { Engine } from "@patterkit/runtime";
|
|
958
1210
|
|
|
959
1211
|
// src/loaded-helpers.ts
|
|
@@ -1007,7 +1259,7 @@ function mergeAuthoring2(loaded) {
|
|
|
1007
1259
|
|
|
1008
1260
|
// src/play.ts
|
|
1009
1261
|
function runPlay(loaded, opts = {}) {
|
|
1010
|
-
const bundle =
|
|
1262
|
+
const bundle = exportBundle4({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales });
|
|
1011
1263
|
const events = [];
|
|
1012
1264
|
const engine = new Engine(bundle, { seed: opts.seed });
|
|
1013
1265
|
const start = resolveStart(loaded, opts);
|
|
@@ -1062,48 +1314,17 @@ function renderPlay(result) {
|
|
|
1062
1314
|
}
|
|
1063
1315
|
|
|
1064
1316
|
// src/coverage.ts
|
|
1065
|
-
import { exportBundle as
|
|
1317
|
+
import { exportBundle as exportBundle5 } from "@patterkit/compiler";
|
|
1066
1318
|
import { Engine as Engine2 } from "@patterkit/runtime";
|
|
1067
1319
|
import { walkNodes } from "@patterkit/model";
|
|
1068
|
-
|
|
1069
|
-
// ../../../expr/packages/expr/src/ast.ts
|
|
1070
|
-
function deserialiseAst(node) {
|
|
1071
|
-
switch (node[0]) {
|
|
1072
|
-
case "b":
|
|
1073
|
-
return { kind: "bool", value: node[1] };
|
|
1074
|
-
case "n":
|
|
1075
|
-
return { kind: "number", value: node[1] };
|
|
1076
|
-
case "s":
|
|
1077
|
-
return { kind: "string", value: node[1] };
|
|
1078
|
-
case "sv":
|
|
1079
|
-
return { kind: "scopedvar", scope: node[1], name: node[2] };
|
|
1080
|
-
case "u":
|
|
1081
|
-
return { kind: "unary", op: node[1], operand: deserialiseAst(node[2]) };
|
|
1082
|
-
case "bin":
|
|
1083
|
-
return { kind: "binary", op: node[1], left: deserialiseAst(node[2]), right: deserialiseAst(node[3]) };
|
|
1084
|
-
case "call": {
|
|
1085
|
-
const args = node.slice(2).map(deserialiseAst);
|
|
1086
|
-
return { kind: "call", name: node[1], args };
|
|
1087
|
-
}
|
|
1088
|
-
case "fd":
|
|
1089
|
-
return { kind: "flagdelta", sign: node[1], name: node[2] };
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
// src/coverage.ts
|
|
1094
1320
|
var CADENCE_PROB = {
|
|
1095
1321
|
rarely: 0.15,
|
|
1096
1322
|
sometimes: 0.4,
|
|
1097
1323
|
often: 0.8
|
|
1098
1324
|
};
|
|
1099
1325
|
function mulberry32(seed) {
|
|
1100
|
-
|
|
1101
|
-
return () =>
|
|
1102
|
-
a = a + 1831565813 | 0;
|
|
1103
|
-
let t = Math.imul(a ^ a >>> 15, 1 | a);
|
|
1104
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
1105
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
1106
|
-
};
|
|
1326
|
+
const prng = makePrng(seed);
|
|
1327
|
+
return () => prng.next();
|
|
1107
1328
|
}
|
|
1108
1329
|
function scanExpr(node, hostTokens, refs, proposals) {
|
|
1109
1330
|
const refOf = (n) => n.kind === "scopedvar" && hostTokens.has(n.scope) ? `@${n.scope}.${n.name}` : void 0;
|
|
@@ -1142,45 +1363,118 @@ function targetHostRef(target, hostTokens) {
|
|
|
1142
1363
|
const m = /^@([A-Za-z_][\w]*)\.(.+)$/.exec(target);
|
|
1143
1364
|
return m && hostTokens.has(m[1]) ? `@${m[1]}.${m[2]}` : void 0;
|
|
1144
1365
|
}
|
|
1366
|
+
function flagKeys(node, hostTokens, fn) {
|
|
1367
|
+
if (node.kind !== "call" || node.name !== fn) return [];
|
|
1368
|
+
const subject = node.args[0];
|
|
1369
|
+
if (!subject || subject.kind !== "scopedvar" || !hostTokens.has(subject.scope)) return [];
|
|
1370
|
+
const ref = `@${subject.scope}.${subject.name}`;
|
|
1371
|
+
return node.args.slice(1).filter((a) => a.kind === "flagdelta").map((a) => `${ref}:${a.name}`);
|
|
1372
|
+
}
|
|
1373
|
+
function fineRefsIn(node, hostTokens, out) {
|
|
1374
|
+
for (const k of flagKeys(node, hostTokens, "check_flags")) out.add(k);
|
|
1375
|
+
switch (node.kind) {
|
|
1376
|
+
case "unary":
|
|
1377
|
+
fineRefsIn(node.operand, hostTokens, out);
|
|
1378
|
+
break;
|
|
1379
|
+
case "binary":
|
|
1380
|
+
fineRefsIn(node.left, hostTokens, out);
|
|
1381
|
+
fineRefsIn(node.right, hostTokens, out);
|
|
1382
|
+
break;
|
|
1383
|
+
case "call":
|
|
1384
|
+
for (const a of node.args) fineRefsIn(a, hostTokens, out);
|
|
1385
|
+
break;
|
|
1386
|
+
default:
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1145
1390
|
function analyzeHostScopes(bundle, hostTokens) {
|
|
1146
1391
|
const written = /* @__PURE__ */ new Set();
|
|
1147
1392
|
const gatesByBeat = /* @__PURE__ */ new Map();
|
|
1393
|
+
const fineGatesByBeat = /* @__PURE__ */ new Map();
|
|
1148
1394
|
const proposals = /* @__PURE__ */ new Map();
|
|
1149
|
-
|
|
1395
|
+
const writerSites = /* @__PURE__ */ new Map();
|
|
1396
|
+
const opaqueWrites = /* @__PURE__ */ new Set();
|
|
1397
|
+
const empty = { written, gatesByBeat, proposals, fineGatesByBeat, writerSites, opaqueWrites };
|
|
1398
|
+
if (hostTokens.size === 0) return empty;
|
|
1150
1399
|
const refsIn = (expr) => {
|
|
1151
1400
|
const refs = /* @__PURE__ */ new Set();
|
|
1152
1401
|
if (expr) scanExpr(deserialiseAst(expr.ast), hostTokens, refs, proposals);
|
|
1153
1402
|
return refs;
|
|
1154
1403
|
};
|
|
1155
|
-
const
|
|
1404
|
+
const fineIn = (expr) => {
|
|
1405
|
+
const refs = /* @__PURE__ */ new Set();
|
|
1406
|
+
if (expr) fineRefsIn(deserialiseAst(expr.ast), hostTokens, refs);
|
|
1407
|
+
return refs;
|
|
1408
|
+
};
|
|
1409
|
+
const addSite = (key, witnesses) => {
|
|
1410
|
+
(writerSites.get(key) ?? writerSites.set(key, []).get(key)).push(witnesses);
|
|
1411
|
+
};
|
|
1412
|
+
const scanEffects = (effects, witnesses) => {
|
|
1156
1413
|
for (const e of effects ?? []) {
|
|
1157
|
-
const
|
|
1158
|
-
if (
|
|
1414
|
+
const target = targetHostRef(e.target, hostTokens);
|
|
1415
|
+
if (target) {
|
|
1416
|
+
written.add(target);
|
|
1417
|
+
addSite(target, witnesses);
|
|
1418
|
+
const flags = e.value ? flagKeys(deserialiseAst(e.value.ast), hostTokens, "set_flags") : [];
|
|
1419
|
+
if (flags.length) for (const k of flags) addSite(k, witnesses);
|
|
1420
|
+
else opaqueWrites.add(target);
|
|
1421
|
+
}
|
|
1159
1422
|
refsIn(e.value);
|
|
1160
1423
|
}
|
|
1161
1424
|
};
|
|
1162
|
-
const walk = (nodes, gate) => {
|
|
1425
|
+
const walk = (nodes, gate, fine) => {
|
|
1163
1426
|
for (const node of nodes) {
|
|
1164
1427
|
const here = /* @__PURE__ */ new Set([...gate, ...refsIn(node.condition)]);
|
|
1428
|
+
const hereFine = /* @__PURE__ */ new Set([...fine, ...fineIn(node.condition)]);
|
|
1165
1429
|
if (node.type === "group") {
|
|
1166
|
-
walk(node.children, here);
|
|
1430
|
+
walk(node.children, here, hereFine);
|
|
1167
1431
|
} else {
|
|
1168
|
-
|
|
1169
|
-
scanEffects(node.
|
|
1170
|
-
|
|
1432
|
+
const witnesses = (node.beats ?? []).map((b) => b.id);
|
|
1433
|
+
scanEffects(node.onEnter, witnesses);
|
|
1434
|
+
scanEffects(node.onExit, witnesses);
|
|
1435
|
+
for (const beat of node.beats ?? []) {
|
|
1436
|
+
gatesByBeat.set(beat.id, here);
|
|
1437
|
+
fineGatesByBeat.set(beat.id, hereFine);
|
|
1438
|
+
}
|
|
1171
1439
|
}
|
|
1172
1440
|
}
|
|
1173
1441
|
};
|
|
1174
1442
|
for (const scene of Object.values(bundle.scenes)) {
|
|
1175
|
-
|
|
1176
|
-
|
|
1443
|
+
const sceneBeats = [];
|
|
1444
|
+
const collect = (nodes) => {
|
|
1445
|
+
for (const n of nodes) {
|
|
1446
|
+
if (n.type === "group") collect(n.children);
|
|
1447
|
+
else for (const b of n.beats ?? []) sceneBeats.push(b.id);
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
for (const block of scene.blocks) collect(block.children);
|
|
1451
|
+
scanEffects(scene.onEntry, sceneBeats);
|
|
1452
|
+
for (const block of scene.blocks) walk(block.children, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Set());
|
|
1453
|
+
}
|
|
1454
|
+
return empty;
|
|
1455
|
+
}
|
|
1456
|
+
function blockedGates(beatId, analysis, drivenRefs, reachedRuns) {
|
|
1457
|
+
const out = [];
|
|
1458
|
+
const gates = /* @__PURE__ */ new Set([...analysis.gatesByBeat.get(beatId) ?? [], ...analysis.fineGatesByBeat.get(beatId) ?? []]);
|
|
1459
|
+
for (const ref of [...gates].sort()) {
|
|
1460
|
+
const coarse = ref.includes(":") ? ref.slice(0, ref.indexOf(":")) : ref;
|
|
1461
|
+
if (drivenRefs.has(coarse)) continue;
|
|
1462
|
+
if (!analysis.written.has(coarse)) continue;
|
|
1463
|
+
if (ref !== coarse && analysis.opaqueWrites.has(coarse)) continue;
|
|
1464
|
+
if (ref === coarse && [...gates].some((g) => g !== ref && g.startsWith(`${ref}:`))) continue;
|
|
1465
|
+
const sites = analysis.writerSites.get(ref) ?? [];
|
|
1466
|
+
if (!sites.length) continue;
|
|
1467
|
+
if (sites.some((s) => !s.length || s.some((w) => !reachedRuns.has(w)))) continue;
|
|
1468
|
+
if (!sites.every((s) => s.every((w) => reachedRuns.get(w) === 0))) continue;
|
|
1469
|
+
const writers = [...new Set(sites.flat())].sort();
|
|
1470
|
+
out.push({ ref, writers });
|
|
1177
1471
|
}
|
|
1178
|
-
return
|
|
1472
|
+
return out;
|
|
1179
1473
|
}
|
|
1180
1474
|
function proposeCoverageDrivers(loaded) {
|
|
1181
1475
|
const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
|
|
1182
1476
|
if (hostTokens.size === 0) return [];
|
|
1183
|
-
const bundle =
|
|
1477
|
+
const bundle = exportBundle5({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales });
|
|
1184
1478
|
const { written, proposals } = analyzeHostScopes(bundle, hostTokens);
|
|
1185
1479
|
const declByRef = /* @__PURE__ */ new Map();
|
|
1186
1480
|
for (const s of loaded.project.scopeRegistry?.scopes ?? []) {
|
|
@@ -1241,7 +1535,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
1241
1535
|
const reachedRuns = new Map(order.map((id) => [id, 0]));
|
|
1242
1536
|
const dryRuns = /* @__PURE__ */ new Map();
|
|
1243
1537
|
const termination = { ended: 0, capped: 0, stalled: 0, evalError: 0 };
|
|
1244
|
-
const bundle =
|
|
1538
|
+
const bundle = exportBundle5({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales });
|
|
1245
1539
|
const rng = mulberry32(seed);
|
|
1246
1540
|
const hostTokens = new Set((loaded.project.scopeRegistry?.scopes ?? []).map((s) => s.token));
|
|
1247
1541
|
const analysis = analyzeHostScopes(bundle, hostTokens);
|
|
@@ -1308,12 +1602,15 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
1308
1602
|
const m = meta.get(id);
|
|
1309
1603
|
const reached = reachedRuns.get(id);
|
|
1310
1604
|
let needsInput;
|
|
1605
|
+
let blockedBy;
|
|
1311
1606
|
if (reached === 0) {
|
|
1312
1607
|
const gates = [...analysis.gatesByBeat.get(id) ?? []].filter((r) => !analysis.written.has(r) && !drivenRefs.has(r));
|
|
1313
1608
|
if (gates.length) {
|
|
1314
1609
|
needsInput = gates;
|
|
1315
1610
|
for (const g of gates) unwrittenInputs.add(g);
|
|
1316
1611
|
}
|
|
1612
|
+
const blocked = blockedGates(id, analysis, drivenRefs, reachedRuns);
|
|
1613
|
+
if (blocked.length) blockedBy = blocked;
|
|
1317
1614
|
}
|
|
1318
1615
|
return {
|
|
1319
1616
|
id,
|
|
@@ -1324,7 +1621,8 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
1324
1621
|
hits: hitCount.get(id),
|
|
1325
1622
|
reachedRuns: reached,
|
|
1326
1623
|
reachPct: executed ? reached / executed * 100 : 0,
|
|
1327
|
-
...needsInput ? { needsInput } : {}
|
|
1624
|
+
...needsInput ? { needsInput } : {},
|
|
1625
|
+
...blockedBy ? { blockedBy } : {}
|
|
1328
1626
|
};
|
|
1329
1627
|
});
|
|
1330
1628
|
const neverHit = beats.filter((b) => b.reachedRuns === 0).length;
|
|
@@ -1387,9 +1685,16 @@ function renderCoverageText(report, sceneName = (id) => id) {
|
|
|
1387
1685
|
out.push("");
|
|
1388
1686
|
out.push(`${sceneName(scene)}${dead ? ` (${dead} never reached)` : ""}`);
|
|
1389
1687
|
for (const b of beats) {
|
|
1390
|
-
const mark = b.reachedRuns === 0 ? b.needsInput ? "? " : "\u203C " : " ";
|
|
1688
|
+
const mark = b.reachedRuns === 0 ? b.needsInput || b.blockedBy ? "? " : "\u203C " : " ";
|
|
1391
1689
|
const label = b.character ? `${b.character}: ${clip(b.preview)}` : clip(b.preview || `(${b.kind})`);
|
|
1392
1690
|
out.push(` ${mark}${b.reachPct.toFixed(0).padStart(3)}% ${String(b.hits).padStart(6)} ${label}`);
|
|
1691
|
+
for (const bg of b.blockedBy ?? []) {
|
|
1692
|
+
const names = bg.writers.map((w) => {
|
|
1693
|
+
const target = report.beats.find((x) => x.id === w);
|
|
1694
|
+
return target ? clip(target.preview || target.id, 28) : w;
|
|
1695
|
+
});
|
|
1696
|
+
out.push(` gated on ${bg.ref}, written only by: ${names.join(", ")} (never played either)`);
|
|
1697
|
+
}
|
|
1393
1698
|
}
|
|
1394
1699
|
}
|
|
1395
1700
|
return out;
|
|
@@ -3259,15 +3564,10 @@ function buildIndex(loaded) {
|
|
|
3259
3564
|
// src/replace.ts
|
|
3260
3565
|
import { canonicalStringify as canonicalStringify6 } from "@patterkit/core";
|
|
3261
3566
|
import { walkNodes as walkNodes5, PROJECT_LOCALE_SCENE as PROJECT_LOCALE_SCENE2 } from "@patterkit/model";
|
|
3262
|
-
|
|
3263
|
-
if (!opts.query) return null;
|
|
3264
|
-
const esc2 = opts.query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3265
|
-
const body = opts.wholeWord ? `\\b${esc2}\\b` : esc2;
|
|
3266
|
-
return new RegExp(body, opts.caseSensitive ? "g" : "gi");
|
|
3267
|
-
}
|
|
3567
|
+
import { findMatcher } from "@wildwinter/toolkit";
|
|
3268
3568
|
function runReplace(loaded, opts) {
|
|
3269
3569
|
const plan = { hits: [], writes: [], shards: [], scenes: 0 };
|
|
3270
|
-
const re =
|
|
3570
|
+
const re = findMatcher(opts);
|
|
3271
3571
|
if (!re) return plan;
|
|
3272
3572
|
const def = loaded.project.locales.default;
|
|
3273
3573
|
const locOf = /* @__PURE__ */ new Map();
|
|
@@ -3317,9 +3617,9 @@ import JSZip from "jszip";
|
|
|
3317
3617
|
import { readFileSync as readFileSync4 } from "fs";
|
|
3318
3618
|
import { dirname as dirname4, relative, sep as sep2 } from "path";
|
|
3319
3619
|
import { parseSource as parseSource4 } from "@patterkit/core";
|
|
3620
|
+
import { ARCHIVE_ENTRY_OPTS } from "@wildwinter/toolkit/archive";
|
|
3320
3621
|
var SHARD_EXTENSIONS = [".patterflow", ".patterloc", ".patterx", ".patterproj"];
|
|
3321
|
-
var
|
|
3322
|
-
var ENTRY_OPTS = { date: FIXED_DATE, createFolders: false };
|
|
3622
|
+
var ENTRY_OPTS = ARCHIVE_ENTRY_OPTS;
|
|
3323
3623
|
async function runPack(startPath) {
|
|
3324
3624
|
const projectFile = findProjectFile(startPath);
|
|
3325
3625
|
const root = dirname4(projectFile);
|
|
@@ -3345,9 +3645,11 @@ async function runPack(startPath) {
|
|
|
3345
3645
|
|
|
3346
3646
|
// src/unpack.ts
|
|
3347
3647
|
import JSZip2 from "jszip";
|
|
3348
|
-
import { join as join5,
|
|
3648
|
+
import { join as join5, resolve as resolve4, sep as sep3 } from "path";
|
|
3349
3649
|
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
3350
3650
|
import { parseSource as parseSource5, canonicalStringify as canonicalStringify7 } from "@patterkit/core";
|
|
3651
|
+
import { isUnsafeEntry } from "@wildwinter/toolkit/archive";
|
|
3652
|
+
import { isUnsafeEntry as isUnsafeEntry2 } from "@wildwinter/toolkit/archive";
|
|
3351
3653
|
var MANIFEST = "patter.manifest.json";
|
|
3352
3654
|
var UnsafeEntryError = class extends Error {
|
|
3353
3655
|
};
|
|
@@ -3432,11 +3734,6 @@ async function runUnpackMerge(returnedBytes, baseBytes, projectDir) {
|
|
|
3432
3734
|
}
|
|
3433
3735
|
return { shards, writes, sidecars, conflicts, warnings, provenance };
|
|
3434
3736
|
}
|
|
3435
|
-
function isUnsafeEntry(name) {
|
|
3436
|
-
if (isAbsolute2(name) || /^[a-zA-Z]:/.test(name)) return true;
|
|
3437
|
-
const norm = normalize(name);
|
|
3438
|
-
return norm === ".." || norm.startsWith(".." + sep3) || norm.startsWith("../");
|
|
3439
|
-
}
|
|
3440
3737
|
function containedWrite(dir, name) {
|
|
3441
3738
|
const root = resolve4(dir);
|
|
3442
3739
|
const full = resolve4(join5(dir, name));
|
|
@@ -3462,13 +3759,14 @@ export {
|
|
|
3462
3759
|
detectMergeType,
|
|
3463
3760
|
extractLoc,
|
|
3464
3761
|
findProjectFile,
|
|
3465
|
-
isUnsafeEntry,
|
|
3762
|
+
isUnsafeEntry2 as isUnsafeEntry,
|
|
3466
3763
|
jsonToCatalog,
|
|
3467
3764
|
listProjectTags,
|
|
3468
3765
|
loadProject,
|
|
3469
3766
|
loadProjectLanding,
|
|
3470
3767
|
poToCatalog,
|
|
3471
3768
|
proposeCoverageDrivers,
|
|
3769
|
+
reachabilityIssues,
|
|
3472
3770
|
renderCoverageText,
|
|
3473
3771
|
renderPlay,
|
|
3474
3772
|
renderReportText,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/ops",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|
|
@@ -27,10 +27,11 @@
|
|
|
27
27
|
"build": "tsup src/index.ts --format esm --dts --clean"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@patterkit/compiler": "0.2.
|
|
31
|
-
"@patterkit/core": "0.2.
|
|
32
|
-
"@patterkit/model": "0.4.
|
|
33
|
-
"@patterkit/runtime": "0.
|
|
30
|
+
"@patterkit/compiler": "0.2.5",
|
|
31
|
+
"@patterkit/core": "0.2.2",
|
|
32
|
+
"@patterkit/model": "0.4.1",
|
|
33
|
+
"@patterkit/runtime": "0.8.0",
|
|
34
|
+
"@wildwinter/toolkit": "^0.1.0",
|
|
34
35
|
"docx": "^9.7.1",
|
|
35
36
|
"exceljs": "^4.4.0",
|
|
36
37
|
"jszip": "^3.10.1",
|