@patterkit/cli 0.7.5 → 0.8.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +249 -119
  2. package/package.json +2 -2
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[word];
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[n.scope];
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[n.name];
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[n.name]) {
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[n.name];
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[node.name]) {
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[node.name];
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[node.name];
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
- values = {};
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.decls.get(n)?.writable === false) throw new Error(`'${name}' is read-only`);
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
- try {
198615
- e.bag.set(name, value, opts?.host ? { host: true } : void 0);
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 own = project.scopeRegistry;
199323
- if (!merged) return { host: own, external: [], notes: [] };
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 own?.scopes ?? []) {
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 ? own : hosted.length ? { version: own?.version ?? 1, scopes: hosted } : void 0;
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 own = bundle.scopeRegistry?.scopes ?? [];
199873
- if (!loaded.gameScopes || !bundle.externalScopes?.length) return own;
199874
- const have = new Set(own.map((s) => s.token));
199875
- return [...own, ...loaded.gameScopes.merged.spec.scopes.filter((s) => s.token !== PATTER_SCOPE && !have.has(s.token))];
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 own = node.condition;
200596
- const branches = own ? disjuncts(own.ast) : [void 0];
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 && own) {
200616
+ if (refuted && reason !== void 0 && own2) {
200604
200617
  issues.push({
200605
200618
  nodeId: node.id,
200606
200619
  field: "condition",
200607
- src: own.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(own, sceneId)]);
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 Be=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})},Te=(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=Be(e,s))||r.enumerable});return n};var Ge=n=>Te(q({},"__esModule",{value:!0}),n);var et={};Ae(et,{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 T(n,t?.want??!0,e,r)}function T(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=T(n.left,e,t,r),i=T(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 T(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??P(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??P(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,G=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 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 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}}},mt=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",B=n=>n.replace(/%/g,"%25").replace(/\\//g,"%2F"),Se=Symbol("ownsRegistry"),y={stage:n=>`patter/scene/${B(n)}`,flow:n=>`patter/flow/${B(n)}/`,flowGlobals:n=>`patter/flow/${B(n)}/patter`,flowScene:(n,e)=>`patter/flow/${B(n)}/scene/${B(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),Pe=Z.filter(g=>!(g.shared??!0)).map(we),Oe=new Set(H.map(g=>g.name.toLowerCase())),x=t.registry??new G,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=>ze(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:Pe,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:P(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?Ye(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;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=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.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,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,pendingPrompt: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.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);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:Je(this.pendingPrompt.shown)}:{},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.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)}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}}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=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,P(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 Je(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 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 ze(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 Ye(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 Qe=(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:Qe(n,e)}}function Xe(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function Ze(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)Ze(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(Xe)}));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 Ge(et);})();\n';
200807
+ var PLAYABLE_RUNTIME_JS = 'var Patterplay=(()=>{var U=Object.defineProperty;var Ve=Object.getOwnPropertyDescriptor;var Le=Object.getOwnPropertyNames;var Me=Object.prototype.hasOwnProperty;var $e=(n,e)=>{for(var t in e)U(n,t,{get:e[t],enumerable:!0})},He=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Le(e))!Me.call(n,s)&&s!==t&&U(n,s,{get:()=>e[s],enumerable:!(r=Ve(e,s))||r.enumerable});return n};var qe=n=>He(U({},"__esModule",{value:!0}),n);var at={};$e(at,{Checkpoint:()=>A,Engine:()=>q,Flow:()=>I,buildTagIndex:()=>H,describeBundle:()=>Ne,effectiveGameData:()=>Ae,gameDataFields:()=>Fe,gameDataValue:()=>ne});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 j(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=W(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=W(i.left,e),l=W(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 le(o,a);case"!=":return!le(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 le(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 W(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 J(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 Ue={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},We=[Ue];function pe(n,e,t){let r=t?.countingCalls??We;return V(n,t?.want??!0,e,r)}function V(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=V(n.left,e,t,r),i=V(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 V(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])=>ge(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 ge(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 Je=n=>n.toLowerCase(),K=1,L=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??Je,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,...ge(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(Ke(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 de(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=de(s);if(i.length===0)continue;let o=new Map;for(let[a,c]of i)o.set(a,{type:c.type,enumValues:c.values,...c.stages!==void 0?{stages:c.stages}:{}});t.set(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:K,scopes:this.save()}}loadFragment(e){if(e.version!==K)throw new Error(`unsupported owned-state fragment version ${e.version} (supported: ${K})`);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 de(n){return n.kind==="foreign"?[...n.decls.entries()]:n.bag.declarations().map(e=>[n.bag.normalise(e.name),e])}function Ke(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 ue=[{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 z={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:fe("check_flags"),eval(n,e){let t=he(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:fe("set_flags"),eval(n,e){let t=[...he(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:$("visits"),eval:(n,e)=>O(e).visits?.(M(n,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:$("seen"),eval:(n,e)=>(O(e).visits?.(M(n,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:$("patter_visits"),eval:(n,e)=>O(e).patterVisits?.(M(n,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:$("patter_seen"),eval:(n,e)=>(O(e).patterVisits?.(M(n,e,"patter_seen"))??0)>0}}},Ct=ue.map(n=>n.token).filter(n=>n!=="patter"&&n!=="scene");function me(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 M(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 $(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 he(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 fe(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 ze=/^@[A-Za-z0-9_.]+$/;function*Ye(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:ze.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 Qe(n){return Array.isArray(n)?n.join(", "):typeof n=="boolean"?n?"true":"false":String(n)}function Xe(n){return n===" "||n===" "||n===`\n`||n==="\\r"||n==="\\f"||n==="\\v"}function Ze(n){let e="",t=!1;for(let r of n){if(Xe(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function Se(n,e,t){if(e.length===0||n.indexOf(e)<0)return n;let r="",s=0,i=!1;for(;s<n.length;){if(n.startsWith(e,s)){let o=n.indexOf(t,s+e.length);if(o>=0){s=o+t.length,i=!0;continue}r+=n.slice(s);break}r+=n[s],s+=1}return i?Ze(r):n}function ye(n,e){if(n.indexOf("{")<0)return n;let t="";for(let r of Ye(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?"":Qe(s)}return t}function C(n,e){for(let t of n){e(t);let r=t.children;r&&C(r,e)}}function et(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||et(n.name)}function Y(n){return`cast:${n}`}var Q={open:"[",close:"]"},ve="SFX";var X=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 H(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 we=new WeakMap,F="Patter",N=n=>n.replace(/%/g,"%25").replace(/\\//g,"%2F"),be=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},q=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,G=>p.set(G.id,G));this.blockGameIdToId.set(g,v)}let se=e.properties??[],_=se.filter(g=>g.shared??!0).map(Ce),Te=se.filter(g=>!(g.shared??!0)).map(Ce),Ge=new Set(_.map(g=>g.name.toLowerCase())),x=t.registry??new L,ie=!t.registry||t[be]===!0,oe=new b(_,{pathPrefix:"@patter."}),ae=[],T=[];try{x.mountOwned("patter",oe,{owner:F}),T.push("patter");let g=new Set;for(let[h,v]of Object.entries(Ie(t))){let m=e.scopeRegistry?.scopes.find(je=>je.token===h),G=(m?.declarations??[]).map(Oe);x.defineForeign(h,v,G,{writable:m?.writable??!0,owner:F}),T.push(h),g.add(h)}for(let h of ie?e.scopeRegistry?.scopes??[]:[]){if(g.has(h.token)||x.has(h.token))continue;let v=(h.declarations??[]).map(m=>rt(m,h.writable));x.defineOwned(h.token,v,{owner:F}),T.push(h.token),ae.push(h.token)}}catch(g){for(let h of T)x.remove(h,{keep:!0});throw g}let ce=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()));ce.set(g,v)}this.host={logEnabled:t.log??!1,tracing: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:H(e),registry:x,ownsRegistry:ie,patterBag:oe,hostScopes:ae,patterSharedDecls:_,patterLocalDecls:Te,patterSharedNames:Ge,sceneSharedNames:ce,sharedVisits:new Map,qualityLadders: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??Q).open,captionClose:(e.closedCaptions??Q).close,captionCharacter:e.closedCaptions?.character||ve,refSplitCache:new Map,refSplitRevision:x.revision}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}get buildId(){return this.host.bundle.content?.hash??""}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){this.refuseInCheckpoint("hotSwap");let t=this.saveGame(),r=o=>(o.setLocale(this.currentLocale),o.setClosedCaptions(this.host.captionsOn),o),s=()=>new n(e,{...this.creationOptions,registry:this.host.registry,[be]: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.begin(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=ee(this.host,e);if(r===void 0)throw new Error(`unknown scene: ${e}`);if(t===void 0)return{sceneId:r};let s=te(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 ee(this.host,e)??e}resolveBlockRef(e,t){if(t!=null)return e==null?t:te(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)xe(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 xe(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,...ke(e.gameData),...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...S(t)?{gameId:S(t)}:{},name:t.name,...ke(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[Y(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});for(let r of Object.keys(Ie(this.creationOptions)))t.has(r)&&t.remove(r)}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.host.tracing=!0,()=>{this.engineTraceHandlers.delete(e),this.host.tracing=this.host.logEnabled||this.engineTraceHandlers.size>0}}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}:{}})}getVisitCounts(){return Object.fromEntries(this.host.sharedVisits)}listProperties(){return this.host.patterSharedDecls.map(e=>({name:e.name,path:`@patter.${e.name}`,type:e.type,values:e.values,stages:e.stages,value:this.getProperty(`@${e.name}`),default:P(e),writable:e.writable??!0}))}splitShared(e){let t=Be(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:X,...this.host.ownsRegistry?{registry:this.host.registry.save()}:{},sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:Re(this.host.sharedSelectors),flows:e}}loadGame(e){this.refuseInCheckpoint("loadGame");let t=e.version;if(t!==2&&t!==X)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?nt(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 Pe(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();if(e==="patter")return Z(this.host,"patter",()=>[...this.host.patterSharedDecls,...this.host.patterLocalDecls]).get(r);if(e==="scene"){let s=this.currentSceneId;return s==null?void 0:Z(this.host,`scene/${s}`,()=>this.host.bundle.scenes[s]?.sceneProps).get(r)}return this.registryQualities?.(e,t)??Z(this.host,`host/${e}`,()=>this.host.bundle.scopeRegistry?.scopes.find(s=>s.token===e)?.declarations).get(r)}begin(e,t){if(this.host.journal&&!this.host.journal.opened.has(this))throw new Error("a flow can\'t be started or reset while a checkpoint is open");if(this.releaseBags(!1),this.host.registry.discardParked(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.clearPending(),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.begin(e,t)}start(e,t){this.reset(e,t)}goto(e,t){if(this.closed)return!1;if(this.touch(),e==="END")return this.started=!0,this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!0,this.stack=[],!0;let r=ee(this.host,e);if(r===void 0)return!1;let s;return t!==void 0&&(s=te(this.host,r,t),s===void 0)?!1:this.started?(this.clearPending(),this.activeSnippet=null,this.beatIndex=0,this.flowEnded=!1,this.enterTarget(s??r,"jump"),this.settle(),!0):(this.begin(r,s),!0)}close(){this.releaseBags(!1),this.closed=!0,this.flowEnded=!0,this.stack=[],this.activeSnippet=null,this.beatIndex=0,this.clearPending()}clearPending(){this.pendingChoice=null,this.pendingPrompt=null,this.pendingPromptOwnerId=null}touch(){let e=this.host.journal;if(!e||e.flows.has(this))return;e.flows.add(this);let{rngState:t,started: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.tracing&&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}:{}})}getVisitCounts(){return Object.fromEntries(this.visitCounts)}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.host.tracing&&this.emit({type:"chose",group:t.groupId,option:e}),this.pendingChoice=null;let i=this.host.replayPromptOnChoose?this.authoredPromptOf(s):void 0;this.pendingPrompt=i&&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:tt(this.pendingPrompt.shown)}:{},selectors:Re(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=Pe(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.host.tracing&&this.emit({type:"choice",group:e.id,options:t.map(o=>({id:o.id,eligible:o.eligible}))}),this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=s.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.tracing&&this.emit({type:"dry",group:e.id}),this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(this.host.tracing&&this.emit({type:"jump",to:e,mode:t}),e==="END"){this.flowEnded=!0,this.stack=[];return}let 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.host.tracing&&this.emit({type:"select",group:e.id,selector:e.selector??"default",...e.selector==="sequence"?{order:s,exhaust:i}:{},children:t,picked:c?.id??null}),c);if(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 pe(e,i=>{try{return De(j(i,this.context(),z))}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.tracing?this.getProperty(r.target):void 0;this.writeProperty(r.target,s,!1),this.host.tracing&&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 De(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.host.tracing&&this.emit({type:"diagnostic",kind:e,node:t,...o?{source:o}:{},message:i})}evalExpr(e){return j(this.conditionAst(e),this.context(),z)}conditionAst(e){let t=we.get(e);return t||(t=k(e.ast),we.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=J(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 ye(e,t=>this.getProperty(t))}stripCaptions(e){return Se(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let r=this.interpolate(this.resolveString(t.id));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}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=Y(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){return Be(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=Ee(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=Ee(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 xe(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 ke(n){return n&&Object.keys(n).length?{gameData:n}:{}}function tt(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 Re(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 Pe(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 Ee(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 Ce(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default}}function Ie(n){return{...n.world?{world:n.world}:{},...n.hostScopes}}function Z(n,e,t){let r=n.qualityLadders.get(e);if(!r){r=new Map;for(let s of t()??[]){let i=s.name.toLowerCase();s.type==="quality"&&s.stages&&!r.has(i)&&r.set(i,s.stages)}n.qualityLadders.set(e,r)}return r}function Oe(n){return{name:n.name,type:n.type,values:n.values,stages:n.stages,default:n.default,writable:n.writable}}function rt(n,e){let t=Oe(n),r=n.writable??e;return r===void 0?t:{...t,writable:r}}function ee(n,e){return n.sceneGameIdToId.get(e)??(n.bundle.scenes[e]?e:void 0)}function te(n,e,t){return n.blockGameIdToId.get(e)?.get(t)??(n.blockIndex.get(t)?.sceneId===e?t:void 0)}function Be(n,e){n.refSplitRevision!==n.registry.revision&&(n.refSplitCache.clear(),n.refSplitRevision=n.registry.revision);let t=n.refSplitCache.get(e);return t||(t=me(e,r=>r==="scene"||n.registry.has(r)||(n.bundle.externalScopes?.includes(r)??!1)),n.refSplitCache.set(e,t)),t}function nt(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 De(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var st=(n,e)=>n.shared??e;function re(n,e){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.default!==void 0?{default:n.default}:{},shared:st(n,e)}}function it(n){return{name:n.name,type:n.type,hasDefault:n.default!==void 0,...n.values?{values:[...n.values]}:{},...n.purpose?{purpose:n.purpose}:{}}}function ot(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 Ne(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)ot(c,e);o.sceneProps?.length&&r.push({gameId:a,properties:o.sceneProps.map(c=>re(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=>re(a,!0))})),i=Object.entries(n.gameDataFields??{}).filter(([,o])=>(o?.length??0)>0).map(([o,a])=>({kind:o,fields:(a??[]).map(it)}));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=>re(o,!0)),scene:r},gameData:i,counts:e}}function Fe(n,e){return n.gameDataFields?.[e]??[]}function ne(n,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:n.find(r=>r.name===t)?.default}function Ae(n,e){let t={};for(let r of n){let s=ne(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 qe(at);})();\n';
200795
200808
 
200796
200809
  // ../ops/src/export-html.ts
200797
200810
  var esc = (s) => s.replace(/[&<>]/g, (c2) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[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 {
@@ -201100,11 +201114,13 @@ var Engine = class _Engine {
201100
201114
  sceneGameIdToId = /* @__PURE__ */ new Map();
201101
201115
  blockGameIdToId = /* @__PURE__ */ new Map();
201102
201116
  /** The options this engine was built with - reused verbatim by `hotSwap` so the replacement
201103
- * engine keeps the same world resolver, custom RNG, and diagnostic hooks. */
201117
+ * engine keeps the same host scopes, custom RNG, and diagnostic hooks. */
201104
201118
  creationOptions;
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;
@@ -201150,12 +201166,12 @@ var Engine = class _Engine {
201150
201166
  registry.mountOwned("patter", patterBag, { owner: OWNER });
201151
201167
  registered.push("patter");
201152
201168
  const hostBound = /* @__PURE__ */ new Set();
201153
- if (options.world) {
201154
- const worldSpec = bundle.scopeRegistry?.scopes.find((s) => s.token === "world");
201155
- const decls = (worldSpec?.declarations ?? []).map(toForeignDecl);
201156
- registry.defineForeign("world", options.world, decls, { writable: worldSpec?.writable ?? true, owner: OWNER });
201157
- registered.push("world");
201158
- hostBound.add("world");
201169
+ for (const [token2, resolver] of Object.entries(boundScopes(options))) {
201170
+ const spec = bundle.scopeRegistry?.scopes.find((s) => s.token === token2);
201171
+ const decls = (spec?.declarations ?? []).map(toForeignDecl);
201172
+ registry.defineForeign(token2, resolver, decls, { writable: spec?.writable ?? true, owner: OWNER });
201173
+ registered.push(token2);
201174
+ hostBound.add(token2);
201159
201175
  }
201160
201176
  for (const spec of ownsRegistry ? bundle.scopeRegistry?.scopes ?? [] : []) {
201161
201177
  if (hostBound.has(spec.token) || registry.has(spec.token)) continue;
@@ -201175,6 +201191,7 @@ var Engine = class _Engine {
201175
201191
  }
201176
201192
  this.host = {
201177
201193
  logEnabled: options.log ?? false,
201194
+ tracing: options.log ?? false,
201178
201195
  emitEngine: (flow, event, scene) => this.emitEngine(flow, event, scene),
201179
201196
  bundle,
201180
201197
  emitIds,
@@ -201197,11 +201214,13 @@ var Engine = class _Engine {
201197
201214
  patterSharedNames,
201198
201215
  sceneSharedNames,
201199
201216
  sharedVisits: /* @__PURE__ */ new Map(),
201217
+ qualityLadders: /* @__PURE__ */ new Map(),
201200
201218
  sharedSelectors: /* @__PURE__ */ new Map(),
201201
201219
  stageBags: /* @__PURE__ */ new Map(),
201202
201220
  journal: null,
201203
201221
  customRng: options.rng,
201204
201222
  onDryChoice: options.onDryChoice,
201223
+ onError: options.onError ?? ((e) => console.warn(`Patterplay: ${e.kind} on ${e.node} in flow '${e.flow}' failed, played through: ${e.message}`)),
201205
201224
  replayPromptOnChoose: options.replayPromptOnChoose ?? false,
201206
201225
  captionsOn: options.closedCaptions ?? true,
201207
201226
  // captions shown by default (full text)
@@ -201222,6 +201241,11 @@ var Engine = class _Engine {
201222
201241
  get isSourceDebug() {
201223
201242
  return this.sourceDebug;
201224
201243
  }
201244
+ /** The bundle's build identity (its content hash), the one a debug link handshakes with, or an empty
201245
+ * string for a bundle compiled without one. */
201246
+ get buildId() {
201247
+ return this.host.bundle.content?.hash ?? "";
201248
+ }
201225
201249
  /**
201226
201250
  * Switch the active locale LIVE - a real game's "language" setting can change mid-session. Subsequent
201227
201251
  * string lookups (new beats, re-resolved character names, `{@ref}` interpolation) render in the new
@@ -201342,7 +201366,7 @@ var Engine = class _Engine {
201342
201366
  });
201343
201367
  }
201344
201368
  this.flowsById.set(id, flow);
201345
- flow.start(sceneId, blockId);
201369
+ flow.begin(sceneId, blockId);
201346
201370
  return flow;
201347
201371
  }
201348
201372
  /**
@@ -201395,10 +201419,10 @@ var Engine = class _Engine {
201395
201419
  */
201396
201420
  resolveOpenAddress(scene, block) {
201397
201421
  if (scene !== void 0) {
201398
- const sceneId = this.resolveSceneRef(scene);
201399
- if (!this.host.bundle.scenes[sceneId]) throw new Error(`unknown scene: ${scene}`);
201422
+ const sceneId = resolveScene(this.host, scene);
201423
+ if (sceneId === void 0) throw new Error(`unknown scene: ${scene}`);
201400
201424
  if (block === void 0) return { sceneId };
201401
- const blockId = this.blockGameIdToId.get(sceneId)?.get(block) ?? (this.host.blockIndex.get(block)?.sceneId === sceneId ? block : void 0);
201425
+ const blockId = resolveBlock(this.host, sceneId, block);
201402
201426
  if (blockId === void 0) throw new Error(`unknown block: ${block}`);
201403
201427
  return { sceneId, blockId };
201404
201428
  }
@@ -201409,21 +201433,19 @@ var Engine = class _Engine {
201409
201433
  if (Object.keys(this.host.bundle.scenes).length === 0) throw new Error("no scenes in bundle");
201410
201434
  return {};
201411
201435
  }
201412
- /** Resolve a scene reference (a gameId address OR an internal id) to its internal id. */
201436
+ /** Resolve a scene reference to its internal id by the one address rule (see resolveScene); an
201437
+ * unknown one passes through, for the caller to find nothing. */
201413
201438
  resolveSceneRef(ref) {
201414
201439
  if (ref == null) return void 0;
201415
- if (this.host.bundle.scenes[ref]) return ref;
201416
- return this.sceneGameIdToId.get(ref) ?? ref;
201440
+ return resolveScene(this.host, ref) ?? ref;
201417
201441
  }
201418
- /** Resolve a block reference (a scene-scoped gameId OR an internal id) to its internal id. */
201442
+ /** Resolve a block reference to its internal id by the one address rule (see resolveBlock): within its
201443
+ * scene, so a block of another scene resolves to nothing. With no scene, only an internal id resolves,
201444
+ * since a block's gameId is only unique within its scene. */
201419
201445
  resolveBlockRef(sceneId, ref) {
201420
201446
  if (ref == null) return void 0;
201421
- if (this.host.blockById.has(ref)) return ref;
201422
- if (sceneId != null) {
201423
- const id = this.blockGameIdToId.get(sceneId)?.get(ref);
201424
- if (id) return id;
201425
- }
201426
- return ref;
201447
+ if (sceneId == null) return ref;
201448
+ return resolveBlock(this.host, sceneId, ref);
201427
201449
  }
201428
201450
  /** The host-facing address (gameId) of a scene / block by internal id, or undefined if unknown.
201429
201451
  * The inverse of the resolve helpers - for a host that wants to display / log the address. */
@@ -201708,7 +201730,7 @@ var Engine = class _Engine {
201708
201730
  for (const s of this.host.stageBags.keys()) reg.remove(keys.stage(s), { keep });
201709
201731
  this.host.stageBags.clear();
201710
201732
  for (const t of ["patter", ...this.host.hostScopes]) if (reg.has(t)) reg.remove(t, { keep });
201711
- if (this.creationOptions.world && reg.has("world")) reg.remove("world");
201733
+ for (const t of Object.keys(boundScopes(this.creationOptions))) if (reg.has(t)) reg.remove(t);
201712
201734
  }
201713
201735
  /** Read a shared (`@patter` / foreign) property by ref. `@scene` refs are rejected (flow-level). */
201714
201736
  getProperty(ref) {
@@ -201745,12 +201767,21 @@ var Engine = class _Engine {
201745
201767
  * retained. Returns its own unsubscribe. */
201746
201768
  onTrace(handler) {
201747
201769
  this.engineTraceHandlers.add(handler);
201748
- return () => this.engineTraceHandlers.delete(handler);
201770
+ this.host.tracing = true;
201771
+ return () => {
201772
+ this.engineTraceHandlers.delete(handler);
201773
+ this.host.tracing = this.host.logEnabled || this.engineTraceHandlers.size > 0;
201774
+ };
201749
201775
  }
201750
201776
  emitEngine(flow, event, scene) {
201751
201777
  for (const h of this.engineTraceHandlers) h(flow, event);
201752
201778
  if (!this.host.logEnabled) return;
201753
- this.engineLog.push({ ...event, flow, seq: this.engineLog.length, ...scene ? { scene } : {} });
201779
+ this.engineLog.push({ ...event, flow, seq: this.engineSeq++, ...scene ? { scene } : {} });
201780
+ }
201781
+ /** How many times each node has been entered across every flow, by node id: the shared count. With a
201782
+ * flow's own {@link Flow.getVisitCounts}, every visit count the run keeps, read without a save. */
201783
+ getVisitCounts() {
201784
+ return Object.fromEntries(this.host.sharedVisits);
201754
201785
  }
201755
201786
  listProperties() {
201756
201787
  return this.host.patterSharedDecls.map((d) => ({
@@ -201816,6 +201847,8 @@ var Engine = class _Engine {
201816
201847
  this.refuseInCheckpoint("loadGame");
201817
201848
  const version2 = save.version;
201818
201849
  if (version2 !== 2 && version2 !== SAVE_VERSION) throw new Error(`unsupported save version: ${String(version2)}`);
201850
+ const flows = save.flows;
201851
+ if (typeof flows !== "object" || flows === null || Array.isArray(flows)) throw new Error("malformed save: no flows");
201819
201852
  this.assertExternalScopes();
201820
201853
  const reg = this.host.registry;
201821
201854
  for (const [id, flow] of this.flowsById) {
@@ -201954,19 +201987,21 @@ var Flow = class {
201954
201987
  * lowercase, as the compiler emits references (the selfBackedResolver lesson). */
201955
201988
  stagesFor(scope, name) {
201956
201989
  const key2 = name.toLowerCase();
201957
- const fromDecls = (decls) => decls?.find((d) => d.name.toLowerCase() === key2 && d.type === "quality")?.stages;
201958
201990
  if (scope === "patter") {
201959
- return fromDecls(this.host.patterSharedDecls) ?? fromDecls(this.host.patterLocalDecls);
201991
+ return ladders(this.host, "patter", () => [...this.host.patterSharedDecls, ...this.host.patterLocalDecls]).get(key2);
201960
201992
  }
201961
201993
  if (scope === "scene") {
201962
- const scene = this.currentSceneId != null ? this.host.bundle.scenes[this.currentSceneId] : void 0;
201963
- return fromDecls(scene?.sceneProps);
201994
+ const sceneId = this.currentSceneId;
201995
+ if (sceneId == null) return void 0;
201996
+ return ladders(this.host, `scene/${sceneId}`, () => this.host.bundle.scenes[sceneId]?.sceneProps).get(key2);
201964
201997
  }
201965
- return this.registryQualities?.(scope, name) ?? fromDecls(this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations);
201998
+ return this.registryQualities?.(scope, name) ?? ladders(this.host, `host/${scope}`, () => this.host.bundle.scopeRegistry?.scopes.find((s) => s.token === scope)?.declarations).get(key2);
201966
201999
  }
201967
202000
  // -- Host API -------------------------------------------------------------
201968
- /** Begin this flow at a scene (and optionally a specific block within it). */
201969
- start(sceneId, blockId) {
202001
+ /** Begin this flow at a scene (and optionally a specific block within it). The engine's own entry point:
202002
+ * openFlow and a goto on an unstarted flow begin a flow here. A game calls {@link reset}.
202003
+ * @internal */
202004
+ begin(sceneId, blockId) {
201970
202005
  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");
201971
202006
  this.releaseBags(false);
201972
202007
  this.host.registry.discardParked(keys.flow(this.id));
@@ -201978,7 +202013,7 @@ var Flow = class {
201978
202013
  this.flowEnded = false;
201979
202014
  this.activeSnippet = null;
201980
202015
  this.beatIndex = 0;
201981
- this.pendingChoice = null;
202016
+ this.clearPending();
201982
202017
  this.started = true;
201983
202018
  if (blockId) {
201984
202019
  const loc = this.host.blockIndex.get(blockId);
@@ -202003,10 +202038,15 @@ var Flow = class {
202003
202038
  * Forget everything in this flow and begin again - its per-flow state (not-shared
202004
202039
  * `@patter` globals + `@scene` props), cursor, callstack, selector cursors, and
202005
202040
  * visit counts. Shared state (shared `@patter` / `@scene`, world visit counts) is
202006
- * untouched. A clearer-named alias of `start()`.
202041
+ * untouched. The one public way to begin a flow again (openFlow begins a new one).
202007
202042
  */
202008
202043
  reset(sceneId, blockId) {
202009
- this.start(sceneId, blockId);
202044
+ this.begin(sceneId, blockId);
202045
+ }
202046
+ /** @deprecated Use {@link reset}, the one public name for beginning a flow again on every runtime. `start`
202047
+ * was the same call under a second name, and goes in a later release. */
202048
+ start(sceneId, blockId) {
202049
+ this.reset(sceneId, blockId);
202010
202050
  }
202011
202051
  /**
202012
202052
  * Send this flow's cursor to an ADDRESS, exactly as an authored `go` jump would: the target scene's
@@ -202030,29 +202070,25 @@ var Flow = class {
202030
202070
  this.touch();
202031
202071
  if (scene === "END") {
202032
202072
  this.started = true;
202033
- this.pendingChoice = null;
202034
- this.pendingPrompt = null;
202035
- this.pendingPromptOwnerId = null;
202073
+ this.clearPending();
202036
202074
  this.activeSnippet = null;
202037
202075
  this.beatIndex = 0;
202038
202076
  this.flowEnded = true;
202039
202077
  this.stack = [];
202040
202078
  return true;
202041
202079
  }
202042
- const sceneId = this.host.sceneGameIdToId.get(scene) ?? (this.host.bundle.scenes[scene] ? scene : void 0);
202080
+ const sceneId = resolveScene(this.host, scene);
202043
202081
  if (sceneId === void 0) return false;
202044
202082
  let blockId;
202045
202083
  if (block !== void 0) {
202046
- blockId = this.host.blockGameIdToId.get(sceneId)?.get(block) ?? (this.host.blockIndex.get(block)?.sceneId === sceneId ? block : void 0);
202084
+ blockId = resolveBlock(this.host, sceneId, block);
202047
202085
  if (blockId === void 0) return false;
202048
202086
  }
202049
202087
  if (!this.started) {
202050
- this.start(sceneId, blockId);
202088
+ this.begin(sceneId, blockId);
202051
202089
  return true;
202052
202090
  }
202053
- this.pendingChoice = null;
202054
- this.pendingPrompt = null;
202055
- this.pendingPromptOwnerId = null;
202091
+ this.clearPending();
202056
202092
  this.activeSnippet = null;
202057
202093
  this.beatIndex = 0;
202058
202094
  this.flowEnded = false;
@@ -202077,6 +202113,15 @@ var Flow = class {
202077
202113
  this.stack = [];
202078
202114
  this.activeSnippet = null;
202079
202115
  this.beatIndex = 0;
202116
+ this.clearPending();
202117
+ }
202118
+ /**
202119
+ * Drop everything waiting to be delivered: an open choice, and a chosen option's prompt still to be
202120
+ * replayed. Every move that abandons the flow's place (start and reset, goto, close) does this, so none
202121
+ * leaves a stale prompt behind. start() cleared only the choice, so a reset between choose() and the
202122
+ * next advance() replayed the abandoned run's prompt as the new run's first beat.
202123
+ */
202124
+ clearPending() {
202080
202125
  this.pendingChoice = null;
202081
202126
  this.pendingPrompt = null;
202082
202127
  this.pendingPromptOwnerId = null;
@@ -202153,7 +202198,7 @@ var Flow = class {
202153
202198
  if (this.flowEnded || this.pendingChoice) return;
202154
202199
  if (this.activeSnippet) {
202155
202200
  if (this.beatIndex < (this.activeSnippet.beats?.length ?? 0)) return;
202156
- this.runEffects(this.activeSnippet.onExit);
202201
+ this.runEffects(this.activeSnippet.onExit, this.activeSnippet.id);
202157
202202
  const jump2 = this.activeSnippet.jump;
202158
202203
  this.activeSnippet = null;
202159
202204
  this.beatIndex = 0;
@@ -202173,7 +202218,7 @@ var Flow = class {
202173
202218
  }
202174
202219
  const from = frame.index;
202175
202220
  while (frame.index < children.length && !this.eligible(children[frame.index])) frame.index++;
202176
- if (this.host.logEnabled && frame.index !== from) {
202221
+ if (this.host.tracing && frame.index !== from) {
202177
202222
  this.emit({
202178
202223
  type: "select",
202179
202224
  group: frame.containerId,
@@ -202209,14 +202254,18 @@ var Flow = class {
202209
202254
  clearLog() {
202210
202255
  this.flowLog.length = 0;
202211
202256
  }
202212
- /** Record one decision, on this flow's log and the engine's. Cheap to call with logging
202213
- * off: the entry is never built. */
202257
+ /** Record one decision, on this flow's log and the engine's, and hand it to any `onTrace`. Each call
202258
+ * site checks `host.tracing` first, so with nothing logging or tracing the entry is never built. */
202214
202259
  emit(event) {
202215
202260
  const scene = this.currentSceneId ?? void 0;
202216
202261
  this.host.emitEngine(this.id, event, scene);
202217
202262
  if (!this.host.logEnabled) return;
202218
202263
  this.flowLog.push({ ...event, seq: this.flowSeq++, ...scene ? { scene } : {} });
202219
202264
  }
202265
+ /** How many times this flow has entered each node, by node id. */
202266
+ getVisitCounts() {
202267
+ return Object.fromEntries(this.visitCounts);
202268
+ }
202220
202269
  /** The options of a pending choice (empty when not at a choice point). */
202221
202270
  getChoices() {
202222
202271
  return this.pendingChoice?.options ?? [];
@@ -202230,7 +202279,7 @@ var Flow = class {
202230
202279
  if (!option.eligible) throw new Error(`choice option is not eligible: ${id}`);
202231
202280
  this.touch();
202232
202281
  const node = choice.byId.get(id);
202233
- this.emit({ type: "chose", group: choice.groupId, option: id });
202282
+ if (this.host.tracing) this.emit({ type: "chose", group: choice.groupId, option: id });
202234
202283
  this.pendingChoice = null;
202235
202284
  const authored = this.host.replayPromptOnChoose ? this.authoredPromptOf(node) : void 0;
202236
202285
  this.pendingPrompt = authored && option.prompt ? { beat: authored, shown: { ...option.prompt } } : null;
@@ -202369,7 +202418,7 @@ var Flow = class {
202369
202418
  this.currentSceneId = sceneId;
202370
202419
  this.enter(sceneId);
202371
202420
  this.seedScene(scene);
202372
- this.runEffects(scene.onEntry);
202421
+ this.runEffects(scene.onEntry, scene.id);
202373
202422
  }
202374
202423
  /**
202375
202424
  * Play one child of the active run. A snippet begins delivering. A group is
@@ -202405,7 +202454,7 @@ var Flow = class {
202405
202454
  return void 0;
202406
202455
  }
202407
202456
  beginSnippet(snippet) {
202408
- this.runEffects(snippet.onEnter);
202457
+ this.runEffects(snippet.onEnter, snippet.id);
202409
202458
  this.activeSnippet = snippet;
202410
202459
  this.beatIndex = 0;
202411
202460
  }
@@ -202425,8 +202474,8 @@ var Flow = class {
202425
202474
  options.push({ id: child2.id, prompt: this.promptFor(child2), eligible, gameData: child2.gameData });
202426
202475
  byId.set(child2.id, child2);
202427
202476
  }
202428
- if (options.length > 0) {
202429
- this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
202477
+ if (options.some((o) => o.eligible)) {
202478
+ if (this.host.tracing) this.emit({ type: "choice", group: group.id, options: options.map((o) => ({ id: o.id, eligible: o.eligible })) });
202430
202479
  this.pendingChoice = { groupId: group.id, options, byId };
202431
202480
  return;
202432
202481
  }
@@ -202435,7 +202484,7 @@ var Flow = class {
202435
202484
  this.enterChild(fallback2);
202436
202485
  return;
202437
202486
  }
202438
- this.emit({ type: "dry", group: group.id });
202487
+ if (this.host.tracing) this.emit({ type: "dry", group: group.id });
202439
202488
  this.host.onDryChoice?.(group.id);
202440
202489
  }
202441
202490
  // -- Jumps (jump / call-return) ----------------------------------------
@@ -202450,7 +202499,7 @@ var Flow = class {
202450
202499
  * hard-ends the flow regardless of the callstack.
202451
202500
  */
202452
202501
  enterTarget(to, mode) {
202453
- this.emit({ type: "jump", to, mode });
202502
+ if (this.host.tracing) this.emit({ type: "jump", to, mode });
202454
202503
  if (to === "END") {
202455
202504
  this.flowEnded = true;
202456
202505
  this.stack = [];
@@ -202483,11 +202532,11 @@ var Flow = class {
202483
202532
  // -- Selectors ------------------------------------------------------------
202484
202533
  selectChild(group) {
202485
202534
  const verdicts = group.children.map((c2) => ({ id: c2.id, eligible: this.eligible(c2) }));
202486
- const eligible = group.children.filter((c2) => this.eligible(c2));
202535
+ const eligible = group.children.filter((_2, i) => verdicts[i].eligible);
202487
202536
  const order = group.options?.order ?? "sequential";
202488
202537
  const exhaust = group.options?.exhaust ?? "once";
202489
202538
  const trace = (picked) => {
202490
- this.emit({
202539
+ if (this.host.tracing) this.emit({
202491
202540
  type: "select",
202492
202541
  group: group.id,
202493
202542
  selector: group.selector ?? "default",
@@ -202530,8 +202579,9 @@ var Flow = class {
202530
202579
  const len = eligible.length;
202531
202580
  const stick = exhaust === "stick";
202532
202581
  const fill = () => (stick ? eligible.slice(0, len - 1) : eligible).map((c2) => c2.id);
202582
+ const eligibleIds = new Set(eligible.map((c2) => c2.id));
202533
202583
  if (st.bag === void 0) st.bag = fill();
202534
- if (st.bag.length === 0) {
202584
+ if (!st.bag.some((id2) => eligibleIds.has(id2))) {
202535
202585
  if (exhaust === "once") return null;
202536
202586
  if (stick) {
202537
202587
  const last2 = eligible[len - 1];
@@ -202539,13 +202589,14 @@ var Flow = class {
202539
202589
  return last2;
202540
202590
  }
202541
202591
  st.bag = fill();
202592
+ if (st.bag.length === 0) return null;
202542
202593
  }
202543
- const pool = st.bag;
202594
+ const pool = st.bag.filter((id2) => eligibleIds.has(id2));
202544
202595
  const p = st.last !== void 0 && pool.length > 1 ? pool.indexOf(st.last) : -1;
202545
202596
  let i = Math.floor(this.rng() * (p >= 0 ? pool.length - 1 : pool.length));
202546
202597
  if (p >= 0 && i >= p) i++;
202547
202598
  const id = pool[i];
202548
- pool.splice(i, 1);
202599
+ st.bag.splice(st.bag.indexOf(id), 1);
202549
202600
  st.last = id;
202550
202601
  return eligible.find((c2) => c2.id === id);
202551
202602
  }
@@ -202594,7 +202645,7 @@ var Flow = class {
202594
202645
  /** A child's Best-match score against the current state: 0 when it has no condition (the filler
202595
202646
  * tier), else the specificity of its (already-passing) condition. */
202596
202647
  specScore(node) {
202597
- return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true) : 0;
202648
+ return node.condition ? this.matchedSpec(this.conditionAst(node.condition), true, node) : 0;
202598
202649
  }
202599
202650
  /**
202600
202651
  * The **matched-specificity** metric (parity contract): how many atomic constraints are actively
@@ -202604,8 +202655,15 @@ var Flow = class {
202604
202655
  * to hold?" (true at the root). Only `and`/`or`/`not`/`check_flags` are structural; every other
202605
202656
  * node (comparisons, scoped vars, literals, other calls) is an atom, evaluated whole.
202606
202657
  */
202607
- matchedSpec(node, want) {
202608
- const evalTruthy = (n) => truthy(evaluate(n, this.context(), patterDialect));
202658
+ matchedSpec(node, want, owner) {
202659
+ const evalTruthy = (n) => {
202660
+ try {
202661
+ return truthy(evaluate(n, this.context(), patterDialect));
202662
+ } catch (err) {
202663
+ this.reportError("best-match", owner.id, owner.condition, err);
202664
+ return false;
202665
+ }
202666
+ };
202609
202667
  return matchedSpecificity(node, evalTruthy, { want });
202610
202668
  }
202611
202669
  /** A selector's cursor state - shared across flows (`group.shared`) or this flow's own. */
@@ -202637,17 +202695,36 @@ var Flow = class {
202637
202695
  return st;
202638
202696
  }
202639
202697
  // -- Effects + expressions ------------------------------------------------
202640
- runEffects(effects) {
202698
+ /** Run an effect list. `owner` is the snippet or scene it belongs to, for an error report. */
202699
+ runEffects(effects, owner) {
202641
202700
  for (const e of effects ?? []) {
202642
- const value = this.evalExpr(e.value);
202643
- const prev = this.host.logEnabled ? this.getProperty(e.target) : void 0;
202644
- this.writeProperty(e.target, value, false);
202645
- this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
202701
+ let value;
202702
+ try {
202703
+ value = this.evalExpr(e.value);
202704
+ const prev = this.host.tracing ? this.getProperty(e.target) : void 0;
202705
+ this.writeProperty(e.target, value, false);
202706
+ if (this.host.tracing) this.emit({ type: "write", target: e.target, value, ...prev !== void 0 ? { prev } : {} });
202707
+ } catch (err) {
202708
+ this.reportError("effect", owner, e.value, err);
202709
+ }
202646
202710
  }
202647
202711
  }
202712
+ /** Whether a node's condition holds. A condition that fails to evaluate counts as false (see PlayError). */
202648
202713
  eligible(node) {
202649
202714
  if (!node.condition) return true;
202650
- return truthy(this.evalExpr(node.condition));
202715
+ try {
202716
+ return truthy(this.evalExpr(node.condition));
202717
+ } catch (err) {
202718
+ this.reportError("condition", node.id, node.condition, err);
202719
+ return false;
202720
+ }
202721
+ }
202722
+ /** Report a content error the engine is playing through: to the host's onError, and to the log. */
202723
+ reportError(kind, node, expr, err) {
202724
+ const message = err instanceof Error ? err.message : String(err);
202725
+ const source2 = expr?.src;
202726
+ this.host.onError({ flow: this.id, kind, node, ...source2 ? { source: source2 } : {}, message });
202727
+ if (this.host.tracing) this.emit({ type: "diagnostic", kind, node, ...source2 ? { source: source2 } : {}, message });
202651
202728
  }
202652
202729
  evalExpr(expr) {
202653
202730
  return evaluate(this.conditionAst(expr), this.context(), patterDialect);
@@ -202664,15 +202741,15 @@ var Flow = class {
202664
202741
  }
202665
202742
  /** Record an entry of a node (entered-only; spec §7): bumps the flow + world counts. */
202666
202743
  enter(id) {
202667
- const own = this.visitCounts;
202668
- const ownBefore = own.get(id);
202669
- own.set(id, (ownBefore ?? 0) + 1);
202744
+ const own2 = this.visitCounts;
202745
+ const ownBefore = own2.get(id);
202746
+ own2.set(id, (ownBefore ?? 0) + 1);
202670
202747
  const shared = this.host.sharedVisits;
202671
202748
  const before = shared.get(id);
202672
202749
  shared.set(id, (before ?? 0) + 1);
202673
202750
  if (this.host.journal) this.host.journal.undo.push(() => {
202674
- if (ownBefore === void 0) own.delete(id);
202675
- else own.set(id, ownBefore);
202751
+ if (ownBefore === void 0) own2.delete(id);
202752
+ else own2.set(id, ownBefore);
202676
202753
  if (before === void 0) shared.delete(id);
202677
202754
  else shared.set(id, before);
202678
202755
  });
@@ -202843,23 +202920,23 @@ var Flow = class {
202843
202920
  if (!this.sceneBags.has(s)) {
202844
202921
  const bag = new PropertyBag(props.filter((d) => !shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
202845
202922
  const key2 = keys.flowScene(this.id, s);
202846
- this.host.registry.mountOwned(key2, bag, { owner: OWNER });
202923
+ const claimed = mountClaims(this.host.registry, key2, bag, journal !== null);
202847
202924
  this.registered.add(key2);
202848
202925
  this.sceneBags.set(s, bag);
202849
202926
  if (journal) journal.undo.push(() => {
202850
202927
  if (this.sceneBags.get(s) !== bag) return;
202851
- this.host.registry.remove(key2);
202928
+ this.host.registry.remove(key2, claimed ? { keep: true } : void 0);
202852
202929
  this.registered.delete(key2);
202853
202930
  this.sceneBags.delete(s);
202854
202931
  });
202855
202932
  }
202856
202933
  if (!this.host.stageBags.has(s)) {
202857
202934
  const bag = new PropertyBag(props.filter((d) => shared.has(d.name.toLowerCase())), { pathPrefix: "@scene." });
202858
- this.host.registry.mountOwned(keys.stage(s), bag, { owner: OWNER });
202935
+ const claimed = mountClaims(this.host.registry, keys.stage(s), bag, journal !== null);
202859
202936
  this.host.stageBags.set(s, bag);
202860
202937
  if (journal) journal.undo.push(() => {
202861
202938
  if (this.host.stageBags.get(s) !== bag) return;
202862
- this.host.registry.remove(keys.stage(s));
202939
+ this.host.registry.remove(keys.stage(s), claimed ? { keep: true } : void 0);
202863
202940
  this.host.stageBags.delete(s);
202864
202941
  });
202865
202942
  }
@@ -202926,9 +203003,29 @@ function deserialiseSelectors(rec) {
202926
203003
  }
202927
203004
  return map2;
202928
203005
  }
203006
+ function mountClaims(registry, key2, bag, ask) {
203007
+ const before = ask ? JSON.stringify(bag.save()) : "";
203008
+ registry.mountOwned(key2, bag, { owner: OWNER });
203009
+ return ask && JSON.stringify(bag.save()) !== before;
203010
+ }
202929
203011
  function toDecl(decl) {
202930
203012
  return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default };
202931
203013
  }
203014
+ function boundScopes(options) {
203015
+ return { ...options.world ? { world: options.world } : {}, ...options.hostScopes };
203016
+ }
203017
+ function ladders(host2, cacheKey, decls) {
203018
+ let found = host2.qualityLadders.get(cacheKey);
203019
+ if (!found) {
203020
+ found = /* @__PURE__ */ new Map();
203021
+ for (const d of decls() ?? []) {
203022
+ const k = d.name.toLowerCase();
203023
+ if (d.type === "quality" && d.stages && !found.has(k)) found.set(k, d.stages);
203024
+ }
203025
+ host2.qualityLadders.set(cacheKey, found);
203026
+ }
203027
+ return found;
203028
+ }
202932
203029
  function toForeignDecl(decl) {
202933
203030
  return { name: decl.name, type: decl.type, values: decl.values, stages: decl.stages, default: decl.default, writable: decl.writable };
202934
203031
  }
@@ -202937,6 +203034,12 @@ function selfBackedDecl(decl, scopeWritable) {
202937
203034
  const writable = decl.writable ?? scopeWritable;
202938
203035
  return writable === void 0 ? d : { ...d, writable };
202939
203036
  }
203037
+ function resolveScene(host2, ref) {
203038
+ return host2.sceneGameIdToId.get(ref) ?? (host2.bundle.scenes[ref] ? ref : void 0);
203039
+ }
203040
+ function resolveBlock(host2, sceneId, ref) {
203041
+ return host2.blockGameIdToId.get(sceneId)?.get(ref) ?? (host2.blockIndex.get(ref)?.sceneId === sceneId ? ref : void 0);
203042
+ }
202940
203043
  function splitHostRef(host2, ref) {
202941
203044
  if (host2.refSplitRevision !== host2.registry.revision) {
202942
203045
  host2.refSplitCache.clear();
@@ -203019,7 +203122,7 @@ function runPlay(loaded, opts = {}) {
203019
203122
  const bundle = exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales, gameScopes: loaded.gameScopes?.merged });
203020
203123
  const events = [];
203021
203124
  const registry = previewRegistry(loaded.gameScopes, bundle);
203022
- const engine = new Engine(bundle, { seed: opts.seed, ...registry ? { registry } : {} });
203125
+ const engine = new Engine(bundle, { seed: opts.seed, onError: (error) => events.push({ type: "error", error }), ...registry ? { registry } : {} });
203023
203126
  const start = resolveStart(loaded, opts);
203024
203127
  const flow = engine.openFlow("main", { scene: start.scene, block: start.block });
203025
203128
  const scripted = [...opts.choices ?? []];
@@ -203051,6 +203154,11 @@ function renderPlay(result) {
203051
203154
  case "gameEvent":
203052
203155
  out.push(` (game event ${JSON.stringify(e.gameData ?? {})})`);
203053
203156
  break;
203157
+ case "error": {
203158
+ const { kind, node, source: source2, message } = e.error;
203159
+ out.push(` ! ${kind} on '${node}'${source2 ? ` (${source2})` : ""} failed, played through: ${message}`);
203160
+ break;
203161
+ }
203054
203162
  case "choice":
203055
203163
  for (const o of e.options) out.push(` ${o.eligible ? "[ ]" : "[x]"} ${o.prompt?.text ?? "(no label)"} (${o.id})`);
203056
203164
  if (e.picked !== void 0) out.push(` > ${e.picked}`);
@@ -203271,9 +203379,12 @@ function* sweep(loaded, options = {}, hooks = {}) {
203271
203379
  const order = [];
203272
203380
  const meta = /* @__PURE__ */ new Map();
203273
203381
  const choiceScene = /* @__PURE__ */ new Map();
203382
+ const nodeScene = /* @__PURE__ */ new Map();
203274
203383
  for (const scene of loaded.scenes) {
203384
+ nodeScene.set(scene.id, scene.id);
203275
203385
  for (const block of scene.blocks) {
203276
203386
  walkNodes(block.children, (node) => {
203387
+ nodeScene.set(node.id, scene.id);
203277
203388
  if (node.type === "group") {
203278
203389
  if (node.selector === "choice") choiceScene.set(node.id, scene.id);
203279
203390
  return;
@@ -203294,6 +203405,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
203294
203405
  const hitCount = new Map(order.map((id) => [id, 0]));
203295
203406
  const reachedRuns = new Map(order.map((id) => [id, 0]));
203296
203407
  const dryRuns = /* @__PURE__ */ new Map();
203408
+ const errorRuns = /* @__PURE__ */ new Map();
203297
203409
  const termination = { ended: 0, capped: 0, stalled: 0, evalError: 0 };
203298
203410
  const bundle = exportBundle({ project: loaded.project, scenes: loaded.scenes, locales: loaded.locales, gameScopes: loaded.gameScopes?.merged });
203299
203411
  const rng2 = mulberry32(seed);
@@ -203314,10 +203426,15 @@ function* sweep(loaded, options = {}, hooks = {}) {
203314
203426
  break;
203315
203427
  }
203316
203428
  const dryThisRun = /* @__PURE__ */ new Set();
203429
+ const errorsThisRun = /* @__PURE__ */ new Map();
203317
203430
  const registry = previewRegistry(loaded.gameScopes, bundle);
203318
203431
  const engine = new Engine(bundle, {
203319
203432
  seed: Math.floor(rng2() * 4294967296),
203320
203433
  onDryChoice: (groupId) => dryThisRun.add(groupId),
203434
+ onError: (e) => {
203435
+ const key2 = `${e.kind}|${e.node}|${e.message}`;
203436
+ if (!errorsThisRun.has(key2)) errorsThisRun.set(key2, { kind: e.kind, node: e.node, ...e.source ? { source: e.source } : {}, message: e.message });
203437
+ },
203321
203438
  ...registry ? { registry } : {}
203322
203439
  });
203323
203440
  for (const d of initialDrivers) engine.setProperty(d.ref, pick(d.values));
@@ -203353,6 +203470,11 @@ function* sweep(loaded, options = {}, hooks = {}) {
203353
203470
  }
203354
203471
  for (const id of seenThisRun) reachedRuns.set(id, reachedRuns.get(id) + 1);
203355
203472
  for (const id of dryThisRun) dryRuns.set(id, (dryRuns.get(id) ?? 0) + 1);
203473
+ for (const [key2, e] of errorsThisRun) {
203474
+ const seen = errorRuns.get(key2);
203475
+ if (seen) seen.runs++;
203476
+ else errorRuns.set(key2, { ...e, scene: nodeScene.get(e.node) ?? "", runs: 1 });
203477
+ }
203356
203478
  termination[term]++;
203357
203479
  executed++;
203358
203480
  if ((run2 & 255) === 0) hooks.onProgress?.(executed, runs);
@@ -203405,6 +203527,7 @@ function* sweep(loaded, options = {}, hooks = {}) {
203405
203527
  drivers,
203406
203528
  unwrittenInputs: [...unwrittenInputs].sort(),
203407
203529
  dryChoices,
203530
+ contentErrors: [...errorRuns.values()].sort((a, b) => b.runs - a.runs || a.node.localeCompare(b.node)),
203408
203531
  cancelled
203409
203532
  };
203410
203533
  }
@@ -203426,7 +203549,7 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
203426
203549
  );
203427
203550
  out.push(`${report.runs} run(s) - ${report.maxSteps} max steps - seed ${report.seed}${report.cancelled ? " - CANCELLED" : ""}`);
203428
203551
  const term = report.termination;
203429
- out.push(`runs ended: ${term.ended} reached the end, ${term.stalled} stalled at a choice with nothing to pick, ${term.capped} hit the step limit, ${term.evalError} errored`);
203552
+ out.push(`runs ended: ${term.ended} reached the end, ${term.stalled ? `${term.stalled} stalled at a choice with nothing to pick, ` : ""}${term.capped} hit the step limit, ${term.evalError} errored`);
203430
203553
  if (report.drivers.length) out.push(`input drivers: ${report.drivers.map((d) => d.ref).join(", ")}`);
203431
203554
  if (report.unwrittenInputs.length) {
203432
203555
  out.push(`? = gated on an input nothing writes/drives: ${report.unwrittenInputs.join(", ")} (add a coverage driver?)`);
@@ -203438,6 +203561,13 @@ function renderCoverageText(report, sceneName = (id) => id, opts = {}) {
203438
203561
  out.push(` \u203C ${String(d.runs).padStart(6)} run(s) ${sceneName(d.scene)} choice '${d.id}'`);
203439
203562
  }
203440
203563
  }
203564
+ if (report.contentErrors.length) {
203565
+ out.push("");
203566
+ out.push(`content errors (a condition or effect failed and play went on without it - fix the expression): ${report.contentErrors.length}`);
203567
+ for (const e of report.contentErrors) {
203568
+ out.push(` \u203C ${String(e.runs).padStart(6)} run(s) ${sceneName(e.scene)} ${e.kind} on '${e.node}'${e.source ? ` (${e.source})` : ""}: ${e.message}`);
203569
+ }
203570
+ }
203441
203571
  if (!report.beats.length) return out;
203442
203572
  const clip = (s, n = 48) => s.length > n ? `${s.slice(0, n - 1)}\u2026` : s;
203443
203573
  const scenes = new Set(report.beats.map((b) => b.scene));
@@ -203883,10 +204013,10 @@ function resolveDocumentation(loaded, channel) {
203883
204013
  const classes = loaded.project.documentationClasses ?? DEFAULT_DOCUMENTATION_CLASSES;
203884
204014
  const allowed = classesForChannel(classes, channel);
203885
204015
  const docsOf = mergeAuthoring2(loaded).documentation;
203886
- const own = (id) => (docsOf.get(id) ?? []).filter((l2) => l2.type !== void 0 && allowed.has(l2.type));
204016
+ const own2 = (id) => (docsOf.get(id) ?? []).filter((l2) => l2.type !== void 0 && allowed.has(l2.type));
203887
204017
  const out = /* @__PURE__ */ new Map();
203888
204018
  const visit = (id, inherited) => {
203889
- const mine = own(id);
204019
+ const mine = own2(id);
203890
204020
  if (inherited.length === 0 && mine.length === 0) return inherited;
203891
204021
  const resolved = [...inherited, ...mine];
203892
204022
  out.set(id, resolved);
@@ -204282,14 +204412,14 @@ function runVoiceScript(loaded, opts = {}) {
204282
204412
  if (beat.kind !== "line" || cutSet.has(beat.id)) continue;
204283
204413
  const ws = writingOf.get(beat.id) ?? stub;
204284
204414
  if (!everything && recordThreshold !== -1 && (writingIndex.get(ws) ?? 0) < recordThreshold) continue;
204285
- const own = ownVo(beat.id);
204415
+ const own2 = ownVo(beat.id);
204286
204416
  lines.push({
204287
204417
  scope: scope.join(" \u203A "),
204288
204418
  id: beat.id,
204289
204419
  character: beat.character ?? "",
204290
204420
  actor: beat.character ? actorOf.get(beat.character) : void 0,
204291
204421
  text: plainVoice(source2[beat.id] ?? ""),
204292
- comments: leading ? [...ancestorVo, ...own] : own,
204422
+ comments: leading ? [...ancestorVo, ...own2] : own2,
204293
204423
  // first line of the run gets the enclosing context
204294
204424
  recordingStatus: recordingOf(beat.id)
204295
204425
  });
@@ -248910,7 +249040,7 @@ function fileStatus(filePaths, options = {}) {
248910
249040
  // package.json
248911
249041
  var package_default = {
248912
249042
  name: "@patterkit/cli",
248913
- version: "0.7.5",
249043
+ version: "0.8.1",
248914
249044
  description: "The `patter` CLI: validate, format, export, and play a Patter project.",
248915
249045
  type: "module",
248916
249046
  license: "MIT",
@@ -248940,7 +249070,7 @@ var package_default = {
248940
249070
  },
248941
249071
  dependencies: {
248942
249072
  "@patterkit/core": "0.3.2",
248943
- "@patterkit/ops": "0.12.5",
249073
+ "@patterkit/ops": "0.13.1",
248944
249074
  "@wildwinter/simple-vc-lib": "^0.5.0"
248945
249075
  }
248946
249076
  };
@@ -249276,7 +249406,7 @@ ${count} issue(s)${advisory}`);
249276
249406
  seed
249277
249407
  });
249278
249408
  for (const line2 of renderPlay(result)) console.log(line2);
249279
- return result.outcome === "end" ? 0 : 1;
249409
+ return result.outcome === "end" && !result.events.some((e) => e.type === "error") ? 0 : 1;
249280
249410
  }
249281
249411
  case "coverage": {
249282
249412
  const numbers = {};
@@ -249683,7 +249813,7 @@ function describeSuggestion(o) {
249683
249813
  }
249684
249814
 
249685
249815
  // src/cli.ts
249686
- process.exit(await main(process.argv.slice(2)));
249816
+ process.exitCode = await main(process.argv.slice(2));
249687
249817
  /*! Bundled license information:
249688
249818
 
249689
249819
  xmlchars/xml/1.0/ed5.js:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/cli",
3
- "version": "0.7.5",
3
+ "version": "0.8.1",
4
4
  "description": "The `patter` CLI: validate, format, export, and play a Patter project.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@patterkit/core": "0.3.2",
33
- "@patterkit/ops": "0.12.5",
33
+ "@patterkit/ops": "0.13.1",
34
34
  "@wildwinter/simple-vc-lib": "^0.5.0"
35
35
  }
36
36
  }