@patterkit/cli 0.7.4 → 0.8.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/cli.js +219 -101
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -196888,6 +196888,11 @@ function hash32(input) {
|
|
|
196888
196888
|
return fnv32(input).toString(36).padStart(7, "0");
|
|
196889
196889
|
}
|
|
196890
196890
|
|
|
196891
|
+
// ../../../expr/packages/expr/src/own.ts
|
|
196892
|
+
function own(record, key2) {
|
|
196893
|
+
return record !== void 0 && Object.prototype.hasOwnProperty.call(record, key2) ? record[key2] : void 0;
|
|
196894
|
+
}
|
|
196895
|
+
|
|
196891
196896
|
// ../../../expr/packages/expr/src/parser.ts
|
|
196892
196897
|
var ParseError = class extends Error {
|
|
196893
196898
|
constructor(message, pos2, source2) {
|
|
@@ -197050,7 +197055,7 @@ function tokenise(src) {
|
|
|
197050
197055
|
if (/[a-zA-Z_]/.test(ch)) {
|
|
197051
197056
|
const m = src.slice(i).match(/^[a-zA-Z_][a-zA-Z0-9_]*/);
|
|
197052
197057
|
const word = m[0];
|
|
197053
|
-
const kw = KEYWORDS
|
|
197058
|
+
const kw = own(KEYWORDS, word);
|
|
197054
197059
|
tokens.push({ kind: kw ?? "IDENT", value: word, pos: pos2 });
|
|
197055
197060
|
i += word.length;
|
|
197056
197061
|
continue;
|
|
@@ -197334,11 +197339,11 @@ function evaluate(node, ctx, dialect) {
|
|
|
197334
197339
|
case "string":
|
|
197335
197340
|
return n.value;
|
|
197336
197341
|
case "scopedvar": {
|
|
197337
|
-
const scope = ctx.scopes
|
|
197342
|
+
const scope = own(ctx.scopes, n.scope);
|
|
197338
197343
|
if (scope === void 0) {
|
|
197339
197344
|
return false;
|
|
197340
197345
|
}
|
|
197341
|
-
const val = typeof scope.get === "function" ? scope.get(n.name) : scope
|
|
197346
|
+
const val = typeof scope.get === "function" ? scope.get(n.name) : own(scope, n.name);
|
|
197342
197347
|
if (val === void 0) {
|
|
197343
197348
|
if (missingPolicy.get(n.scope) === "throw") {
|
|
197344
197349
|
throw new EvalError2(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);
|
|
@@ -197348,7 +197353,7 @@ function evaluate(node, ctx, dialect) {
|
|
|
197348
197353
|
return val;
|
|
197349
197354
|
}
|
|
197350
197355
|
case "call": {
|
|
197351
|
-
if (n.name === "advance" && !dialect.functions
|
|
197356
|
+
if (n.name === "advance" && !own(dialect.functions, n.name)) {
|
|
197352
197357
|
const arg = n.args[0];
|
|
197353
197358
|
if (n.args.length !== 1 || arg === void 0) {
|
|
197354
197359
|
throw new EvalError2(`advance() takes exactly 1 argument, got ${n.args.length}`);
|
|
@@ -197360,7 +197365,7 @@ function evaluate(node, ctx, dialect) {
|
|
|
197360
197365
|
const current = stageIndex(rec(arg), ladder, "advance");
|
|
197361
197366
|
return ladder[Math.min(current + 1, ladder.length - 1)];
|
|
197362
197367
|
}
|
|
197363
|
-
const def = dialect.functions
|
|
197368
|
+
const def = own(dialect.functions, n.name);
|
|
197364
197369
|
if (!def) throw new EvalError2(`unknown function '${n.name}'`);
|
|
197365
197370
|
return def.eval(n.args, { evaluate: rec, ctx });
|
|
197366
197371
|
}
|
|
@@ -197611,7 +197616,7 @@ function walkValidate(node, schema, dialect, path, issues) {
|
|
|
197611
197616
|
return;
|
|
197612
197617
|
}
|
|
197613
197618
|
case "call": {
|
|
197614
|
-
if (node.name === "advance" && !dialect.functions
|
|
197619
|
+
if (node.name === "advance" && !own(dialect.functions, node.name)) {
|
|
197615
197620
|
if (node.args.length !== 1) {
|
|
197616
197621
|
issues.push({ path, kind: "wrong-arg-count", severity: "error", message: `advance() takes exactly 1 argument, got ${node.args.length}` });
|
|
197617
197622
|
}
|
|
@@ -197629,7 +197634,7 @@ function walkValidate(node, schema, dialect, path, issues) {
|
|
|
197629
197634
|
}
|
|
197630
197635
|
return;
|
|
197631
197636
|
}
|
|
197632
|
-
const def = dialect.functions
|
|
197637
|
+
const def = own(dialect.functions, node.name);
|
|
197633
197638
|
if (!def) {
|
|
197634
197639
|
issues.push({ path, kind: "unknown-function", severity: "error", message: `unknown function '${node.name}'` });
|
|
197635
197640
|
return;
|
|
@@ -197743,7 +197748,7 @@ function typeOf(node, schema, dialect) {
|
|
|
197743
197748
|
}
|
|
197744
197749
|
case "call": {
|
|
197745
197750
|
if (node.name === "advance") return "quality";
|
|
197746
|
-
const def = dialect.functions
|
|
197751
|
+
const def = own(dialect.functions, node.name);
|
|
197747
197752
|
return def ? inferredFromReturn(def.returnType) : "unknown";
|
|
197748
197753
|
}
|
|
197749
197754
|
case "unary":
|
|
@@ -198354,8 +198359,12 @@ import { dirname, join, relative, resolve } from "path";
|
|
|
198354
198359
|
var PropertyBag = class _PropertyBag {
|
|
198355
198360
|
/** The live values record (stable identity across reseed, so an
|
|
198356
198361
|
* EvalContext built over it stays valid). Read-path for evaluation;
|
|
198357
|
-
* writes go through `set` so the firing rule applies.
|
|
198358
|
-
|
|
198362
|
+
* writes go through `set` so the firing rule applies.
|
|
198363
|
+
*
|
|
198364
|
+
* It has no prototype, so a scope answers only its own properties: on a plain
|
|
198365
|
+
* object, `@world.constructor` read the built-in `Object` function rather than
|
|
198366
|
+
* "missing", and a write to `__proto__` vanished (October 2026 review). */
|
|
198367
|
+
values = /* @__PURE__ */ Object.create(null);
|
|
198359
198368
|
decls = /* @__PURE__ */ new Map();
|
|
198360
198369
|
subscribers = /* @__PURE__ */ new Set();
|
|
198361
198370
|
auditors = /* @__PURE__ */ new Set();
|
|
@@ -198388,6 +198397,13 @@ var PropertyBag = class _PropertyBag {
|
|
|
198388
198397
|
normalise(name) {
|
|
198389
198398
|
return this.norm(name);
|
|
198390
198399
|
}
|
|
198400
|
+
/** Whether a STORY write to this property is allowed: false only for a
|
|
198401
|
+
* declaration marked `writable: false`. A host write is always allowed. The
|
|
198402
|
+
* registry asks before writing, so it never has to read a refusal out of an
|
|
198403
|
+
* error that a listener might have raised instead. */
|
|
198404
|
+
writable(name) {
|
|
198405
|
+
return this.decls.get(this.norm(name))?.writable !== false;
|
|
198406
|
+
}
|
|
198391
198407
|
/** Write a property. Engine writes (the default) notify subscribers;
|
|
198392
198408
|
* pass `silent: true` for a host write, which reaches only the audit
|
|
198393
198409
|
* hook. Throws on a read-only property unless the caller says it is the
|
|
@@ -198397,7 +198413,7 @@ var PropertyBag = class _PropertyBag {
|
|
|
198397
198413
|
* it. Returns the change. */
|
|
198398
198414
|
set(name, value, opts) {
|
|
198399
198415
|
const n = this.norm(name);
|
|
198400
|
-
if (!opts?.host && this.
|
|
198416
|
+
if (!opts?.host && !this.writable(n)) throw new Error(`'${name}' is read-only`);
|
|
198401
198417
|
const change = {
|
|
198402
198418
|
name: n,
|
|
198403
198419
|
prev: this.values[n],
|
|
@@ -198406,8 +198422,8 @@ var PropertyBag = class _PropertyBag {
|
|
|
198406
198422
|
reason: opts?.reason
|
|
198407
198423
|
};
|
|
198408
198424
|
this.values[n] = value;
|
|
198409
|
-
for (const audit of this.auditors) audit(change);
|
|
198410
|
-
if (!change.silent) for (const fn of this.subscribers) fn(change);
|
|
198425
|
+
for (const audit of [...this.auditors]) audit(change);
|
|
198426
|
+
if (!change.silent) for (const fn of [...this.subscribers]) fn(change);
|
|
198411
198427
|
return change;
|
|
198412
198428
|
}
|
|
198413
198429
|
/** Notified of engine (non-silent) writes. Returns the unsubscribe. */
|
|
@@ -198611,11 +198627,8 @@ var ScopeRegistry = class {
|
|
|
198611
198627
|
const e = this.scopes.get(scope);
|
|
198612
198628
|
if (!e) throw new Error(`unknown scope '@${scope}'`);
|
|
198613
198629
|
if (e.kind === "owned") {
|
|
198614
|
-
|
|
198615
|
-
|
|
198616
|
-
} catch {
|
|
198617
|
-
throw new Error(`'@${scope}.${name}' is read-only`);
|
|
198618
|
-
}
|
|
198630
|
+
if (!opts?.host && !e.bag.writable(name)) throw new Error(`'@${scope}.${name}' is read-only`);
|
|
198631
|
+
e.bag.set(name, value, opts?.host ? { host: true } : void 0);
|
|
198619
198632
|
return;
|
|
198620
198633
|
}
|
|
198621
198634
|
const n = e.norm(name);
|
|
@@ -199319,14 +199332,14 @@ function sameScope(a, b) {
|
|
|
199319
199332
|
return norm(a) === norm(b);
|
|
199320
199333
|
}
|
|
199321
199334
|
function projectScopes(project, merged) {
|
|
199322
|
-
const
|
|
199323
|
-
if (!merged) return { host:
|
|
199335
|
+
const own2 = project.scopeRegistry;
|
|
199336
|
+
if (!merged) return { host: own2, external: [], notes: [] };
|
|
199324
199337
|
const notes = [];
|
|
199325
199338
|
const hosted = [];
|
|
199326
199339
|
const bundled = /* @__PURE__ */ new Set();
|
|
199327
199340
|
let changed = false;
|
|
199328
199341
|
const shared = (token2) => token2 === PATTER_SCOPE ? void 0 : merged.spec.scopes.find((s) => s.token === token2);
|
|
199329
|
-
for (const h of
|
|
199342
|
+
for (const h of own2?.scopes ?? []) {
|
|
199330
199343
|
const s = shared(h.token);
|
|
199331
199344
|
const who2 = merged.owners.get(h.token);
|
|
199332
199345
|
if (!s || !who2) {
|
|
@@ -199362,7 +199375,7 @@ function projectScopes(project, merged) {
|
|
|
199362
199375
|
changed = true;
|
|
199363
199376
|
}
|
|
199364
199377
|
const external = merged.spec.scopes.filter((s) => s.token !== PATTER_SCOPE && !bundled.has(s.token));
|
|
199365
|
-
const host2 = !changed ?
|
|
199378
|
+
const host2 = !changed ? own2 : hosted.length ? { version: own2?.version ?? 1, scopes: hosted } : void 0;
|
|
199366
199379
|
return { host: host2, external, notes };
|
|
199367
199380
|
}
|
|
199368
199381
|
function externalGameScopes(strict, merged) {
|
|
@@ -199869,10 +199882,10 @@ function ownedDecls(s) {
|
|
|
199869
199882
|
});
|
|
199870
199883
|
}
|
|
199871
199884
|
function drivableScopes(loaded, bundle) {
|
|
199872
|
-
const
|
|
199873
|
-
if (!loaded.gameScopes || !bundle.externalScopes?.length) return
|
|
199874
|
-
const have = new Set(
|
|
199875
|
-
return [...
|
|
199885
|
+
const own2 = bundle.scopeRegistry?.scopes ?? [];
|
|
199886
|
+
if (!loaded.gameScopes || !bundle.externalScopes?.length) return own2;
|
|
199887
|
+
const have = new Set(own2.map((s) => s.token));
|
|
199888
|
+
return [...own2, ...loaded.gameScopes.merged.spec.scopes.filter((s) => s.token !== PATTER_SCOPE && !have.has(s.token))];
|
|
199876
199889
|
}
|
|
199877
199890
|
var hostScopeTokens = (loaded, bundle) => new Set(drivableScopes(loaded, bundle).map((s) => s.token));
|
|
199878
199891
|
var asHostScope2 = (s) => ({
|
|
@@ -200592,25 +200605,25 @@ function reachabilityIssues(loaded, compiled) {
|
|
|
200592
200605
|
const issues = [];
|
|
200593
200606
|
const report = (nodes, sceneId, gate) => {
|
|
200594
200607
|
for (const node of nodes) {
|
|
200595
|
-
const
|
|
200596
|
-
const branches =
|
|
200608
|
+
const own2 = node.condition;
|
|
200609
|
+
const branches = own2 ? disjuncts(own2.ast) : [void 0];
|
|
200597
200610
|
let reason;
|
|
200598
200611
|
const refuted = branches.every((b) => {
|
|
200599
200612
|
const r = refute([...gate, ...b ? termsOfAst(b, sceneId) : []]);
|
|
200600
200613
|
reason ??= r;
|
|
200601
200614
|
return r !== void 0;
|
|
200602
200615
|
});
|
|
200603
|
-
if (refuted && reason !== void 0 &&
|
|
200616
|
+
if (refuted && reason !== void 0 && own2) {
|
|
200604
200617
|
issues.push({
|
|
200605
200618
|
nodeId: node.id,
|
|
200606
200619
|
field: "condition",
|
|
200607
|
-
src:
|
|
200620
|
+
src: own2.src,
|
|
200608
200621
|
severity: "warning",
|
|
200609
200622
|
message: `this can never run: ${reason}`
|
|
200610
200623
|
});
|
|
200611
200624
|
continue;
|
|
200612
200625
|
}
|
|
200613
|
-
if (node.type === "group") report(node.children, sceneId, [...gate, ...requirementsOf(
|
|
200626
|
+
if (node.type === "group") report(node.children, sceneId, [...gate, ...requirementsOf(own2, sceneId)]);
|
|
200614
200627
|
}
|
|
200615
200628
|
};
|
|
200616
200629
|
for (const scene of Object.values(bundle.scenes)) {
|
|
@@ -200791,7 +200804,7 @@ function bundleOutputPath(loaded) {
|
|
|
200791
200804
|
}
|
|
200792
200805
|
|
|
200793
200806
|
// ../ops/src/playable-runtime.ts
|
|
200794
|
-
var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var q=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Ne=Object.getOwnPropertyNames;var Fe=Object.prototype.hasOwnProperty;var Ae=(n,e)=>{for(var t in e)q(n,t,{get:e[t],enumerable:!0})},Ge=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Ne(e))!Fe.call(n,s)&&s!==t&&q(n,s,{get:()=>e[s],enumerable:!(r=Pe(e,s))||r.enumerable});return n};var Te=n=>Ge(q({},"__esModule",{value:!0}),n);var Ze={};Ae(Ze,{Checkpoint:()=>N,Engine:()=>$,Flow:()=>D,buildTagIndex:()=>M,describeBundle:()=>Ie,effectiveGameData:()=>Re,gameDataFields:()=>De,gameDataValue:()=>X});function k(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:k(n[2])};case"bin":return{kind:"binary",op:n[1],left:k(n[2]),right:k(n[3])};case"call":{let e=n.slice(2).map(k);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var d=class extends Error{constructor(e){super(e),this.name="EvalError"}};function A(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 d(`@${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 d(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=_(a,e);if(c===void 0)throw new d("advance() needs a quality reference (@scope.name of a quality property)");let l=w(s(a),c,"advance");return c[Math.min(l+1,c.length-1)]}let o=t.functions[i.name];if(!o)throw new d(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new d("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 d(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new d(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let u=s(i.left);if(typeof u!="boolean")throw new d(`\'and\' requires boolean operands, left is ${typeof u}`);if(!u)return!1;let m=s(i.right);if(typeof m!="boolean")throw new d(`\'and\' requires boolean operands, right is ${typeof m}`);return m}if(i.op==="or"){let u=s(i.left);if(typeof u!="boolean")throw new d(`\'or\' requires boolean operands, left is ${typeof u}`);if(u)return!0;let m=s(i.right);if(typeof m!="boolean")throw new d(`\'or\' requires boolean operands, right is ${typeof m}`);return m}let o=s(i.left),a=s(i.right),c=_(i.left,e),l=_(i.right,e),p=c??l;if(p!==void 0){if(c&&l&&!je(c,l)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new d(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return w(o,p,">")>w(a,p,">");case">=":return w(o,p,">=")>=w(a,p,">=");case"<":return w(o,p,"<")<w(a,p,"<");case"<=":return w(o,p,"<=")<=w(a,p,"<=");case"+":case"-":case"*":case"/":throw new d(`\'${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 ie(o,a);case"!=":return!ie(o,a);case">":return E(o,a,">"),o>a;case">=":return E(o,a,">="),o>=a;case"<":return E(o,a,"<"),o<a;case"<=":return E(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new d(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return E(o,a,"-"),o-a;case"*":return E(o,a,"*"),o*a;case"/":if(E(o,a,"/"),a===0)throw new d("division by zero");return o/a}}}};return s(n)}function ie(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 E(n,e,t){if(typeof n!="number"||typeof e!="number")throw new d(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}function _(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function w(n,e,t){if(typeof n!="string")throw new d(`\'${t}\' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new d(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var je=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);function R(n){if(Number.isNaN(n)||!Number.isFinite(n))return 0;let e=Math.trunc(n)%4294967296;return e<0?e+4294967296:e}function U(n){let e=R(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 Ve={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},Le=[Ve];function oe(n,e,t){let r=t?.countingCalls??Le;return G(n,t?.want??!0,e,r)}function G(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=G(n.left,e,t,r),i=G(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 G(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 b=class n{values={};decls=new Map;subscribers=new Set;auditors=new Set;norm;pathPrefix;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),this.pathPrefix=t?.pathPrefix??"",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??B(t))}}get(e){return this.values[this.norm(e)]}normalise(e){return this.norm(e)}set(e,t,r){let s=this.norm(e);if(!r?.host&&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])=>ce(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function ce(n,e,t,r,s=""){let i=r??n.name.toLowerCase();return{name:i,path:s+i,type:n.type,value:e,default:n.default??B(n),...n.values!==void 0?{values:n.values}:{},...n.stages!==void 0?{stages:n.stages}:{},writable:t??n.writable??!0}}var Me=n=>n.toLowerCase(),W=1,T=class{scopes=new Map;parked=new Map;rev=0;get revision(){return this.rev}defineOwned(e,t,r){let s=typeof r=="string"?{pathPrefix:r}:r??{},i=new b(t,{pathPrefix:s.pathPrefix??`${e}.`,...s.normalise?{normalise:s.normalise}:{}});return this.mountOwned(e,i,s.owner!==void 0?{owner:s.owner}:void 0)}mountOwned(e,t,r){this.assertFree(e,r?.owner),this.scopes.set(e,{kind:"owned",bag:t,...r?.owner!==void 0?{owner:r.owner}:{}}),this.rev++;let s=this.parked.get(e);return s&&(t.load(s),this.parked.delete(e)),this}remove(e,t){let r=this.scopes.get(e);if(!r)throw new Error(`unknown scope \'@${e}\'`);return t?.keep&&r.kind==="owned"&&this.parked.set(e,r.bag.save()),this.scopes.delete(e),this.rev++,this}discardParked(e){if(e===void 0)this.parked.clear();else for(let t of[...this.parked.keys()])t.startsWith(e)&&this.parked.delete(t);return 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){let i=typeof s=="boolean"?{writable:s}:s;this.assertFree(e,i.owner);let o=i.normalise??Me,a=new Map;for(let c of r)a.set(o(c.name),c);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:a,scopeWritable:i.writable??!0,norm:o,...i.owner!==void 0?{owner:i.owner}:{}}),this.rev++,this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(r)return r.kind==="owned"?r.bag.get(t):r.resolver.get(r.norm(t))}set(e,t,r,s){let i=this.scopes.get(e);if(!i)throw new Error(`unknown scope \'@${e}\'`);if(i.kind==="owned"){try{i.bag.set(t,r,s?.host?{host:!0}:void 0)}catch{throw new Error(`\'@${e}.${t}\' is read-only`)}return}let o=i.norm(t);if(!i.resolver.set)throw new Error(`\'@${e}.${t}\' is read-only`);if(!s?.host&&!this.foreignWritable(i,o))throw new Error(`\'@${e}.${t}\' is read-only`);i.resolver.set(o,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){let s=r.owner!==void 0?{owner:r.owner}:{};if(r.kind==="owned")for(let i of r.bag.rows())e.push({scope:t,...s,...i});else for(let[i,o]of r.decls)e.push({scope:t,...s,...ce(o,r.resolver.get(i),this.foreignWritable(r,i),i,`${t}.`)})}return e}toEvalContext(e,t){let r=this.view(t?.aliases),s={};for(let[o,a]of r)s[o]=a.kind==="owned"?a.bag.values:a.resolver;let i=this.qualityLadders(r);return i.size===0?{scopes:s,host:e}:{scopes:s,host:e,qualities:(o,a)=>{let c=r.get(o);return c?i.get(o)?.get($e(c)(a)):void 0}}}view(e){let t=new Map(this.scopes);for(let[r,s]of Object.entries(e??{})){let i=this.scopes.get(s);if(!i)throw new Error(`alias \'@${r}\' names \'${s}\', which is not registered`);t.set(r,i)}return t}qualityLadders(e){let t=new Map;for(let[r,s]of e)for(let[i,o]of ae(s)){if(o.type!=="quality"||o.stages===void 0)continue;let a=t.get(r);a||(a=new Map,t.set(r,a)),a.set(i,o.stages)}return t}toSchema(e){let t=new Map;for(let[r,s]of this.view(e?.aliases)){let i=ae(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(r,o)}return{properties:t}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());for(let[t,r]of this.parked)e[t]=structuredClone(r);return e}load(e,t){t?.keepParked||this.parked.clear();for(let[r,s]of Object.entries(e)){let i=this.scopes.get(r);i?.kind==="owned"?i.bag.load(s):i||this.parked.set(r,structuredClone(s))}}saveFragment(){return{version:W,scopes:this.save()}}loadFragment(e){if(e.version!==W)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${W})`);this.load(e.scopes)}assertFree(e,t){let r=this.scopes.get(e);if(!r)return;let s=r.owner!==void 0?` by ${r.owner}`:"",i=t!==void 0?` (wanted by ${t})`:"";throw new Error(`scope \'@${e}\' is already registered${s}${i}`)}};function ae(n){return n.kind==="foreign"?[...n.decls.entries()]:n.bag.declarations().map(e=>[n.bag.normalise(e.name),e])}function $e(n){return n.kind==="foreign"?n.norm:e=>n.bag.normalise(e)}function B(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]??"";default:return!1}}var le=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function O(n){return n.ctx.host??{}}var K={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new d("random(a, b) requires exactly 2 arguments");let t=O(e).nextRandom;if(!t)throw new d("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 d("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new d("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:de("check_flags"),eval(n,e){let t=pe(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new d("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:de("set_flags"),eval(n,e){let t=[...pe(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new d("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:V("visits"),eval:(n,e)=>O(e).visits?.(j(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:V("seen"),eval:(n,e)=>(O(e).visits?.(j(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:V("patter_visits"),eval:(n,e)=>O(e).patterVisits?.(j(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:V("patter_seen"),eval:(n,e)=>(O(e).patterVisits?.(j(n,e,"patter_seen"))??0)>0}}},ft=le.map(n=>n.token).filter(n=>n!=="patter"&&n!=="scene");function ge(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 j(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new d(`${t}(id) requires a string node id`);return r}function V(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 pe(n,e,t){if(!n)throw new d(`${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 d(`${t}() first argument must be a flags property`)}function de(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 He=/^@[A-Za-z0-9_.]+$/;function*qe(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:He.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 _e(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function Ue(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function We(n){let e="",t=!1;for(let r of n){if(Ue(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function ue(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?We(r):n}function he(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of qe(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?"":_e(s)}return t}function C(n,e){for(let t of n){e(t);let r=t.children;r&&C(r,e)}}function Ke(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function S(n){let e=n.gameId?.trim();return e||Ke(n.name)}function J(n){return`cast:${n}`}var z={open:"[",close:"]"},fe="SFX";var Y=3;function L(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function M(n){let e=new Map,t=(r,s)=>{let i=L([...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,L([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=L(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=L([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var me=new WeakMap,I="Patter",P=n=>n.replace(/%/g,"%25").replace(/\\//g,"%2F"),Se=Symbol("ownsRegistry"),y={stage:n=>`patter/scene/${P(n)}`,flow:n=>`patter/flow/${P(n)}/`,flowGlobals:n=>`patter/flow/${P(n)}/patter`,flowScene:(n,e)=>`patter/flow/${P(n)}/scene/${P(e)}`},N=class{constructor(e){this.journal=e}journal},$=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=r;let i=s[r]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let l=new Map;for(let g of e.cast??[])g.displayName&&l.set(g.name,g.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let p=new Map,u=new Map,m=new Map;for(let[g,h]of Object.entries(e.scenes)){this.sceneGameIdToId.set(S(h),g);let v=new Map;for(let f of h.blocks)u.set(f.id,{sceneId:g}),m.set(f.id,f),v.set(S(f),f.id),C(f.children,se=>p.set(se.id,se));this.blockGameIdToId.set(g,v)}let Z=e.properties??[],H=Z.filter(g=>g.shared??!0).map(we),Be=Z.filter(g=>!(g.shared??!0)).map(we),Oe=new Set(H.map(g=>g.name.toLowerCase())),x=t.registry??new T,ee=!t.registry||t[Se]===!0,te=new b(H,{pathPrefix:"@patter."}),re=[],F=[];try{x.mountOwned("patter",te,{owner:I}),F.push("patter");let g=new Set;if(t.world){let h=e.scopeRegistry?.scopes.find(f=>f.token==="world"),v=(h?.declarations??[]).map(Ee);x.defineForeign("world",t.world,v,{writable:h?.writable??!0,owner:I}),F.push("world"),g.add("world")}for(let h of ee?e.scopeRegistry?.scopes??[]:[]){if(g.has(h.token)||x.has(h.token))continue;let v=(h.declarations??[]).map(f=>Je(f,h.writable));x.defineOwned(h.token,v,{owner:I}),F.push(h.token),re.push(h.token)}}catch(g){for(let h of F)x.remove(h,{keep:!0});throw g}let ne=new Map;for(let[g,h]of Object.entries(e.scenes)){let v=new Set((h.sceneProps??[]).filter(f=>f.shared??!1).map(f=>f.name.toLowerCase()));ne.set(g,v)}this.host={logEnabled:t.log??!1,emitEngine:(g,h,v)=>this.emitEngine(g,h,v),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,nodeIndex:p,blockIndex:u,blockById:m,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:M(e),registry:x,ownsRegistry:ee,patterBag:te,hostScopes:re,patterSharedDecls:H,patterLocalDecls:Be,patterSharedNames:Oe,sceneSharedNames:ne,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,journal:null,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??z).open,captionClose:(e.closedCaptions??z).close,captionCharacter:e.closedCaptions?.character||fe,refSplitCache:new Map,refSplitRevision:x.revision}}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){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),r=o=>(o.setLocale(this.currentLocale),o.setClosedCaptions(this.host.captionsOn),o),s=()=>new n(e,{...this.creationOptions,registry:this.host.registry,[Se]:this.host.ownsRegistry});this.release(!0);let i=s();try{return i.loadGame(t),r(i)}catch{i.release(!0);let o=s();for(let[a,c]of Object.entries(t.flows)){let l=c.cursor.currentSceneId;try{o.openFlow(a,l!==null?{scene:l}:{})}catch{}}return r(o)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){this.assertExternalScopes();let{sceneId:r,blockId:s}=this.resolveOpenAddress(t.scene,t.block),i=this.flowsById.get(e),o=this.host.journal;if(o&&i&&!i.isClosed)throw new Error(`openFlow would replace the open flow \'${e}\', which can\'t be undone while a checkpoint is open`);i?.close();let a=new D(e,this.host,t.seed??this.defaultSeed);return o&&(o.flows.add(a),o.opened.add(a),o.undo.push(()=>{a.close(),i?this.flowsById.set(e,i):this.flowsById.delete(e)})),this.flowsById.set(e,a),a.start(r,s),a}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}assertExternalScopes(){for(let e of this.host.bundle.externalScopes??[])if(!this.host.registry.has(e))throw new Error(`this content names @${e}, which no engine on this registry registered: give every engine the game\'s one registry`)}resolveOpenAddress(e,t){if(e!==void 0){let r=this.resolveSceneRef(e);if(!this.host.bundle.scenes[r])throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:r};let s=this.blockGameIdToId.get(r)?.get(t)??(this.host.blockIndex.get(t)?.sceneId===r?t:void 0);if(s===void 0)throw new Error(`unknown block: ${t}`);return{sceneId:r,blockId:s}}if(t!==void 0){if(!this.host.blockIndex.has(t))throw new Error(`unknown block: ${t}`);return{blockId:t}}if(Object.keys(this.host.bundle.scenes).length===0)throw new Error("no scenes in bundle");return{}}resolveSceneRef(e){if(e!=null)return 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?S(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?S(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)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.gameData}}gameDataForBlock(e,t){let r=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(r!=null?this.host.blockById.get(r):void 0)?.gameData}}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)ye(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 ye(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...S(e)?{gameId:S(e)}:{},name:e.name,...ve(e.gameData),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...S(t)?{gameId:S(t)}:{},name:t.name,...ve(t.gameData),...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)C(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[J(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()]}listBags(){let e=[{bag:this.host.patterBag}];for(let[t,r]of this.host.stageBags)e.push({bag:r,pathPrefix:`@scene:${t}.`});return e}checkpoint(){if(this.host.journal)throw new Error("a checkpoint is already open: roll it back or commit it first");let e={undo:[],flows:new Set,opened:new Set,selectors:new Set,flowSelectors:new Map};return this.host.journal=e,new N(e)}rollback(e){this.endCheckpoint(e);let t=e.journal.undo;for(let r=t.length-1;r>=0;r--)t[r]();n.forget(e.journal)}commit(e){this.endCheckpoint(e),n.forget(e.journal)}get inCheckpoint(){return this.host.journal!==null}endCheckpoint(e){if(this.host.journal!==e.journal)throw new Error("that checkpoint is not the open one");this.host.journal=null}static forget(e){e.undo=[],e.flows.clear(),e.opened.clear(),e.selectors.clear(),e.flowSelectors.clear()}refuseInCheckpoint(e){if(this.host.journal)throw new Error(`${e} can\'t be undone, so it isn\'t allowed while a checkpoint is open`)}closeFlow(e){this.refuseInCheckpoint("closeFlow"),this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){this.refuseInCheckpoint("reset");for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.patterBag.reseed(this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear();for(let e of this.host.stageBags.keys())this.host.registry.remove(y.stage(e));this.host.stageBags.clear(),this.host.registry.discardParked("patter/")}release(e){for(let r of this.flowsById.values())r.releaseBags(e),r.close();this.flowsById.clear();let t=this.host.registry;for(let r of this.host.stageBags.keys())t.remove(y.stage(r),{keep:e});this.host.stageBags.clear();for(let r of["patter",...this.host.hostScopes])t.has(r)&&t.remove(r,{keep:e});this.creationOptions.world&&t.has("world")&&t.remove("world")}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.registry.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e),i=this.host.journal?this.host.registry.get(r,s):void 0;this.host.registry.set(r,s,t,{host:!0}),this.host.journal&&i!==void 0&&this.host.journal.undo.push(()=>this.host.registry.set(r,s,i,{host:!0}))}log(){return this.engineLog}clearLog(){this.engineLog.length=0}onTrace(e){return this.engineTraceHandlers.add(e),()=>this.engineTraceHandlers.delete(e)}emitEngine(e,t,r){for(let s of this.engineTraceHandlers)s(e,t);this.host.logEnabled&&this.engineLog.push({...t,flow:e,seq:this.engineLog.length,...r?{scene:r}:{}})}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:B(e),writable:e.writable??!0}))}splitShared(e){let t=Ce(this.host,e);if(t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return{patter:this.host.patterBag.save()}}load(e){e.patter&&this.host.patterBag.load(e.patter)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:Y,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:xe(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==Y)throw new Error(`unsupported save version: ${String(t)}`);this.assertExternalScopes();let r=this.host.registry;for(let[i,o]of this.flowsById)o.releaseBags(i in e.flows),o.close();this.flowsById.clear();for(let i of this.host.stageBags.keys())r.remove(y.stage(i),{keep:!0});this.host.stageBags.clear();let s=e.version===2?ze(e):e.registry;s&&(this.host.ownsRegistry?r.load(s):r.load(s,{keepParked:!0})),this.host.sharedVisits.clear();for(let[i,o]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(i,o);this.host.sharedSelectors.clear();for(let[i,o]of ke(e.sharedSelectors))this.host.sharedSelectors.set(i,o);for(let[i,o]of Object.entries(e.flows)){let a=new D(i,this.host,this.defaultSeed);a.restore(o),this.flowsById.set(i,a)}}},D=class{id;host;local;registered=new Set;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.patterBag.get(e):this.local.get(e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.registry.set("patter",e,t):this.local.set(e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;if(t!==null)return this.sceneBagFor(t,e)?.get(e)},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.sceneBagFor(r,e);s&&s.set(e,t)}};evalCtx;ctxRevision=-1;registryQualities;flowLog=[];flowSeq=0;constructor(e,t,r){this.id=e,this.host=t,this.rngState=R(r),this.local=this.newLocal(),this.evalCtx={scopes:{},host:{nextRandom:this.rng,visits:s=>this.visitCounts.get(s)??0,patterVisits:s=>this.host.sharedVisits.get(s)??0},qualities:(s,i)=>this.stagesFor(s,i)}}context(){let e=this.host.registry;if(e.revision!==this.ctxRevision){let t=e.toEvalContext(),r=this.evalCtx.scopes;for(let s of Object.keys(r))delete r[s];Object.assign(r,t.scopes),r.patter=this.patterResolver,r.scene=this.sceneResolver,this.registryQualities=t.qualities,this.ctxRevision=e.revision}return this.evalCtx}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 this.registryQualities?.(e,t)??s(this.host.bundle.scopeRegistry?.scopes.find(i=>i.token===e)?.declarations)}start(e,t){if(this.host.journal&&!this.host.journal.opened.has(this))throw new Error("a flow can\'t be started or reset while a checkpoint is open");if(this.releaseBags(!1),this.host.registry.discardParked(y.flow(this.id)),this.mountLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.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(this.touch(),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.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.pendingPromptBeat=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started:r,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPromptBeat:l,pendingPromptOwnerId:p}=this,u=this.stack.map(m=>({...m}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:r,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPromptBeat:l,pendingPromptOwnerId:p,stack:u})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.touch(),this.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 i=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(i);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}let s=t.index;for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(this.host.logEnabled&&t.index!==s&&this.emit({type:"select",group:t.containerId,selector:"run",children:r.slice(s,t.index+1).map((i,o)=>({id:i.id,eligible:s+o===t.index})),picked:r[t.index]?.id??null}),t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}listBags(){let e=[{bag:this.local,pathPrefix:`${this.id}/@patter.`}];for(let[t,r]of this.sceneBags)e.push({bag:r,pathPrefix:`${this.id}/@scene:${t}.`});return e}log(){return this.flowLog}clearLog(){this.flowLog.length=0}emit(e){let t=this.currentSceneId??void 0;this.host.emitEngine(this.id,e,t),this.host.logEnabled&&this.flowLog.push({...e,seq:this.flowSeq++,...t?{scene:t}:{}})}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}`);this.touch();let s=t.byId.get(e);this.emit({type:"chose",group:t.groupId,option: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.registry.get(t,r)}setProperty(e,t){this.writeProperty(e,t,!0)}writeProperty(e,t,r){let{scope:s,name:i}=this.splitRef(e),o=this.host.journal;if(s==="patter"){if(o){let a=this.host.patterSharedNames.has(i),c=a?this.host.patterBag.get(i):this.local.get(i),l=this.local;c!==void 0&&o.undo.push(a?()=>this.host.registry.set("patter",i,c):()=>l.set(i,c))}this.patterResolver.set(i,t)}else if(s==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);if(o){let a=this.sceneBagFor(this.currentSceneId,i),c=a?.get(i);a&&c!==void 0&&o.undo.push(()=>a.set(i,c))}this.sceneResolver.set(i,t)}else{let a=o?this.host.registry.get(s,i):void 0;this.host.registry.set(s,i,t,r?{host:!0}:void 0),o&&a!==void 0&&o.undo.push(()=>this.host.registry.set(s,i,a,{host:!0}))}}snapshot(){return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,selectors:xe(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");this.restoreCursor(e),this.releaseBags(!0),this.mountLocal();for(let t of new Set([this.currentSceneId,...this.stack.map(r=>r.sceneId)]))t!==null&&this.host.bundle.scenes[t]&&this.ensureSceneBags(t)}restoreCursor(e){this.rngState=R(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.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=ke(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.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),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.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.emit({type:"jump",to:e,mode:t}),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.map(c=>({id:c.id,eligible:this.eligible(c)})),r=e.children.filter(c=>this.eligible(c)),s=e.options?.order??"sequential",i=e.options?.exhaust??"once",o=c=>(this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(r.length===0)return o(null);let a=this.selectorState(e);switch(e.selector){case"branch":return o(r[0]);case"sequence":return o(s==="shuffle"?this.pickShuffle(r,i,a):s==="specificity"?this.pickSpecificity(r,i,a):this.pickSequential(r,i,a));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(u=>u.id);if(r.bag===void 0&&(r.bag=o()),r.bag.length===0){if(t==="once")return null;if(i){let u=e[s-1];return r.last=u.id,u}r.bag=o()}let a=r.bag,c=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,l=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&l>=c&&l++;let p=a[l];return a.splice(l,1),r.last=p,e.find(u=>u.id===p)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(p=>p.id));let l=new Set(r.bag);if(s=e.filter(p=>l.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(l=>{let p=this.specScore(l);return p>i&&(i=p),{c:l,s:p}}).filter(l=>l.s===i).map(l=>l.c),c;if(a.length===1)c=a[0];else{let l=r.last!==void 0?a.findIndex(u=>u.id===r.last):-1,p=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&p>=l&&p++,c=a[p]}return t!=="repeat"&&(r.bag=r.bag.filter(l=>l!==c.id)),r.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0):0}matchedSpec(e,t){return oe(e,s=>be(A(s,this.context(),K)),{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=this.host.journal,s;if(r&&(s=e.shared?r.selectors:r.flowSelectors.get(this),s||(s=new Set,r.flowSelectors.set(this,s))),r&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;r.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e){for(let t of e??[]){let r=this.evalExpr(t.value),s=this.host.logEnabled?this.getProperty(t.target):void 0;this.writeProperty(t.target,r,!1),this.emit({type:"write",target:t.target,value:r,...s!==void 0?{prev:s}:{}})}}eligible(e){return e.condition?be(this.evalExpr(e.condition)):!0}evalExpr(e){return A(this.conditionAst(e),this.context(),K)}conditionAst(e){let t=me.get(e);return t||(t=k(e.ast),me.set(e,t)),t}enter(e){let t=this.visitCounts,r=t.get(e);t.set(e,(r??0)+1);let s=this.host.sharedVisits,i=s.get(e);s.set(e,(i??0)+1),this.host.journal&&this.host.journal.undo.push(()=>{r===void 0?t.delete(e):t.set(e,r),i===void 0?s.delete(e):s.set(e,i)})}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=U(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 he(e,t=>this.getProperty(t))}stripCaptions(e){return ue(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 C(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=J(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){return Ce(this.host,e)}newLocal(){return new b(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=y.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:I}),this.registered.add(e)}releaseBags(e){for(let t of this.registered)this.host.registry.remove(t,{keep:e});this.registered.clear(),this.sceneBags.clear()}sceneBagFor(e,t){if(this.host.bundle.scenes[e])return this.ensureSceneBags(e),this.host.sceneSharedNames.get(e)?.has(t.toLowerCase())?this.host.stageBags.get(e):this.sceneBags.get(e)}ensureSceneBags(e){let t=this.host.sceneSharedNames.get(e)??new Set,r=this.host.bundle.scenes[e]?.sceneProps??[],s=this.host.journal;if(!this.sceneBags.has(e)){let i=new b(r.filter(a=>!t.has(a.name.toLowerCase())),{pathPrefix:"@scene."}),o=y.flowScene(this.id,e);this.host.registry.mountOwned(o,i,{owner:I}),this.registered.add(o),this.sceneBags.set(e,i),s&&s.undo.push(()=>{this.sceneBags.get(e)===i&&(this.host.registry.remove(o),this.registered.delete(o),this.sceneBags.delete(e))})}if(!this.host.stageBags.has(e)){let i=new b(r.filter(o=>t.has(o.name.toLowerCase())),{pathPrefix:"@scene."});this.host.registry.mountOwned(y.stage(e),i,{owner:I}),this.host.stageBags.set(e,i),s&&s.undo.push(()=>{this.host.stageBags.get(e)===i&&(this.host.registry.remove(y.stage(e)),this.host.stageBags.delete(e))})}}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;this.ensureSceneBags(e.id);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);if(!i)continue;let o=this.host.journal?i.get(s):void 0;o!==void 0&&this.host.journal.undo.push(()=>i.set(s,o)),i.set(s,B(r))}}};function ye(n,e){C(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 ve(n){return n&&Object.keys(n).length?{gameData:n}:{}}function xe(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 ke(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 we(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Ee(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function Je(n,e){let t=Ee(n),r=n.writable??e;return r===void 0?t:{...t,writable:r}}function Ce(n,e){n.refSplitRevision!==n.registry.revision&&(n.refSplitCache.clear(),n.refSplitRevision=n.registry.revision);let t=n.refSplitCache.get(e);return t||(t=ge(e,r=>r==="scene"||n.registry.has(r)||(n.bundle.externalScopes?.includes(r)??!1)),n.refSplitCache.set(e,t)),t}function ze(n){let e={};n.shared?.patter&&(e.patter=n.shared.patter);for(let[t,r]of Object.entries(n.stageBags??{}))e[y.stage(t)]=r;for(let[t,r]of Object.entries(n.flows??{})){r.scopes?.patter&&(e[y.flowGlobals(t)]=r.scopes.patter);for(let[s,i]of Object.entries(r.sceneBags??{}))e[y.flowScene(t,s)]=i}return e}function be(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Ye=(n,e)=>n.shared??e;function Q(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:Ye(n,e)}}function Qe(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function Xe(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 Ie(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=S(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:S(c),name:c.name}))});for(let c of o.blocks)Xe(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>Q(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=>Q(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(Qe)}));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=>Q(o,!0)),scene:r},gameData:i,counts:e}}function De(n,e){return n.gameDataFields?.[e]??[]}function X(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function Re(n,e){let t={};for(let r of n){let s=X(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 Te(Ze);})();\n';
|
|
200807
|
+
var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var _=Object.defineProperty;var Te=Object.getOwnPropertyDescriptor;var Ge=Object.getOwnPropertyNames;var je=Object.prototype.hasOwnProperty;var Ve=(n,e)=>{for(var t in e)_(n,t,{get:e[t],enumerable:!0})},Le=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Ge(e))!je.call(n,s)&&s!==t&&_(n,s,{get:()=>e[s],enumerable:!(r=Te(e,s))||r.enumerable});return n};var Me=n=>Le(_({},"__esModule",{value:!0}),n);var st={};Ve(st,{Checkpoint:()=>A,Engine:()=>H,Flow:()=>I,buildTagIndex:()=>$,describeBundle:()=>Oe,effectiveGameData:()=>Ne,gameDataFields:()=>Be,gameDataValue:()=>te});function D(n,e){return n!==void 0&&Object.prototype.hasOwnProperty.call(n,e)?n[e]:void 0}function k(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:k(n[2])};case"bin":return{kind:"binary",op:n[1],left:k(n[2]),right:k(n[3])};case"call":{let e=n.slice(2).map(k);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var d=class extends Error{constructor(e){super(e),this.name="EvalError"}};function G(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=D(e.scopes,i.scope);if(o===void 0)return!1;let a=typeof o.get=="function"?o.get(i.name):D(o,i.name);if(a===void 0){if(r.get(i.scope)==="throw")throw new d(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{if(i.name==="advance"&&!D(t.functions,i.name)){let a=i.args[0];if(i.args.length!==1||a===void 0)throw new d(`advance() takes exactly 1 argument, got ${i.args.length}`);let c=U(a,e);if(c===void 0)throw new d("advance() needs a quality reference (@scope.name of a quality property)");let l=w(s(a),c,"advance");return c[Math.min(l+1,c.length-1)]}let o=D(t.functions,i.name);if(!o)throw new d(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:s,ctx:e})}case"flagdelta":throw new d("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 d(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=s(i.operand);if(typeof o!="number")throw new d(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let f=s(i.left);if(typeof f!="boolean")throw new d(`\'and\' requires boolean operands, left is ${typeof f}`);if(!f)return!1;let u=s(i.right);if(typeof u!="boolean")throw new d(`\'and\' requires boolean operands, right is ${typeof u}`);return u}if(i.op==="or"){let f=s(i.left);if(typeof f!="boolean")throw new d(`\'or\' requires boolean operands, left is ${typeof f}`);if(f)return!0;let u=s(i.right);if(typeof u!="boolean")throw new d(`\'or\' requires boolean operands, right is ${typeof u}`);return u}let o=s(i.left),a=s(i.right),c=U(i.left,e),l=U(i.right,e),p=c??l;if(p!==void 0){if(c&&l&&!$e(c,l)&&(i.op===">"||i.op===">="||i.op==="<"||i.op==="<="))throw new d(`\'${i.op}\' compares two different qualities, whose stage orders are unrelated`);switch(i.op){case">":return w(o,p,">")>w(a,p,">");case">=":return w(o,p,">=")>=w(a,p,">=");case"<":return w(o,p,"<")<w(a,p,"<");case"<=":return w(o,p,"<=")<=w(a,p,"<=");case"+":case"-":case"*":case"/":throw new d(`\'${i.op}\' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(i.op){case"==":return ce(o,a);case"!=":return!ce(o,a);case">":return E(o,a,">"),o>a;case">=":return E(o,a,">="),o>=a;case"<":return E(o,a,"<"),o<a;case"<=":return E(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new d(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return E(o,a,"-"),o-a;case"*":return E(o,a,"*"),o*a;case"/":if(E(o,a,"/"),a===0)throw new d("division by zero");return o/a}}}};return s(n)}function ce(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 E(n,e,t){if(typeof n!="number"||typeof e!="number")throw new d(`\'${t}\' requires numeric operands, got ${typeof n} and ${typeof e}`)}function U(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function w(n,e,t){if(typeof n!="string")throw new d(`\'${t}\' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new d(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var $e=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);function R(n){if(Number.isNaN(n)||!Number.isFinite(n))return 0;let e=Math.trunc(n)%4294967296;return e<0?e+4294967296:e}function W(n){let e=R(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 He={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},qe=[He];function le(n,e,t){let r=t?.countingCalls??qe;return j(n,t?.want??!0,e,r)}function j(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=j(n.left,e,t,r),i=j(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 j(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 b=class n{values=Object.create(null);decls=new Map;subscribers=new Set;auditors=new Set;norm;pathPrefix;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),this.pathPrefix=t?.pathPrefix??"",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??P(t))}}get(e){return this.values[this.norm(e)]}normalise(e){return this.norm(e)}writable(e){return this.decls.get(this.norm(e))?.writable!==!1}set(e,t,r){let s=this.norm(e);if(!r?.host&&!this.writable(s))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])=>de(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new n([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function de(n,e,t,r,s=""){let i=r??n.name.toLowerCase();return{name:i,path:s+i,type:n.type,value:e,default:n.default??P(n),...n.values!==void 0?{values:n.values}:{},...n.stages!==void 0?{stages:n.stages}:{},writable:t??n.writable??!0}}var _e=n=>n.toLowerCase(),J=1,V=class{scopes=new Map;parked=new Map;rev=0;get revision(){return this.rev}defineOwned(e,t,r){let s=typeof r=="string"?{pathPrefix:r}:r??{},i=new b(t,{pathPrefix:s.pathPrefix??`${e}.`,...s.normalise?{normalise:s.normalise}:{}});return this.mountOwned(e,i,s.owner!==void 0?{owner:s.owner}:void 0)}mountOwned(e,t,r){this.assertFree(e,r?.owner),this.scopes.set(e,{kind:"owned",bag:t,...r?.owner!==void 0?{owner:r.owner}:{}}),this.rev++;let s=this.parked.get(e);return s&&(t.load(s),this.parked.delete(e)),this}remove(e,t){let r=this.scopes.get(e);if(!r)throw new Error(`unknown scope \'@${e}\'`);return t?.keep&&r.kind==="owned"&&this.parked.set(e,r.bag.save()),this.scopes.delete(e),this.rev++,this}discardParked(e){if(e===void 0)this.parked.clear();else for(let t of[...this.parked.keys()])t.startsWith(e)&&this.parked.delete(t);return 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){let i=typeof s=="boolean"?{writable:s}:s;this.assertFree(e,i.owner);let o=i.normalise??_e,a=new Map;for(let c of r)a.set(o(c.name),c);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:a,scopeWritable:i.writable??!0,norm:o,...i.owner!==void 0?{owner:i.owner}:{}}),this.rev++,this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(r)return r.kind==="owned"?r.bag.get(t):r.resolver.get(r.norm(t))}set(e,t,r,s){let i=this.scopes.get(e);if(!i)throw new Error(`unknown scope \'@${e}\'`);if(i.kind==="owned"){if(!s?.host&&!i.bag.writable(t))throw new Error(`\'@${e}.${t}\' is read-only`);i.bag.set(t,r,s?.host?{host:!0}:void 0);return}let o=i.norm(t);if(!i.resolver.set)throw new Error(`\'@${e}.${t}\' is read-only`);if(!s?.host&&!this.foreignWritable(i,o))throw new Error(`\'@${e}.${t}\' is read-only`);i.resolver.set(o,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){let s=r.owner!==void 0?{owner:r.owner}:{};if(r.kind==="owned")for(let i of r.bag.rows())e.push({scope:t,...s,...i});else for(let[i,o]of r.decls)e.push({scope:t,...s,...de(o,r.resolver.get(i),this.foreignWritable(r,i),i,`${t}.`)})}return e}toEvalContext(e,t){let r=this.view(t?.aliases),s={};for(let[o,a]of r)s[o]=a.kind==="owned"?a.bag.values:a.resolver;let i=this.qualityLadders(r);return i.size===0?{scopes:s,host:e}:{scopes:s,host:e,qualities:(o,a)=>{let c=r.get(o);return c?i.get(o)?.get(Ue(c)(a)):void 0}}}view(e){let t=new Map(this.scopes);for(let[r,s]of Object.entries(e??{})){let i=this.scopes.get(s);if(!i)throw new Error(`alias \'@${r}\' names \'${s}\', which is not registered`);t.set(r,i)}return t}qualityLadders(e){let t=new Map;for(let[r,s]of e)for(let[i,o]of pe(s)){if(o.type!=="quality"||o.stages===void 0)continue;let a=t.get(r);a||(a=new Map,t.set(r,a)),a.set(i,o.stages)}return t}toSchema(e){let t=new Map;for(let[r,s]of this.view(e?.aliases)){let i=pe(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(r,o)}return{properties:t}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]=r.bag.save());for(let[t,r]of this.parked)e[t]=structuredClone(r);return e}load(e,t){t?.keepParked||this.parked.clear();for(let[r,s]of Object.entries(e)){let i=this.scopes.get(r);i?.kind==="owned"?i.bag.load(s):i||this.parked.set(r,structuredClone(s))}}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,t){let r=this.scopes.get(e);if(!r)return;let s=r.owner!==void 0?` by ${r.owner}`:"",i=t!==void 0?` (wanted by ${t})`:"";throw new Error(`scope \'@${e}\' is already registered${s}${i}`)}};function pe(n){return n.kind==="foreign"?[...n.decls.entries()]:n.bag.declarations().map(e=>[n.bag.normalise(e.name),e])}function Ue(n){return n.kind==="foreign"?n.norm:e=>n.bag.normalise(e)}function P(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]??"";default:return!1}}var ge=[{token:"patter",engine:"Patterplay",means:"Patter\'s shared globals"},{token:"story",engine:"Storylet Engine",means:"the Storylet Engine\'s shared @story properties"}];function O(n){return n.ctx.host??{}}var K={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new d("random(a, b) requires exactly 2 arguments");let t=O(e).nextRandom;if(!t)throw new d("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 d("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new d("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:he("check_flags"),eval(n,e){let t=ue(n[0],e,"check_flags");for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new d("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:he("set_flags"),eval(n,e){let t=[...ue(n[0],e,"set_flags")];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new d("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:M("visits"),eval:(n,e)=>O(e).visits?.(L(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:M("seen"),eval:(n,e)=>(O(e).visits?.(L(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:M("patter_visits"),eval:(n,e)=>O(e).patterVisits?.(L(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:M("patter_seen"),eval:(n,e)=>(O(e).patterVisits?.(L(n,e,"patter_seen"))??0)>0}}},xt=ge.map(n=>n.token).filter(n=>n!=="patter"&&n!=="scene");function fe(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 L(n,e,t){let r=e.evaluate(n[0]);if(typeof r!="string")throw new d(`${t}(id) requires a string node id`);return r}function M(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 ue(n,e,t){if(!n)throw new d(`${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 d(`${t}() first argument must be a flags property`)}function he(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 We=/^@[A-Za-z0-9_.]+$/;function*Je(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:We.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 Ke(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function ze(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function Ye(n){let e="",t=!1;for(let r of n){if(ze(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function me(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?Ye(r):n}function Se(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of Je(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?"":Ke(s)}return t}function C(n,e){for(let t of n){e(t);let r=t.children;r&&C(r,e)}}function Qe(n){return n.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function S(n){let e=n.gameId?.trim();return e||Qe(n.name)}function z(n){return`cast:${n}`}var Y={open:"[",close:"]"},ye="SFX";var Q=3;function B(n){let e=new Set,t=[];for(let r of n)e.has(r)||(e.add(r),t.push(r));return t}function $(n){let e=new Map,t=(r,s)=>{let i=B([...s,...r.tags??[]]);if(e.set(r.id,i),r.type==="group"&&r.prompt&&e.set(r.prompt.id,B([...i,...r.prompt.tags??[]])),r.type==="group")for(let o of r.children)t(o,i);else for(let o of r.beats??[])e.set(o.id,B([...i,...o.tags??[]]))};for(let r of Object.values(n.scenes)){let s=B(r.tags??[]);e.set(r.id,s);for(let i of r.blocks){let o=B([...s,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var ve=new WeakMap,F="Patter",N=n=>n.replace(/%/g,"%25").replace(/\\//g,"%2F"),we=Symbol("ownsRegistry"),y={stage:n=>`patter/scene/${N(n)}`,flow:n=>`patter/flow/${N(n)}/`,flowGlobals:n=>`patter/flow/${N(n)}/patter`,flowScene:(n,e)=>`patter/flow/${N(n)}/scene/${N(e)}`},A=class{constructor(e){this.journal=e}journal},H=class n{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;engineLog=[];engineSeq=0;engineTraceHandlers=new Set;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,s=e.strings;this.allStrings=s,this.currentLocale=r;let i=s[r]??{},o=s[e.locales.default]??{},a=e.localisation,c=a?.mode==="ids"&&!a.sourceDebug;this.sourceDebug=a?.mode==="ids"&&!!a.sourceDebug,this.sourceDebug&&typeof console<"u"&&console.warn("[Patterplay] source-only DEBUG build: strings are the source language for debugging, not a shippable localised build.");let l=new Map;for(let g of e.cast??[])g.displayName&&l.set(g.name,g.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let p=new Map,f=new Map,u=new Map;for(let[g,h]of Object.entries(e.scenes)){this.sceneGameIdToId.set(S(h),g);let v=new Map;for(let m of h.blocks)f.set(m.id,{sceneId:g}),u.set(m.id,m),v.set(S(m),m.id),C(m.children,ae=>p.set(ae.id,ae));this.blockGameIdToId.set(g,v)}let re=e.properties??[],q=re.filter(g=>g.shared??!0).map(Ee),Fe=re.filter(g=>!(g.shared??!0)).map(Ee),Ae=new Set(q.map(g=>g.name.toLowerCase())),x=t.registry??new V,ne=!t.registry||t[we]===!0,se=new b(q,{pathPrefix:"@patter."}),ie=[],T=[];try{x.mountOwned("patter",se,{owner:F}),T.push("patter");let g=new Set;if(t.world){let h=e.scopeRegistry?.scopes.find(m=>m.token==="world"),v=(h?.declarations??[]).map(Re);x.defineForeign("world",t.world,v,{writable:h?.writable??!0,owner:F}),T.push("world"),g.add("world")}for(let h of ne?e.scopeRegistry?.scopes??[]:[]){if(g.has(h.token)||x.has(h.token))continue;let v=(h.declarations??[]).map(m=>Ze(m,h.writable));x.defineOwned(h.token,v,{owner:F}),T.push(h.token),ie.push(h.token)}}catch(g){for(let h of T)x.remove(h,{keep:!0});throw g}let oe=new Map;for(let[g,h]of Object.entries(e.scenes)){let v=new Set((h.sceneProps??[]).filter(m=>m.shared??!1).map(m=>m.name.toLowerCase()));oe.set(g,v)}this.host={logEnabled:t.log??!1,emitEngine:(g,h,v)=>this.emitEngine(g,h,v),bundle:e,emitIds:c,strings:i,defaultStrings:o,castDisplay:l,nodeIndex:p,blockIndex:f,blockById:u,sceneGameIdToId:this.sceneGameIdToId,blockGameIdToId:this.blockGameIdToId,tagIndex:$(e),registry:x,ownsRegistry:ne,patterBag:se,hostScopes:ie,patterSharedDecls:q,patterLocalDecls:Fe,patterSharedNames:Ae,sceneSharedNames:oe,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,journal:null,customRng:t.rng,onDryChoice:t.onDryChoice,onError:t.onError??(g=>console.warn(`Patterplay: ${g.kind} on ${g.node} in flow \'${g.flow}\' failed, played through: ${g.message}`)),replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??Y).open,captionClose:(e.closedCaptions??Y).close,captionCharacter:e.closedCaptions?.character||ye,refSplitCache:new Map,refSplitRevision:x.revision}}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){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),r=o=>(o.setLocale(this.currentLocale),o.setClosedCaptions(this.host.captionsOn),o),s=()=>new n(e,{...this.creationOptions,registry:this.host.registry,[we]:this.host.ownsRegistry});this.release(!0);let i=s();try{return i.loadGame(t),r(i)}catch{i.release(!0);let o=s();for(let[a,c]of Object.entries(t.flows)){let l=c.cursor.currentSceneId;try{o.openFlow(a,l!==null?{scene:l}:{})}catch{}}return r(o)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){this.assertExternalScopes();let{sceneId:r,blockId:s}=this.resolveOpenAddress(t.scene,t.block),i=this.flowsById.get(e),o=this.host.journal;if(o&&i&&!i.isClosed)throw new Error(`openFlow would replace the open flow \'${e}\', which can\'t be undone while a checkpoint is open`);i?.close();let a=new I(e,this.host,t.seed??this.defaultSeed);return o&&(o.flows.add(a),o.opened.add(a),o.undo.push(()=>{a.close(),i?this.flowsById.set(e,i):this.flowsById.delete(e)})),this.flowsById.set(e,a),a.start(r,s),a}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}assertExternalScopes(){for(let e of this.host.bundle.externalScopes??[])if(!this.host.registry.has(e))throw new Error(`this content names @${e}, which no engine on this registry registered: give every engine the game\'s one registry`)}resolveOpenAddress(e,t){if(e!==void 0){let r=X(this.host,e);if(r===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:r};let s=Z(this.host,r,t);if(s===void 0)throw new Error(`unknown block: ${t}`);return{sceneId:r,blockId:s}}if(t!==void 0){if(!this.host.blockIndex.has(t))throw new Error(`unknown block: ${t}`);return{blockId:t}}if(Object.keys(this.host.bundle.scenes).length===0)throw new Error("no scenes in bundle");return{}}resolveSceneRef(e){if(e!=null)return X(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:Z(this.host,e,t)}sceneAddress(e){let t=this.host.bundle.scenes[e];return t?S(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?S(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)??[]}gameDataForScene(e){let t=this.resolveSceneRef(e);return{...(t!=null?this.host.bundle.scenes[t]:void 0)?.gameData}}gameDataForBlock(e,t){let r=this.resolveBlockRef(this.resolveSceneRef(e),t);return{...(r!=null?this.host.blockById.get(r):void 0)?.gameData}}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)be(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 be(i.children,o),[...o]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...S(e)?{gameId:S(e)}:{},name:e.name,...xe(e.gameData),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...S(t)?{gameId:S(t)}:{},name:t.name,...xe(t.gameData),...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)C(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[z(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()]}listBags(){let e=[{bag:this.host.patterBag}];for(let[t,r]of this.host.stageBags)e.push({bag:r,pathPrefix:`@scene:${t}.`});return e}checkpoint(){if(this.host.journal)throw new Error("a checkpoint is already open: roll it back or commit it first");let e={undo:[],flows:new Set,opened:new Set,selectors:new Set,flowSelectors:new Map};return this.host.journal=e,new A(e)}rollback(e){this.endCheckpoint(e);let t=e.journal.undo;for(let r=t.length-1;r>=0;r--)t[r]();n.forget(e.journal)}commit(e){this.endCheckpoint(e),n.forget(e.journal)}get inCheckpoint(){return this.host.journal!==null}endCheckpoint(e){if(this.host.journal!==e.journal)throw new Error("that checkpoint is not the open one");this.host.journal=null}static forget(e){e.undo=[],e.flows.clear(),e.opened.clear(),e.selectors.clear(),e.flowSelectors.clear()}refuseInCheckpoint(e){if(this.host.journal)throw new Error(`${e} can\'t be undone, so it isn\'t allowed while a checkpoint is open`)}closeFlow(e){this.refuseInCheckpoint("closeFlow"),this.flowsById.get(e)?.close(),this.flowsById.delete(e)}reset(){this.refuseInCheckpoint("reset");for(let e of this.flowsById.values())e.close();this.flowsById.clear(),this.host.patterBag.reseed(this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear();for(let e of this.host.stageBags.keys())this.host.registry.remove(y.stage(e));this.host.stageBags.clear(),this.host.registry.discardParked("patter/")}release(e){for(let r of this.flowsById.values())r.releaseBags(e),r.close();this.flowsById.clear();let t=this.host.registry;for(let r of this.host.stageBags.keys())t.remove(y.stage(r),{keep:e});this.host.stageBags.clear();for(let r of["patter",...this.host.hostScopes])t.has(r)&&t.remove(r,{keep:e});this.creationOptions.world&&t.has("world")&&t.remove("world")}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.registry.get(t,r)}setProperty(e,t){let{scope:r,name:s}=this.splitShared(e),i=this.host.journal?this.host.registry.get(r,s):void 0;this.host.registry.set(r,s,t,{host:!0}),this.host.journal&&i!==void 0&&this.host.journal.undo.push(()=>this.host.registry.set(r,s,i,{host:!0}))}log(){return this.engineLog}clearLog(){this.engineLog.length=0}onTrace(e){return this.engineTraceHandlers.add(e),()=>this.engineTraceHandlers.delete(e)}emitEngine(e,t,r){for(let s of this.engineTraceHandlers)s(e,t);this.host.logEnabled&&this.engineLog.push({...t,flow:e,seq:this.engineSeq++,...r?{scene:r}:{}})}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:P(e),writable:e.writable??!0}))}splitShared(e){let t=Pe(this.host,e);if(t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return{patter:this.host.patterBag.save()}}load(e){e.patter&&this.host.patterBag.load(e.patter)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:Q,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:Ie(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==Q)throw new Error(`unsupported save version: ${String(t)}`);let r=e.flows;if(typeof r!="object"||r===null||Array.isArray(r))throw new Error("malformed save: no flows");this.assertExternalScopes();let s=this.host.registry;for(let[o,a]of this.flowsById)a.releaseBags(o in e.flows),a.close();this.flowsById.clear();for(let o of this.host.stageBags.keys())s.remove(y.stage(o),{keep:!0});this.host.stageBags.clear();let i=e.version===2?et(e):e.registry;i&&(this.host.ownsRegistry?s.load(i):s.load(i,{keepParked:!0})),this.host.sharedVisits.clear();for(let[o,a]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(o,a);this.host.sharedSelectors.clear();for(let[o,a]of De(e.sharedSelectors))this.host.sharedSelectors.set(o,a);for(let[o,a]of Object.entries(e.flows)){let c=new I(o,this.host,this.defaultSeed);c.restore(a),this.flowsById.set(o,c)}}},I=class{id;host;local;registered=new Set;rngState;started=!1;flowEnded=!1;closed=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPrompt=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.patterBag.get(e):this.local.get(e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.registry.set("patter",e,t):this.local.set(e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;if(t!==null)return this.sceneBagFor(t,e)?.get(e)},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let s=this.sceneBagFor(r,e);s&&s.set(e,t)}};evalCtx;ctxRevision=-1;registryQualities;flowLog=[];flowSeq=0;constructor(e,t,r){this.id=e,this.host=t,this.rngState=R(r),this.local=this.newLocal(),this.evalCtx={scopes:{},host:{nextRandom:this.rng,visits:s=>this.visitCounts.get(s)??0,patterVisits:s=>this.host.sharedVisits.get(s)??0},qualities:(s,i)=>this.stagesFor(s,i)}}context(){let e=this.host.registry;if(e.revision!==this.ctxRevision){let t=e.toEvalContext(),r=this.evalCtx.scopes;for(let s of Object.keys(r))delete r[s];Object.assign(r,t.scopes),r.patter=this.patterResolver,r.scene=this.sceneResolver,this.registryQualities=t.qualities,this.ctxRevision=e.revision}return this.evalCtx}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 this.registryQualities?.(e,t)??s(this.host.bundle.scopeRegistry?.scopes.find(i=>i.token===e)?.declarations)}start(e,t){if(this.host.journal&&!this.host.journal.opened.has(this))throw new Error("a flow can\'t be started or reset while a checkpoint is open");if(this.releaseBags(!1),this.host.registry.discardParked(y.flow(this.id)),this.mountLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.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(this.touch(),e==="END")return this.started=!0,this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null,this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=X(this.host,e);if(r===void 0)return!1;let s;return t!==void 0&&(s=Z(this.host,r,t),s===void 0)?!1:this.started?(this.pendingChoice=null,this.pendingPrompt=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.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started:r,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:p}=this,f=this.stack.map(u=>({...u}));e.undo.push(()=>{Object.assign(this,{rngState:t,started:r,flowEnded:s,currentSceneId:i,activeSnippet:o,beatIndex:a,pendingChoice:c,pendingPrompt:l,pendingPromptOwnerId:p,stack:f})})}get isClosed(){return this.closed}get currentScene(){return this.currentSceneId}advance(){if(this.closed)return{type:"end"};if(!this.started)throw new Error("flow has not been started");if(this.touch(),this.pendingPrompt){let e=this.pendingPrompt;return this.pendingPrompt=null,this.pendingPromptOwnerId=null,e.shown?this.promptResult(e.beat,e.shown):this.beatResult(e.beat)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit,this.activeSnippet.id);let i=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(i);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}let s=t.index;for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(this.host.logEnabled&&t.index!==s&&this.emit({type:"select",group:t.containerId,selector:"run",children:r.slice(s,t.index+1).map((i,o)=>({id:i.id,eligible:s+o===t.index})),picked:r[t.index]?.id??null}),t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}listBags(){let e=[{bag:this.local,pathPrefix:`${this.id}/@patter.`}];for(let[t,r]of this.sceneBags)e.push({bag:r,pathPrefix:`${this.id}/@scene:${t}.`});return e}log(){return this.flowLog}clearLog(){this.flowLog.length=0}emit(e){let t=this.currentSceneId??void 0;this.host.emitEngine(this.id,e,t),this.host.logEnabled&&this.flowLog.push({...e,seq:this.flowSeq++,...t?{scene:t}:{}})}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(o=>o.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);this.touch();let s=t.byId.get(e);this.emit({type:"chose",group:t.groupId,option:e}),this.pendingChoice=null;let i=this.host.replayPromptOnChoose?this.authoredPromptOf(s):void 0;this.pendingPrompt=i&&r.prompt?{beat:i,shown:{...r.prompt}}:null,this.pendingPromptOwnerId=this.pendingPrompt?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.registry.get(t,r)}setProperty(e,t){this.writeProperty(e,t,!0)}writeProperty(e,t,r){let{scope:s,name:i}=this.splitRef(e),o=this.host.journal;if(s==="patter"){if(o){let a=this.host.patterSharedNames.has(i),c=a?this.host.patterBag.get(i):this.local.get(i),l=this.local;c!==void 0&&o.undo.push(a?()=>this.host.registry.set("patter",i,c):()=>l.set(i,c))}this.patterResolver.set(i,t)}else if(s==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);if(o){let a=this.sceneBagFor(this.currentSceneId,i),c=a?.get(i);a&&c!==void 0&&o.undo.push(()=>a.set(i,c))}this.sceneResolver.set(i,t)}else{let a=o?this.host.registry.get(s,i):void 0;this.host.registry.set(s,i,t,r?{host:!0}:void 0),o&&a!==void 0&&o.undo.push(()=>this.host.registry.set(s,i,a,{host:!0}))}}snapshot(){return{rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,...this.pendingPrompt?.shown?{pendingPrompt:Xe(this.pendingPrompt.shown)}:{},selectors:Ie(this.selectors)}}}restore(e){if(this.host.journal)throw new Error("a flow can\'t be restored while a checkpoint is open");this.restoreCursor(e),this.releaseBags(!0),this.mountLocal();for(let t of new Set([this.currentSceneId,...this.stack.map(r=>r.sceneId)]))t!==null&&this.host.bundle.scenes[t]&&this.ensureSceneBags(t)}restoreCursor(e){this.rngState=R(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.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.pendingPrompt=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId),s=r?this.authoredPromptOf(r):void 0;s?this.pendingPrompt={beat:s,...t.pendingPrompt?{shown:{...t.pendingPrompt}}:{}}:this.pendingPromptOwnerId=null}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry,t.id)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let 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,e.id),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.some(o=>o.eligible)){this.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.emit({type:"jump",to:e,mode:t}),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.map(c=>({id:c.id,eligible:this.eligible(c)})),r=e.children.filter((c,l)=>t[l].eligible),s=e.options?.order??"sequential",i=e.options?.exhaust??"once",o=c=>(this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(r.length===0)return o(null);let a=this.selectorState(e);switch(e.selector){case"branch":return o(r[0]);case"sequence":return o(s==="shuffle"?this.pickShuffle(r,i,a):s==="specificity"?this.pickSpecificity(r,i,a):this.pickSequential(r,i,a));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(u=>u.id),a=new Set(e.map(u=>u.id));if(r.bag===void 0&&(r.bag=o()),!r.bag.some(u=>a.has(u))){if(t==="once")return null;if(i){let u=e[s-1];return r.last=u.id,u}if(r.bag=o(),r.bag.length===0)return null}let c=r.bag.filter(u=>a.has(u)),l=r.last!==void 0&&c.length>1?c.indexOf(r.last):-1,p=Math.floor(this.rng()*(l>=0?c.length-1:c.length));l>=0&&p>=l&&p++;let f=c[p];return r.bag.splice(r.bag.indexOf(f),1),r.last=f,e.find(u=>u.id===f)}pickSpecificity(e,t,r){let s=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(p=>p.id));let l=new Set(r.bag);if(s=e.filter(p=>l.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(l=>{let p=this.specScore(l);return p>i&&(i=p),{c:l,s:p}}).filter(l=>l.s===i).map(l=>l.c),c;if(a.length===1)c=a[0];else{let l=r.last!==void 0?a.findIndex(f=>f.id===r.last):-1,p=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&p>=l&&p++,c=a[p]}return t!=="repeat"&&(r.bag=r.bag.filter(l=>l!==c.id)),r.last=c.id,c}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0,e):0}matchedSpec(e,t,r){return le(e,i=>{try{return Ce(G(i,this.context(),K))}catch(o){return this.reportError("best-match",r.id,r.condition,o),!1}},{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=this.host.journal,s;if(r&&(s=e.shared?r.selectors:r.flowSelectors.get(this),s||(s=new Set,r.flowSelectors.set(this,s))),r&&s&&!s.has(e.id)){s.add(e.id);let o=t.get(e.id),a=o?{...o,...o.bag?{bag:[...o.bag]}:{}}:void 0;r.undo.push(()=>{a?t.set(e.id,a):t.delete(e.id)})}let i=t.get(e.id);return i||(i={},t.set(e.id,i)),i}runEffects(e,t){for(let r of e??[]){let s;try{s=this.evalExpr(r.value);let i=this.host.logEnabled?this.getProperty(r.target):void 0;this.writeProperty(r.target,s,!1),this.emit({type:"write",target:r.target,value:s,...i!==void 0?{prev:i}:{}})}catch(i){this.reportError("effect",t,r.value,i)}}}eligible(e){if(!e.condition)return!0;try{return Ce(this.evalExpr(e.condition))}catch(t){return this.reportError("condition",e.id,e.condition,t),!1}}reportError(e,t,r,s){let i=s instanceof Error?s.message:String(s),o=r?.src;this.host.onError({flow:this.id,kind:e,node:t,...o?{source:o}:{},message:i}),this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return G(this.conditionAst(e),this.context(),K)}conditionAst(e){let t=ve.get(e);return t||(t=k(e.ast),ve.set(e,t)),t}enter(e){let t=this.visitCounts,r=t.get(e);t.set(e,(r??0)+1);let s=this.host.sharedVisits,i=s.get(e);s.set(e,(i??0)+1),this.host.journal&&this.host.journal.undo.push(()=>{r===void 0?t.delete(e):t.set(e,r),i===void 0?s.delete(e):s.set(e,i)})}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=W(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 Se(e,t=>this.getProperty(t))}stripCaptions(e){return me(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let 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}}authoredPromptOf(e){return e.type==="group"?e.prompt:void 0}promptResult(e,t){let r=this.host.tagIndex.get(e.id),s=r&&r.length?{tags:r}:{};return t.kind==="text"?{type:"text",id:e.id,text:t.text,gameData:e.gameData,...s}:{type:"line",id:e.id,text:t.text,character:t.character,characterName:t.characterName,direction:t.direction,gameData:e.gameData,...s}}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 C(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=z(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){return Pe(this.host,e)}newLocal(){return new b(this.host.patterLocalDecls,{pathPrefix:"@patter."})}mountLocal(){this.local=this.newLocal();let e=y.flowGlobals(this.id);this.host.registry.mountOwned(e,this.local,{owner:F}),this.registered.add(e)}releaseBags(e){for(let t of this.registered)this.host.registry.remove(t,{keep:e});this.registered.clear(),this.sceneBags.clear()}sceneBagFor(e,t){if(this.host.bundle.scenes[e])return this.ensureSceneBags(e),this.host.sceneSharedNames.get(e)?.has(t.toLowerCase())?this.host.stageBags.get(e):this.sceneBags.get(e)}ensureSceneBags(e){let t=this.host.sceneSharedNames.get(e)??new Set,r=this.host.bundle.scenes[e]?.sceneProps??[],s=this.host.journal;if(!this.sceneBags.has(e)){let i=new b(r.filter(c=>!t.has(c.name.toLowerCase())),{pathPrefix:"@scene."}),o=y.flowScene(this.id,e),a=ke(this.host.registry,o,i,s!==null);this.registered.add(o),this.sceneBags.set(e,i),s&&s.undo.push(()=>{this.sceneBags.get(e)===i&&(this.host.registry.remove(o,a?{keep:!0}:void 0),this.registered.delete(o),this.sceneBags.delete(e))})}if(!this.host.stageBags.has(e)){let i=new b(r.filter(a=>t.has(a.name.toLowerCase())),{pathPrefix:"@scene."}),o=ke(this.host.registry,y.stage(e),i,s!==null);this.host.stageBags.set(e,i),s&&s.undo.push(()=>{this.host.stageBags.get(e)===i&&(this.host.registry.remove(y.stage(e),o?{keep:!0}:void 0),this.host.stageBags.delete(e))})}}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;this.ensureSceneBags(e.id);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);if(!i)continue;let o=this.host.journal?i.get(s):void 0;o!==void 0&&this.host.journal.undo.push(()=>i.set(s,o)),i.set(s,P(r))}}};function be(n,e){C(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 xe(n){return n&&Object.keys(n).length?{gameData:n}:{}}function Xe(n){let e={kind:n.kind,text:n.text};return n.character!==void 0&&(e.character=n.character),n.characterName!==void 0&&(e.characterName=n.characterName),n.direction!==void 0&&(e.direction=n.direction),e}function Ie(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 ke(n,e,t,r){let s=r?JSON.stringify(t.save()):"";return n.mountOwned(e,t,{owner:F}),r&&JSON.stringify(t.save())!==s}function Ee(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Re(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function Ze(n,e){let t=Re(n),r=n.writable??e;return r===void 0?t:{...t,writable:r}}function X(n,e){return n.sceneGameIdToId.get(e)??(n.bundle.scenes[e]?e:void 0)}function Z(n,e,t){return n.blockGameIdToId.get(e)?.get(t)??(n.blockIndex.get(t)?.sceneId===e?t:void 0)}function Pe(n,e){n.refSplitRevision!==n.registry.revision&&(n.refSplitCache.clear(),n.refSplitRevision=n.registry.revision);let t=n.refSplitCache.get(e);return t||(t=fe(e,r=>r==="scene"||n.registry.has(r)||(n.bundle.externalScopes?.includes(r)??!1)),n.refSplitCache.set(e,t)),t}function et(n){let e={};n.shared?.patter&&(e.patter=n.shared.patter);for(let[t,r]of Object.entries(n.stageBags??{}))e[y.stage(t)]=r;for(let[t,r]of Object.entries(n.flows??{})){r.scopes?.patter&&(e[y.flowGlobals(t)]=r.scopes.patter);for(let[s,i]of Object.entries(r.sceneBags??{}))e[y.flowScene(t,s)]=i}return e}function Ce(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var tt=(n,e)=>n.shared??e;function ee(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:tt(n,e)}}function rt(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function nt(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 Oe(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=S(o);t.push({gameId:a,name:o.name,blocks:o.blocks.map(c=>({gameId:S(c),name:c.name}))});for(let c of o.blocks)nt(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>ee(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=>ee(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(rt)}));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=>ee(o,!0)),scene:r},gameData:i,counts:e}}function Be(n,e){return n.gameDataFields?.[e]??[]}function te(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function Ne(n,e){let t={};for(let r of n){let s=te(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 Me(st);})();\n';
|
|
200795
200808
|
|
|
200796
200809
|
// ../ops/src/export-html.ts
|
|
200797
200810
|
var esc = (s) => s.replace(/[&<>]/g, (c2) => ({ "&": "&", "<": "<", ">": ">" })[c2]);
|
|
@@ -201042,6 +201055,7 @@ function buildTagIndex(bundle) {
|
|
|
201042
201055
|
const visit = (node, inherited) => {
|
|
201043
201056
|
const acc = dedupe([...inherited, ...node.tags ?? []]);
|
|
201044
201057
|
index2.set(node.id, acc);
|
|
201058
|
+
if (node.type === "group" && node.prompt) index2.set(node.prompt.id, dedupe([...acc, ...node.prompt.tags ?? []]));
|
|
201045
201059
|
if (node.type === "group") {
|
|
201046
201060
|
for (const child2 of node.children) visit(child2, acc);
|
|
201047
201061
|
} else {
|
|
@@ -201105,6 +201119,8 @@ var Engine = class _Engine {
|
|
|
201105
201119
|
/** The run's ordered stream: every flow's events, each naming its flow. Empty and
|
|
201106
201120
|
* unwritten unless `options.log` asked for it. */
|
|
201107
201121
|
engineLog = [];
|
|
201122
|
+
/** The next engine-log `seq`: its own counter, since the log's length restarts after a clear. */
|
|
201123
|
+
engineSeq = 0;
|
|
201108
201124
|
engineTraceHandlers = /* @__PURE__ */ new Set();
|
|
201109
201125
|
constructor(bundle, options = {}) {
|
|
201110
201126
|
this.creationOptions = options;
|
|
@@ -201202,6 +201218,7 @@ var Engine = class _Engine {
|
|
|
201202
201218
|
journal: null,
|
|
201203
201219
|
customRng: options.rng,
|
|
201204
201220
|
onDryChoice: options.onDryChoice,
|
|
201221
|
+
onError: options.onError ?? ((e) => console.warn(`Patterplay: ${e.kind} on ${e.node} in flow '${e.flow}' failed, played through: ${e.message}`)),
|
|
201205
201222
|
replayPromptOnChoose: options.replayPromptOnChoose ?? false,
|
|
201206
201223
|
captionsOn: options.closedCaptions ?? true,
|
|
201207
201224
|
// captions shown by default (full text)
|
|
@@ -201395,10 +201412,10 @@ var Engine = class _Engine {
|
|
|
201395
201412
|
*/
|
|
201396
201413
|
resolveOpenAddress(scene, block) {
|
|
201397
201414
|
if (scene !== void 0) {
|
|
201398
|
-
const sceneId = this.
|
|
201399
|
-
if (
|
|
201415
|
+
const sceneId = resolveScene(this.host, scene);
|
|
201416
|
+
if (sceneId === void 0) throw new Error(`unknown scene: ${scene}`);
|
|
201400
201417
|
if (block === void 0) return { sceneId };
|
|
201401
|
-
const blockId =
|
|
201418
|
+
const blockId = resolveBlock(this.host, sceneId, block);
|
|
201402
201419
|
if (blockId === void 0) throw new Error(`unknown block: ${block}`);
|
|
201403
201420
|
return { sceneId, blockId };
|
|
201404
201421
|
}
|
|
@@ -201409,21 +201426,19 @@ var Engine = class _Engine {
|
|
|
201409
201426
|
if (Object.keys(this.host.bundle.scenes).length === 0) throw new Error("no scenes in bundle");
|
|
201410
201427
|
return {};
|
|
201411
201428
|
}
|
|
201412
|
-
/** Resolve a scene reference
|
|
201429
|
+
/** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
|
|
201430
|
+
* unknown one passes through, for the caller to find nothing. */
|
|
201413
201431
|
resolveSceneRef(ref) {
|
|
201414
201432
|
if (ref == null) return void 0;
|
|
201415
|
-
|
|
201416
|
-
return this.sceneGameIdToId.get(ref) ?? ref;
|
|
201433
|
+
return resolveScene(this.host, ref) ?? ref;
|
|
201417
201434
|
}
|
|
201418
|
-
/** Resolve a block reference
|
|
201435
|
+
/** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
|
|
201436
|
+
* scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
|
|
201437
|
+
* since a block's gameId is only unique within its scene. */
|
|
201419
201438
|
resolveBlockRef(sceneId, ref) {
|
|
201420
201439
|
if (ref == null) return void 0;
|
|
201421
|
-
if (
|
|
201422
|
-
|
|
201423
|
-
const id = this.blockGameIdToId.get(sceneId)?.get(ref);
|
|
201424
|
-
if (id) return id;
|
|
201425
|
-
}
|
|
201426
|
-
return ref;
|
|
201440
|
+
if (sceneId == null) return ref;
|
|
201441
|
+
return resolveBlock(this.host, sceneId, ref);
|
|
201427
201442
|
}
|
|
201428
201443
|
/** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
|
|
201429
201444
|
* The inverse of the resolve helpers - for a host that wants to display / log the address. */
|
|
@@ -201750,7 +201765,7 @@ var Engine = class _Engine {
|
|
|
201750
201765
|
emitEngine(flow, event, scene) {
|
|
201751
201766
|
for (const h of this.engineTraceHandlers) h(flow, event);
|
|
201752
201767
|
if (!this.host.logEnabled) return;
|
|
201753
|
-
this.engineLog.push({ ...event, flow, seq: this.
|
|
201768
|
+
this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...scene ? { scene } : {} });
|
|
201754
201769
|
}
|
|
201755
201770
|
listProperties() {
|
|
201756
201771
|
return this.host.patterSharedDecls.map((d) => ({
|
|
@@ -201816,6 +201831,8 @@ var Engine = class _Engine {
|
|
|
201816
201831
|
this.refuseInCheckpoint("loadGame");
|
|
201817
201832
|
const version2 = save.version;
|
|
201818
201833
|
if (version2 !== 2 && version2 !== SAVE_VERSION) throw new Error(`unsupported save version: ${String(version2)}`);
|
|
201834
|
+
const flows = save.flows;
|
|
201835
|
+
if (typeof flows !== "object" || flows === null || Array.isArray(flows)) throw new Error("malformed save: no flows");
|
|
201819
201836
|
this.assertExternalScopes();
|
|
201820
201837
|
const reg = this.host.registry;
|
|
201821
201838
|
for (const [id, flow] of this.flowsById) {
|
|
@@ -201863,10 +201880,13 @@ var Flow = class {
|
|
|
201863
201880
|
activeSnippet = null;
|
|
201864
201881
|
beatIndex = 0;
|
|
201865
201882
|
pendingChoice = null;
|
|
201866
|
-
/** When `replayPromptOnChoose`, the chosen option's prompt
|
|
201867
|
-
|
|
201868
|
-
|
|
201869
|
-
*
|
|
201883
|
+
/** When `replayPromptOnChoose`, the chosen option's AUTHORED prompt to deliver before its content: its
|
|
201884
|
+
* beat (id, gameData, tags) and the prompt exactly as the choice showed it (text and speaker fields).
|
|
201885
|
+
* `shown` is missing only after loading a save written before saves carried it: the beat is then
|
|
201886
|
+
* resolved when it is delivered, as it always was. */
|
|
201887
|
+
pendingPrompt = null;
|
|
201888
|
+
/** The chosen option that owns `pendingPrompt`, so a save taken between choose() and the next
|
|
201889
|
+
* advance() can find the prompt beat again on load (it isn't otherwise reachable by id). */
|
|
201870
201890
|
pendingPromptOwnerId = null;
|
|
201871
201891
|
selectors = /* @__PURE__ */ new Map();
|
|
201872
201892
|
/** Per-node entry counts for this flow (node id -> times entered). */
|
|
@@ -202028,7 +202048,7 @@ var Flow = class {
|
|
|
202028
202048
|
if (scene === "END") {
|
|
202029
202049
|
this.started = true;
|
|
202030
202050
|
this.pendingChoice = null;
|
|
202031
|
-
this.
|
|
202051
|
+
this.pendingPrompt = null;
|
|
202032
202052
|
this.pendingPromptOwnerId = null;
|
|
202033
202053
|
this.activeSnippet = null;
|
|
202034
202054
|
this.beatIndex = 0;
|
|
@@ -202036,11 +202056,11 @@ var Flow = class {
|
|
|
202036
202056
|
this.stack = [];
|
|
202037
202057
|
return true;
|
|
202038
202058
|
}
|
|
202039
|
-
const sceneId =
|
|
202059
|
+
const sceneId = resolveScene(this.host, scene);
|
|
202040
202060
|
if (sceneId === void 0) return false;
|
|
202041
202061
|
let blockId;
|
|
202042
202062
|
if (block !== void 0) {
|
|
202043
|
-
blockId =
|
|
202063
|
+
blockId = resolveBlock(this.host, sceneId, block);
|
|
202044
202064
|
if (blockId === void 0) return false;
|
|
202045
202065
|
}
|
|
202046
202066
|
if (!this.started) {
|
|
@@ -202048,7 +202068,7 @@ var Flow = class {
|
|
|
202048
202068
|
return true;
|
|
202049
202069
|
}
|
|
202050
202070
|
this.pendingChoice = null;
|
|
202051
|
-
this.
|
|
202071
|
+
this.pendingPrompt = null;
|
|
202052
202072
|
this.pendingPromptOwnerId = null;
|
|
202053
202073
|
this.activeSnippet = null;
|
|
202054
202074
|
this.beatIndex = 0;
|
|
@@ -202075,7 +202095,7 @@ var Flow = class {
|
|
|
202075
202095
|
this.activeSnippet = null;
|
|
202076
202096
|
this.beatIndex = 0;
|
|
202077
202097
|
this.pendingChoice = null;
|
|
202078
|
-
this.
|
|
202098
|
+
this.pendingPrompt = null;
|
|
202079
202099
|
this.pendingPromptOwnerId = null;
|
|
202080
202100
|
}
|
|
202081
202101
|
/** Inside a checkpoint, the first change to this flow records its cursor and PRNG, so a rollback can
|
|
@@ -202085,10 +202105,10 @@ var Flow = class {
|
|
|
202085
202105
|
const journal = this.host.journal;
|
|
202086
202106
|
if (!journal || journal.flows.has(this)) return;
|
|
202087
202107
|
journal.flows.add(this);
|
|
202088
|
-
const { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice,
|
|
202108
|
+
const { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPrompt, pendingPromptOwnerId } = this;
|
|
202089
202109
|
const stack2 = this.stack.map((f) => ({ ...f }));
|
|
202090
202110
|
journal.undo.push(() => {
|
|
202091
|
-
Object.assign(this, { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice,
|
|
202111
|
+
Object.assign(this, { rngState, started, flowEnded, currentSceneId, activeSnippet, beatIndex, pendingChoice, pendingPrompt, pendingPromptOwnerId, stack: stack2 });
|
|
202092
202112
|
});
|
|
202093
202113
|
}
|
|
202094
202114
|
/** True once the engine has closed this flow (closed, dropped by `reset()`, or replaced by name). */
|
|
@@ -202106,11 +202126,11 @@ var Flow = class {
|
|
|
202106
202126
|
if (this.closed) return { type: "end" };
|
|
202107
202127
|
if (!this.started) throw new Error("flow has not been started");
|
|
202108
202128
|
this.touch();
|
|
202109
|
-
if (this.
|
|
202110
|
-
const
|
|
202111
|
-
this.
|
|
202129
|
+
if (this.pendingPrompt) {
|
|
202130
|
+
const p = this.pendingPrompt;
|
|
202131
|
+
this.pendingPrompt = null;
|
|
202112
202132
|
this.pendingPromptOwnerId = null;
|
|
202113
|
-
return this.beatResult(
|
|
202133
|
+
return p.shown ? this.promptResult(p.beat, p.shown) : this.beatResult(p.beat);
|
|
202114
202134
|
}
|
|
202115
202135
|
this.settle();
|
|
202116
202136
|
if (this.flowEnded) return { type: "end" };
|
|
@@ -202150,7 +202170,7 @@ var Flow = class {
|
|
|
202150
202170
|
if (this.flowEnded || this.pendingChoice) return;
|
|
202151
202171
|
if (this.activeSnippet) {
|
|
202152
202172
|
if (this.beatIndex < (this.activeSnippet.beats?.length ?? 0)) return;
|
|
202153
|
-
this.runEffects(this.activeSnippet.onExit);
|
|
202173
|
+
this.runEffects(this.activeSnippet.onExit, this.activeSnippet.id);
|
|
202154
202174
|
const jump2 = this.activeSnippet.jump;
|
|
202155
202175
|
this.activeSnippet = null;
|
|
202156
202176
|
this.beatIndex = 0;
|
|
@@ -202229,8 +202249,9 @@ var Flow = class {
|
|
|
202229
202249
|
const node = choice.byId.get(id);
|
|
202230
202250
|
this.emit({ type: "chose", group: choice.groupId, option: id });
|
|
202231
202251
|
this.pendingChoice = null;
|
|
202232
|
-
|
|
202233
|
-
this.
|
|
202252
|
+
const authored = this.host.replayPromptOnChoose ? this.authoredPromptOf(node) : void 0;
|
|
202253
|
+
this.pendingPrompt = authored && option.prompt ? { beat: authored, shown: { ...option.prompt } } : null;
|
|
202254
|
+
this.pendingPromptOwnerId = this.pendingPrompt ? node.id : null;
|
|
202234
202255
|
this.enterChild(node);
|
|
202235
202256
|
}
|
|
202236
202257
|
isEnded() {
|
|
@@ -202296,6 +202317,8 @@ var Flow = class {
|
|
|
202296
202317
|
beatIndex: this.beatIndex,
|
|
202297
202318
|
pendingChoice: this.pendingChoice ? { groupId: this.pendingChoice.groupId, options: this.pendingChoice.options.map((o) => ({ ...o })) } : null,
|
|
202298
202319
|
pendingPromptOwnerId: this.pendingPromptOwnerId,
|
|
202320
|
+
// The prompt still to be replayed, as the choice showed it: present only in the choose -> advance window.
|
|
202321
|
+
...this.pendingPrompt?.shown ? { pendingPrompt: savedPrompt(this.pendingPrompt.shown) } : {},
|
|
202299
202322
|
selectors: serialiseSelectors(this.selectors)
|
|
202300
202323
|
}
|
|
202301
202324
|
};
|
|
@@ -202346,12 +202369,13 @@ var Flow = class {
|
|
|
202346
202369
|
}
|
|
202347
202370
|
if (options.length > 0) this.pendingChoice = { groupId: c2.pendingChoice.groupId, options, byId };
|
|
202348
202371
|
}
|
|
202349
|
-
this.
|
|
202372
|
+
this.pendingPrompt = null;
|
|
202350
202373
|
this.pendingPromptOwnerId = c2.pendingPromptOwnerId ?? null;
|
|
202351
202374
|
if (this.pendingPromptOwnerId) {
|
|
202352
202375
|
const owner = this.host.nodeIndex.get(this.pendingPromptOwnerId);
|
|
202353
|
-
|
|
202354
|
-
if (
|
|
202376
|
+
const beat = owner ? this.authoredPromptOf(owner) : void 0;
|
|
202377
|
+
if (beat) this.pendingPrompt = { beat, ...c2.pendingPrompt ? { shown: { ...c2.pendingPrompt } } : {} };
|
|
202378
|
+
else this.pendingPromptOwnerId = null;
|
|
202355
202379
|
}
|
|
202356
202380
|
}
|
|
202357
202381
|
// -- Scene / block / node entry ------------------------------------------
|
|
@@ -202362,7 +202386,7 @@ var Flow = class {
|
|
|
202362
202386
|
this.currentSceneId = sceneId;
|
|
202363
202387
|
this.enter(sceneId);
|
|
202364
202388
|
this.seedScene(scene);
|
|
202365
|
-
this.runEffects(scene.onEntry);
|
|
202389
|
+
this.runEffects(scene.onEntry, scene.id);
|
|
202366
202390
|
}
|
|
202367
202391
|
/**
|
|
202368
202392
|
* Play one child of the active run. A snippet begins delivering. A group is
|
|
@@ -202398,7 +202422,7 @@ var Flow = class {
|
|
|
202398
202422
|
return void 0;
|
|
202399
202423
|
}
|
|
202400
202424
|
beginSnippet(snippet) {
|
|
202401
|
-
this.runEffects(snippet.onEnter);
|
|
202425
|
+
this.runEffects(snippet.onEnter, snippet.id);
|
|
202402
202426
|
this.activeSnippet = snippet;
|
|
202403
202427
|
this.beatIndex = 0;
|
|
202404
202428
|
}
|
|
@@ -202418,7 +202442,7 @@ var Flow = class {
|
|
|
202418
202442
|
options.push({ id: child2.id, prompt: this.promptFor(child2), eligible, gameData: child2.gameData });
|
|
202419
202443
|
byId.set(child2.id, child2);
|
|
202420
202444
|
}
|
|
202421
|
-
if (options.
|
|
202445
|
+
if (options.some((o) => o.eligible)) {
|
|
202422
202446
|
this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
|
|
202423
202447
|
this.pendingChoice = { groupId: group.id, options, byId };
|
|
202424
202448
|
return;
|
|
@@ -202476,7 +202500,7 @@ var Flow = class {
|
|
|
202476
202500
|
// -- Selectors ------------------------------------------------------------
|
|
202477
202501
|
selectChild(group) {
|
|
202478
202502
|
const verdicts = group.children.map((c2) => ({ id: c2.id, eligible: this.eligible(c2) }));
|
|
202479
|
-
const eligible = group.children.filter((
|
|
202503
|
+
const eligible = group.children.filter((_2, i) => verdicts[i].eligible);
|
|
202480
202504
|
const order = group.options?.order ?? "sequential";
|
|
202481
202505
|
const exhaust = group.options?.exhaust ?? "once";
|
|
202482
202506
|
const trace = (picked) => {
|
|
@@ -202523,8 +202547,9 @@ var Flow = class {
|
|
|
202523
202547
|
const len = eligible.length;
|
|
202524
202548
|
const stick = exhaust === "stick";
|
|
202525
202549
|
const fill = () => (stick ? eligible.slice(0, len - 1) : eligible).map((c2) => c2.id);
|
|
202550
|
+
const eligibleIds = new Set(eligible.map((c2) => c2.id));
|
|
202526
202551
|
if (st.bag === void 0) st.bag = fill();
|
|
202527
|
-
if (st.bag.
|
|
202552
|
+
if (!st.bag.some((id2) => eligibleIds.has(id2))) {
|
|
202528
202553
|
if (exhaust === "once") return null;
|
|
202529
202554
|
if (stick) {
|
|
202530
202555
|
const last2 = eligible[len - 1];
|
|
@@ -202532,13 +202557,14 @@ var Flow = class {
|
|
|
202532
202557
|
return last2;
|
|
202533
202558
|
}
|
|
202534
202559
|
st.bag = fill();
|
|
202560
|
+
if (st.bag.length === 0) return null;
|
|
202535
202561
|
}
|
|
202536
|
-
const pool = st.bag;
|
|
202562
|
+
const pool = st.bag.filter((id2) => eligibleIds.has(id2));
|
|
202537
202563
|
const p = st.last !== void 0 && pool.length > 1 ? pool.indexOf(st.last) : -1;
|
|
202538
202564
|
let i = Math.floor(this.rng() * (p >= 0 ? pool.length - 1 : pool.length));
|
|
202539
202565
|
if (p >= 0 && i >= p) i++;
|
|
202540
202566
|
const id = pool[i];
|
|
202541
|
-
|
|
202567
|
+
st.bag.splice(st.bag.indexOf(id), 1);
|
|
202542
202568
|
st.last = id;
|
|
202543
202569
|
return eligible.find((c2) => c2.id === id);
|
|
202544
202570
|
}
|
|
@@ -202587,7 +202613,7 @@ var Flow = class {
|
|
|
202587
202613
|
/** A child's Best-match score against the current state: 0 when it has no condition (the filler
|
|
202588
202614
|
* tier), else the specificity of its (already-passing) condition. */
|
|
202589
202615
|
specScore(node) {
|
|
202590
|
-
return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true) : 0;
|
|
202616
|
+
return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true, node) : 0;
|
|
202591
202617
|
}
|
|
202592
202618
|
/**
|
|
202593
202619
|
* The **matched-specificity** metric (parity contract): how many atomic constraints are actively
|
|
@@ -202597,8 +202623,15 @@ var Flow = class {
|
|
|
202597
202623
|
* to hold?" (true at the root). Only `and`/`or`/`not`/`check_flags` are structural; every other
|
|
202598
202624
|
* node (comparisons, scoped vars, literals, other calls) is an atom, evaluated whole.
|
|
202599
202625
|
*/
|
|
202600
|
-
matchedSpec(node, want) {
|
|
202601
|
-
const evalTruthy = (n) =>
|
|
202626
|
+
matchedSpec(node, want, owner) {
|
|
202627
|
+
const evalTruthy = (n) => {
|
|
202628
|
+
try {
|
|
202629
|
+
return truthy(evaluate(n, this.context(), patterDialect));
|
|
202630
|
+
} catch (err) {
|
|
202631
|
+
this.reportError("best-match", owner.id, owner.condition, err);
|
|
202632
|
+
return false;
|
|
202633
|
+
}
|
|
202634
|
+
};
|
|
202602
202635
|
return matchedSpecificity(node, evalTruthy, { want });
|
|
202603
202636
|
}
|
|
202604
202637
|
/** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
|
|
@@ -202630,17 +202663,36 @@ var Flow = class {
|
|
|
202630
202663
|
return st;
|
|
202631
202664
|
}
|
|
202632
202665
|
// -- Effects + expressions ------------------------------------------------
|
|
202633
|
-
|
|
202666
|
+
/** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
|
|
202667
|
+
runEffects(effects, owner) {
|
|
202634
202668
|
for (const e of effects ?? []) {
|
|
202635
|
-
|
|
202636
|
-
|
|
202637
|
-
|
|
202638
|
-
|
|
202669
|
+
let value;
|
|
202670
|
+
try {
|
|
202671
|
+
value = this.evalExpr(e.value);
|
|
202672
|
+
const prev = this.host.logEnabled ? this.getProperty(e.target) : void 0;
|
|
202673
|
+
this.writeProperty(e.target, value, false);
|
|
202674
|
+
this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
|
|
202675
|
+
} catch (err) {
|
|
202676
|
+
this.reportError("effect", owner, e.value, err);
|
|
202677
|
+
}
|
|
202639
202678
|
}
|
|
202640
202679
|
}
|
|
202680
|
+
/** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
|
|
202641
202681
|
eligible(node) {
|
|
202642
202682
|
if (!node.condition) return true;
|
|
202643
|
-
|
|
202683
|
+
try {
|
|
202684
|
+
return truthy(this.evalExpr(node.condition));
|
|
202685
|
+
} catch (err) {
|
|
202686
|
+
this.reportError("condition", node.id, node.condition, err);
|
|
202687
|
+
return false;
|
|
202688
|
+
}
|
|
202689
|
+
}
|
|
202690
|
+
/** Report a content error the engine is playing through: to the host's onError, and to the log. */
|
|
202691
|
+
reportError(kind, node, expr, err) {
|
|
202692
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
202693
|
+
const source2 = expr?.src;
|
|
202694
|
+
this.host.onError({ flow: this.id, kind, node, ...source2 ? { source: source2 } : {}, message });
|
|
202695
|
+
this.emit({ type: "diagnostic", kind, node, ...source2 ? { source: source2 } : {}, message });
|
|
202644
202696
|
}
|
|
202645
202697
|
evalExpr(expr) {
|
|
202646
202698
|
return evaluate(this.conditionAst(expr), this.context(), patterDialect);
|
|
@@ -202657,15 +202709,15 @@ var Flow = class {
|
|
|
202657
202709
|
}
|
|
202658
202710
|
/** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
|
|
202659
202711
|
enter(id) {
|
|
202660
|
-
const
|
|
202661
|
-
const ownBefore =
|
|
202662
|
-
|
|
202712
|
+
const own2 = this.visitCounts;
|
|
202713
|
+
const ownBefore = own2.get(id);
|
|
202714
|
+
own2.set(id, (ownBefore ?? 0) + 1);
|
|
202663
202715
|
const shared = this.host.sharedVisits;
|
|
202664
202716
|
const before = shared.get(id);
|
|
202665
202717
|
shared.set(id, (before ?? 0) + 1);
|
|
202666
202718
|
if (this.host.journal) this.host.journal.undo.push(() => {
|
|
202667
|
-
if (ownBefore === void 0)
|
|
202668
|
-
else
|
|
202719
|
+
if (ownBefore === void 0) own2.delete(id);
|
|
202720
|
+
else own2.set(id, ownBefore);
|
|
202669
202721
|
if (before === void 0) shared.delete(id);
|
|
202670
202722
|
else shared.set(id, before);
|
|
202671
202723
|
});
|
|
@@ -202746,6 +202798,27 @@ var Flow = class {
|
|
|
202746
202798
|
const text = this.interpolate(this.resolveString(beat.id));
|
|
202747
202799
|
return beat.kind === "line" ? { kind: "line", text: this.captionLine(text), character: beat.character, characterName: this.resolveCharacterName(beat.character), direction: beat.direction } : { kind: "text", text };
|
|
202748
202800
|
}
|
|
202801
|
+
/** An option's AUTHORED prompt beat: an Option group's own `prompt`. The only prompt a replay speaks. */
|
|
202802
|
+
authoredPromptOf(node) {
|
|
202803
|
+
return node.type === "group" ? node.prompt : void 0;
|
|
202804
|
+
}
|
|
202805
|
+
/** A replayed prompt as a step: the beat's id, gameData, and tags, and the text and speaker fields the
|
|
202806
|
+
* choice showed. */
|
|
202807
|
+
promptResult(beat, shown2) {
|
|
202808
|
+
const tags = this.host.tagIndex.get(beat.id);
|
|
202809
|
+
const withTags = tags && tags.length ? { tags } : {};
|
|
202810
|
+
if (shown2.kind === "text") return { type: "text", id: beat.id, text: shown2.text, gameData: beat.gameData, ...withTags };
|
|
202811
|
+
return {
|
|
202812
|
+
type: "line",
|
|
202813
|
+
id: beat.id,
|
|
202814
|
+
text: shown2.text,
|
|
202815
|
+
character: shown2.character,
|
|
202816
|
+
characterName: shown2.characterName,
|
|
202817
|
+
direction: shown2.direction,
|
|
202818
|
+
gameData: beat.gameData,
|
|
202819
|
+
...withTags
|
|
202820
|
+
};
|
|
202821
|
+
}
|
|
202749
202822
|
/** The prompt BEAT of an option: the Option group's `prompt`, else (tolerance) its first content line. */
|
|
202750
202823
|
promptBeatOf(node) {
|
|
202751
202824
|
if (node.type === "group" && node.prompt) return node.prompt;
|
|
@@ -202815,23 +202888,23 @@ var Flow = class {
|
|
|
202815
202888
|
if (!this.sceneBags.has(s)) {
|
|
202816
202889
|
const bag = new PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
202817
202890
|
const key2 = keys.flowScene(this.id, s);
|
|
202818
|
-
this.host.registry
|
|
202891
|
+
const claimed = mountClaims(this.host.registry, key2, bag, journal !== null);
|
|
202819
202892
|
this.registered.add(key2);
|
|
202820
202893
|
this.sceneBags.set(s, bag);
|
|
202821
202894
|
if (journal) journal.undo.push(() => {
|
|
202822
202895
|
if (this.sceneBags.get(s) !== bag) return;
|
|
202823
|
-
this.host.registry.remove(key2);
|
|
202896
|
+
this.host.registry.remove(key2, claimed ? { keep: true } : void 0);
|
|
202824
202897
|
this.registered.delete(key2);
|
|
202825
202898
|
this.sceneBags.delete(s);
|
|
202826
202899
|
});
|
|
202827
202900
|
}
|
|
202828
202901
|
if (!this.host.stageBags.has(s)) {
|
|
202829
202902
|
const bag = new PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
|
|
202830
|
-
this.host.registry
|
|
202903
|
+
const claimed = mountClaims(this.host.registry, keys.stage(s), bag, journal !== null);
|
|
202831
202904
|
this.host.stageBags.set(s, bag);
|
|
202832
202905
|
if (journal) journal.undo.push(() => {
|
|
202833
202906
|
if (this.host.stageBags.get(s) !== bag) return;
|
|
202834
|
-
this.host.registry.remove(keys.stage(s));
|
|
202907
|
+
this.host.registry.remove(keys.stage(s), claimed ? { keep: true } : void 0);
|
|
202835
202908
|
this.host.stageBags.delete(s);
|
|
202836
202909
|
});
|
|
202837
202910
|
}
|
|
@@ -202869,6 +202942,13 @@ function collectCast(nodes, out) {
|
|
|
202869
202942
|
function gameDataField(gameData) {
|
|
202870
202943
|
return gameData && Object.keys(gameData).length ? { gameData } : {};
|
|
202871
202944
|
}
|
|
202945
|
+
function savedPrompt(p) {
|
|
202946
|
+
const out = { kind: p.kind, text: p.text };
|
|
202947
|
+
if (p.character !== void 0) out.character = p.character;
|
|
202948
|
+
if (p.characterName !== void 0) out.characterName = p.characterName;
|
|
202949
|
+
if (p.direction !== void 0) out.direction = p.direction;
|
|
202950
|
+
return out;
|
|
202951
|
+
}
|
|
202872
202952
|
function serialiseSelectors(map2) {
|
|
202873
202953
|
const out = {};
|
|
202874
202954
|
for (const [id, st] of map2) {
|
|
@@ -202891,6 +202971,11 @@ function deserialiseSelectors(rec) {
|
|
|
202891
202971
|
}
|
|
202892
202972
|
return map2;
|
|
202893
202973
|
}
|
|
202974
|
+
function mountClaims(registry, key2, bag, ask) {
|
|
202975
|
+
const before = ask ? JSON.stringify(bag.save()) : "";
|
|
202976
|
+
registry.mountOwned(key2, bag, { owner: OWNER });
|
|
202977
|
+
return ask && JSON.stringify(bag.save()) !== before;
|
|
202978
|
+
}
|
|
202894
202979
|
function toDecl(decl) {
|
|
202895
202980
|
return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };
|
|
202896
202981
|
}
|
|
@@ -202902,6 +202987,12 @@ function selfBackedDecl(decl, scopeWritable) {
|
|
|
202902
202987
|
const writable = decl.writable ?? scopeWritable;
|
|
202903
202988
|
return writable === void 0 ? d : { ...d, writable };
|
|
202904
202989
|
}
|
|
202990
|
+
function resolveScene(host2, ref) {
|
|
202991
|
+
return host2.sceneGameIdToId.get(ref) ?? (host2.bundle.scenes[ref] ? ref : void 0);
|
|
202992
|
+
}
|
|
202993
|
+
function resolveBlock(host2, sceneId, ref) {
|
|
202994
|
+
return host2.blockGameIdToId.get(sceneId)?.get(ref) ?? (host2.blockIndex.get(ref)?.sceneId === sceneId ? ref : void 0);
|
|
202995
|
+
}
|
|
202905
202996
|
function splitHostRef(host2, ref) {
|
|
202906
202997
|
if (host2.refSplitRevision !== host2.registry.revision) {
|
|
202907
202998
|
host2.refSplitCache.clear();
|
|
@@ -202984,7 +203075,7 @@ function runPlay(loaded, opts = {}) {
|
|
|
202984
203075
|
const bundle = exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales, gameScopes: loaded.gameScopes?.merged });
|
|
202985
203076
|
const events = [];
|
|
202986
203077
|
const registry = previewRegistry(loaded.gameScopes, bundle);
|
|
202987
|
-
const engine = new Engine(bundle, { seed: opts.seed, ...registry ? { registry } : {} });
|
|
203078
|
+
const engine = new Engine(bundle, { seed: opts.seed, onError: (error) => events.push({ type: "error", error }), ...registry ? { registry } : {} });
|
|
202988
203079
|
const start = resolveStart(loaded, opts);
|
|
202989
203080
|
const flow = engine.openFlow("main", { scene: start.scene, block: start.block });
|
|
202990
203081
|
const scripted = [...opts.choices ?? []];
|
|
@@ -203016,6 +203107,11 @@ function renderPlay(result) {
|
|
|
203016
203107
|
case "gameEvent":
|
|
203017
203108
|
out.push(` (game event ${JSON.stringify(e.gameData ?? {})})`);
|
|
203018
203109
|
break;
|
|
203110
|
+
case "error": {
|
|
203111
|
+
const { kind, node, source: source2, message } = e.error;
|
|
203112
|
+
out.push(` ! ${kind} on '${node}'${source2 ? ` (${source2})` : ""} failed, played through: ${message}`);
|
|
203113
|
+
break;
|
|
203114
|
+
}
|
|
203019
203115
|
case "choice":
|
|
203020
203116
|
for (const o of e.options) out.push(` ${o.eligible ? "[ ]" : "[x]"} ${o.prompt?.text ?? "(no label)"} (${o.id})`);
|
|
203021
203117
|
if (e.picked !== void 0) out.push(` > ${e.picked}`);
|
|
@@ -203236,9 +203332,12 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
203236
203332
|
const order = [];
|
|
203237
203333
|
const meta = /* @__PURE__ */ new Map();
|
|
203238
203334
|
const choiceScene = /* @__PURE__ */ new Map();
|
|
203335
|
+
const nodeScene = /* @__PURE__ */ new Map();
|
|
203239
203336
|
for (const scene of loaded.scenes) {
|
|
203337
|
+
nodeScene.set(scene.id, scene.id);
|
|
203240
203338
|
for (const block of scene.blocks) {
|
|
203241
203339
|
walkNodes(block.children, (node) => {
|
|
203340
|
+
nodeScene.set(node.id, scene.id);
|
|
203242
203341
|
if (node.type === "group") {
|
|
203243
203342
|
if (node.selector === "choice") choiceScene.set(node.id, scene.id);
|
|
203244
203343
|
return;
|
|
@@ -203259,6 +203358,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
203259
203358
|
const hitCount = new Map(order.map((id) => [id, 0]));
|
|
203260
203359
|
const reachedRuns = new Map(order.map((id) => [id, 0]));
|
|
203261
203360
|
const dryRuns = /* @__PURE__ */ new Map();
|
|
203361
|
+
const errorRuns = /* @__PURE__ */ new Map();
|
|
203262
203362
|
const termination = { ended: 0, capped: 0, stalled: 0, evalError: 0 };
|
|
203263
203363
|
const bundle = exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales, gameScopes: loaded.gameScopes?.merged });
|
|
203264
203364
|
const rng2 = mulberry32(seed);
|
|
@@ -203279,10 +203379,15 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
203279
203379
|
break;
|
|
203280
203380
|
}
|
|
203281
203381
|
const dryThisRun = /* @__PURE__ */ new Set();
|
|
203382
|
+
const errorsThisRun = /* @__PURE__ */ new Map();
|
|
203282
203383
|
const registry = previewRegistry(loaded.gameScopes, bundle);
|
|
203283
203384
|
const engine = new Engine(bundle, {
|
|
203284
203385
|
seed: Math.floor(rng2() * 4294967296),
|
|
203285
203386
|
onDryChoice: (groupId) => dryThisRun.add(groupId),
|
|
203387
|
+
onError: (e) => {
|
|
203388
|
+
const key2 = `${e.kind}|${e.node}|${e.message}`;
|
|
203389
|
+
if (!errorsThisRun.has(key2)) errorsThisRun.set(key2, { kind: e.kind, node: e.node, ...e.source ? { source: e.source } : {}, message: e.message });
|
|
203390
|
+
},
|
|
203286
203391
|
...registry ? { registry } : {}
|
|
203287
203392
|
});
|
|
203288
203393
|
for (const d of initialDrivers) engine.setProperty(d.ref, pick(d.values));
|
|
@@ -203318,6 +203423,11 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
203318
203423
|
}
|
|
203319
203424
|
for (const id of seenThisRun) reachedRuns.set(id, reachedRuns.get(id) + 1);
|
|
203320
203425
|
for (const id of dryThisRun) dryRuns.set(id, (dryRuns.get(id) ?? 0) + 1);
|
|
203426
|
+
for (const [key2, e] of errorsThisRun) {
|
|
203427
|
+
const seen = errorRuns.get(key2);
|
|
203428
|
+
if (seen) seen.runs++;
|
|
203429
|
+
else errorRuns.set(key2, { ...e, scene: nodeScene.get(e.node) ?? "", runs: 1 });
|
|
203430
|
+
}
|
|
203321
203431
|
termination[term]++;
|
|
203322
203432
|
executed++;
|
|
203323
203433
|
if ((run2 & 255) === 0) hooks.onProgress?.(executed, runs);
|
|
@@ -203370,6 +203480,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
|
|
|
203370
203480
|
drivers,
|
|
203371
203481
|
unwrittenInputs: [...unwrittenInputs].sort(),
|
|
203372
203482
|
dryChoices,
|
|
203483
|
+
contentErrors: [...errorRuns.values()].sort((a, b) => b.runs - a.runs || a.node.localeCompare(b.node)),
|
|
203373
203484
|
cancelled
|
|
203374
203485
|
};
|
|
203375
203486
|
}
|
|
@@ -203403,6 +203514,13 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
|
|
|
203403
203514
|
out.push(` \u203C ${String(d.runs).padStart(6)} run(s) ${sceneName(d.scene)} choice '${d.id}'`);
|
|
203404
203515
|
}
|
|
203405
203516
|
}
|
|
203517
|
+
if (report.contentErrors.length) {
|
|
203518
|
+
out.push("");
|
|
203519
|
+
out.push(`content errors (a condition or effect failed and play went on without it - fix the expression): ${report.contentErrors.length}`);
|
|
203520
|
+
for (const e of report.contentErrors) {
|
|
203521
|
+
out.push(` \u203C ${String(e.runs).padStart(6)} run(s) ${sceneName(e.scene)} ${e.kind} on '${e.node}'${e.source ? ` (${e.source})` : ""}: ${e.message}`);
|
|
203522
|
+
}
|
|
203523
|
+
}
|
|
203406
203524
|
if (!report.beats.length) return out;
|
|
203407
203525
|
const clip = (s, n = 48) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
|
|
203408
203526
|
const scenes = new Set(report.beats.map((b) => b.scene));
|
|
@@ -203848,10 +203966,10 @@ function resolveDocumentation(loaded, channel) {
|
|
|
203848
203966
|
const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
|
|
203849
203967
|
const allowed = classesForChannel(classes, channel);
|
|
203850
203968
|
const docsOf = mergeAuthoring2(loaded).documentation;
|
|
203851
|
-
const
|
|
203969
|
+
const own2 = (id) => (docsOf.get(id) ?? []).filter((l2) => l2.type !== void 0 && allowed.has(l2.type));
|
|
203852
203970
|
const out = /* @__PURE__ */ new Map();
|
|
203853
203971
|
const visit = (id, inherited) => {
|
|
203854
|
-
const mine =
|
|
203972
|
+
const mine = own2(id);
|
|
203855
203973
|
if (inherited.length === 0 && mine.length === 0) return inherited;
|
|
203856
203974
|
const resolved = [...inherited, ...mine];
|
|
203857
203975
|
out.set(id, resolved);
|
|
@@ -204247,14 +204365,14 @@ function runVoiceScript(loaded, opts = {}) {
|
|
|
204247
204365
|
if (beat.kind !== "line" || cutSet.has(beat.id)) continue;
|
|
204248
204366
|
const ws = writingOf.get(beat.id) ?? stub;
|
|
204249
204367
|
if (!everything && recordThreshold !== -1 && (writingIndex.get(ws) ?? 0) < recordThreshold) continue;
|
|
204250
|
-
const
|
|
204368
|
+
const own2 = ownVo(beat.id);
|
|
204251
204369
|
lines.push({
|
|
204252
204370
|
scope: scope.join(" \u203A "),
|
|
204253
204371
|
id: beat.id,
|
|
204254
204372
|
character: beat.character ?? "",
|
|
204255
204373
|
actor: beat.character ? actorOf.get(beat.character) : void 0,
|
|
204256
204374
|
text: plainVoice(source2[beat.id] ?? ""),
|
|
204257
|
-
comments: leading ? [...ancestorVo, ...
|
|
204375
|
+
comments: leading ? [...ancestorVo, ...own2] : own2,
|
|
204258
204376
|
// first line of the run gets the enclosing context
|
|
204259
204377
|
recordingStatus: recordingOf(beat.id)
|
|
204260
204378
|
});
|
|
@@ -248875,7 +248993,7 @@ function fileStatus(filePaths, options = {}) {
|
|
|
248875
248993
|
// package.json
|
|
248876
248994
|
var package_default = {
|
|
248877
248995
|
name: "@patterkit/cli",
|
|
248878
|
-
version: "0.
|
|
248996
|
+
version: "0.8.0",
|
|
248879
248997
|
description: "The `patter` CLI: validate, format, export, and play a Patter project.",
|
|
248880
248998
|
type: "module",
|
|
248881
248999
|
license: "MIT",
|
|
@@ -248904,8 +249022,8 @@ var package_default = {
|
|
|
248904
249022
|
"build:standalone:others": "node scripts/build-standalone.mjs --targets=linux-x64,linux-arm64,windows-x64"
|
|
248905
249023
|
},
|
|
248906
249024
|
dependencies: {
|
|
248907
|
-
"@patterkit/core": "0.3.
|
|
248908
|
-
"@patterkit/ops": "0.
|
|
249025
|
+
"@patterkit/core": "0.3.2",
|
|
249026
|
+
"@patterkit/ops": "0.13.0",
|
|
248909
249027
|
"@wildwinter/simple-vc-lib": "^0.5.0"
|
|
248910
249028
|
}
|
|
248911
249029
|
};
|
|
@@ -249241,7 +249359,7 @@ ${count} issue(s)${advisory}`);
|
|
|
249241
249359
|
seed
|
|
249242
249360
|
});
|
|
249243
249361
|
for (const line2 of renderPlay(result)) console.log(line2);
|
|
249244
|
-
return result.outcome === "end" ? 0 : 1;
|
|
249362
|
+
return result.outcome === "end" && !result.events.some((e) => e.type === "error") ? 0 : 1;
|
|
249245
249363
|
}
|
|
249246
249364
|
case "coverage": {
|
|
249247
249365
|
const numbers = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patterkit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "The `patter` CLI: validate, format, export, and play a Patter project.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"build:standalone:others": "node scripts/build-standalone.mjs --targets=linux-x64,linux-arm64,windows-x64"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@patterkit/core": "0.3.
|
|
33
|
-
"@patterkit/ops": "0.
|
|
32
|
+
"@patterkit/core": "0.3.2",
|
|
33
|
+
"@patterkit/ops": "0.13.0",
|
|
34
34
|
"@wildwinter/simple-vc-lib": "^0.5.0"
|
|
35
35
|
}
|
|
36
36
|
}
|