@patterkit/ops 0.1.3 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProjectFile, Scene, LocaleFile, AuthoringFile, Bundle, GameData, CoverageDriver, DocumentationClass, DocLine } from '@patterkit/model';
1
+ import { ProjectFile, Scene, LocaleFile, AuthoringFile, Bundle, GameData, CoverageDriver, GrammaticalGender, DocumentationClass, DocLine } from '@patterkit/model';
2
2
  import { ValidationIssue } from '@patterkit/core';
3
3
  import { ConditionIssue } from '@patterkit/compiler';
4
4
  import { ChoiceOption } from '@patterkit/runtime';
@@ -406,10 +406,13 @@ interface LocEntry {
406
406
  translation: string;
407
407
  /** Localiser-channel notes for this id (DocLine text, ancestors outermost-first). */
408
408
  comments: string[];
409
- /** Best-effort context for the translator. */
409
+ /** Best-effort context for the translator. `gender` is the speaker's grammatical gender, looked up
410
+ * from the cast - what a gendered language needs to inflect the line. Export-only: `applyLoc` never
411
+ * reads it back, it is regenerated from the cast on every export. */
410
412
  context?: {
411
413
  character?: string;
412
414
  kind?: string;
415
+ gender?: GrammaticalGender;
413
416
  };
414
417
  /** Translated, but the source changed since (source modifiedAt > localisedAt[locale]). */
415
418
  stale: boolean;
package/dist/index.js CHANGED
@@ -332,7 +332,7 @@ function bundleOutputPath(loaded) {
332
332
  import { canonicalStringify } from "@patterkit/core";
333
333
 
334
334
  // src/playable-runtime.ts
335
- var PLAYABLE_RUNTIME_JS = '"use strict";var Patterplay=(()=>{var A=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var ce=Object.getOwnPropertyNames;var le=Object.prototype.hasOwnProperty;var pe=(s,e)=>{for(var t in e)A(s,t,{get:e[t],enumerable:!0})},de=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of ce(e))!le.call(s,n)&&n!==t&&A(s,n,{get:()=>e[n],enumerable:!(r=ae(e,n))||r.enumerable});return s};var ge=s=>de(A({},"__esModule",{value:!0}),s);var xe={};pe(xe,{Engine:()=>B,Flow:()=>v,buildTagIndex:()=>O,effectiveGameData:()=>se,gameDataFields:()=>ne,gameDataValue:()=>j});function S(s){switch(s[0]){case"b":return{kind:"bool",value:s[1]};case"n":return{kind:"number",value:s[1]};case"s":return{kind:"string",value:s[1]};case"sv":return{kind:"scopedvar",scope:s[1],name:s[2]};case"u":return{kind:"unary",op:s[1],operand:S(s[2])};case"bin":return{kind:"binary",op:s[1],left:S(s[2]),right:S(s[3])};case"call":{let e=s.slice(2).map(S);return{kind:"call",name:s[1],args:e}}case"fd":return{kind:"flagdelta",sign:s[1],name:s[2]}}}var p=class extends Error{constructor(e){super(e),this.name="EvalError"}};function w(s,e,t){let r=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),n=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 p(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{let o=t.functions[i.name];if(!o)throw new p(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:n,ctx:e})}case"flagdelta":throw new p("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=n(i.operand);if(typeof a!="boolean")throw new p(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=n(i.operand);if(typeof o!="number")throw new p(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let l=n(i.left);if(typeof l!="boolean")throw new p(`\'and\' requires boolean operands, left is ${typeof l}`);if(!l)return!1;let c=n(i.right);if(typeof c!="boolean")throw new p(`\'and\' requires boolean operands, right is ${typeof c}`);return c}if(i.op==="or"){let l=n(i.left);if(typeof l!="boolean")throw new p(`\'or\' requires boolean operands, left is ${typeof l}`);if(l)return!0;let c=n(i.right);if(typeof c!="boolean")throw new p(`\'or\' requires boolean operands, right is ${typeof c}`);return c}let o=n(i.left),a=n(i.right);switch(i.op){case"==":return U(o,a);case"!=":return!U(o,a);case">":return y(o,a,">"),o>a;case">=":return y(o,a,">="),o>=a;case"<":return y(o,a,"<"),o<a;case"<=":return y(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new p(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return y(o,a,"-"),o-a;case"*":return y(o,a,"*"),o*a;case"/":if(y(o,a,"/"),a===0)throw new p("division by zero");return o/a}}}};return n(s)}function U(s,e){if(Array.isArray(s)||Array.isArray(e)){if(!Array.isArray(s)||!Array.isArray(e)||s.length!==e.length)return!1;for(let t=0;t<s.length;t++)if(s[t]!==e[t])return!1;return!0}return s===e}function y(s,e,t){if(typeof s!="number"||typeof e!="number")throw new p(`\'${t}\' requires numeric operands, got ${typeof s} and ${typeof e}`)}var x=class{scopes=new Map;defineOwned(e,t){this.assertFree(e);let r={},n=new Map;for(let i of t){let o=i.name.toLowerCase();n.set(o,i),r[o]=i.default??W(i)}return this.scopes.set(e,{kind:"owned",bag:r,decls:n}),this}reseedOwned(e,t){let r=this.scopes.get(e);if(!r||r.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);for(let n of Object.keys(r.bag))delete r.bag[n];r.decls.clear();for(let n of t){let i=n.name.toLowerCase();r.decls.set(i,n),r.bag[i]=n.default??W(n)}return this}defineForeign(e,t,r=[],n=!0){this.assertFree(e);let i=new Map;for(let o of r)i.set(o.name.toLowerCase(),o);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:i,scopeWritable:n}),this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(!r)return;let n=t.toLowerCase();return r.kind==="owned"?r.bag[n]:r.resolver.get(n)}set(e,t,r){let n=this.scopes.get(e);if(!n)throw new Error(`unknown scope \'@${e}\'`);let i=t.toLowerCase();if(!this.writable(n,i))throw new Error(`\'@${e}.${t}\' is read-only`);n.kind==="owned"?n.bag[i]=r:n.resolver.set(i,r)}writable(e,t){return e.kind==="owned"?e.decls.get(t)?.writable??!0:e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}toEvalContext(e){let t={};for(let[r,n]of this.scopes)t[r]=n.kind==="owned"?n.bag:n.resolver;return{scopes:t,host:e}}toSchema(){let e=new Map;for(let[t,r]of this.scopes){if(r.decls.size===0)continue;let n=new Map;for(let[i,o]of r.decls)n.set(i,{type:o.type,enumValues:o.values});e.set(t,n)}return{properties:e}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]={...r.bag});return e}load(e){for(let[t,r]of Object.entries(e)){let n=this.scopes.get(t);n?.kind==="owned"&&Object.assign(n.bag,r)}}assertFree(e){if(this.scopes.has(e))throw new Error(`scope \'@${e}\' is already registered`)}};function W(s){if(s.default!==void 0)return s.default;switch(s.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return s.values?.[0]??"";case"flags":return[]}}function k(s){return s.ctx.host??{}}var I={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(s,e){if(s.length!==2)throw new p("random(a, b) requires exactly 2 arguments");let t=k(e).nextRandom;if(!t)throw new p("random() called without a PRNG in context");let r=e.evaluate(s[0]),n=e.evaluate(s[1]);if(typeof r!="number"||typeof n!="number")throw new p("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(n))throw new p("random(a, b) arguments must be integers");let i=Math.min(r,n),o=Math.max(r,n);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:K("check_flags"),eval(s,e){let t=J(s[0],e,"check_flags");for(let r=1;r<s.length;r++){let n=s[r];if(n.kind!=="flagdelta")throw new p("check_flags() flag args must be +flagName or -flagName");if(n.sign==="+"?!t.includes(n.name):t.includes(n.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:K("set_flags"),eval(s,e){let t=[...J(s[0],e,"set_flags")];for(let r=1;r<s.length;r++){let n=s[r];if(n.kind!=="flagdelta")throw new p("set_flags() flag args must be +flagName or -flagName");if(n.sign==="+")t.includes(n.name)||t.push(n.name);else{let i=t.indexOf(n.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:D("visits"),eval:(s,e)=>k(e).visits?.(C(s,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:D("seen"),eval:(s,e)=>(k(e).visits?.(C(s,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:D("patter_visits"),eval:(s,e)=>k(e).patterVisits?.(C(s,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:D("patter_seen"),eval:(s,e)=>(k(e).patterVisits?.(C(s,e,"patter_seen"))??0)>0}}};function V(s,e){let t=s.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function C(s,e,t){let r=e.evaluate(s[0]);if(typeof r!="string")throw new p(`${t}(id) requires a string node id`);return r}function D(s){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:`${s}(id): the argument must be a string id literal (a scene / block / node id)`})}}function J(s,e,t){if(!s)throw new p(`${t}() requires at least one argument (the flags variable)`);let r=e.evaluate(s);if(Array.isArray(r))return r;if(r===!1||r===null||r===void 0)return[];throw new p(`${t}() first argument must be a flags property`)}function K(s){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:`${s}(): first argument must be a flags property reference (@name or @scope.name)`});return}let n=t.schema.properties.get(r.scope)?.get(r.name);if(n&&n.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:`${s}(): \'@${i}\' is not a flags property (got ${n.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:`${s}(): argument ${i+1} must be +flagName or -flagName`}):n?.type==="flags"&&n.enumValues&&!n.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${s}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var ue=/^@[A-Za-z0-9_.]+$/;function*fe(s){let e="",t=0;for(;t<s.length;){let r=s[t];if(r==="{"&&s[t+1]==="{"){e+="{",t+=2;continue}if(r==="}"&&s[t+1]==="}"){e+="}",t+=2;continue}if(r==="{"){let n=s.indexOf("}",t+1);if(n!==-1){let i=s.slice(t,n+1),o=s.slice(t+1,n).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:ue.test(o)?o:void 0},t=n+1;continue}e+=i,t=n+1;continue}}e+=r,t+=1}e&&(yield{kind:"text",value:e})}function he(s){return Array.isArray(s)?s.join(", "):typeof s=="boolean"?s?"true":"false":String(s)}function me(s){return s===" "||s===" "||s===`\n`||s==="\\r"||s==="\\f"||s==="\\v"}function Se(s){let e="",t=!1;for(let r of s){if(me(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function z(s,e,t){if(e.length===0||s.indexOf(e)<0)return s;let r="",n=0,i=!1;for(;n<s.length;){if(s.startsWith(e,n)){let o=s.indexOf(t,n+e.length);if(o>=0){n=o+t.length,i=!0;continue}r+=s.slice(n);break}r+=s[n],n+=1}return i?Se(r):s}function X(s,e){if(s.indexOf("{")<0)return s;let t="";for(let r of fe(s)){if(r.kind==="text"){t+=r.value;continue}if(!r.ref){t+=r.raw;continue}let n=e(r.ref);t+=n===void 0?"":he(n)}return t}function E(s,e){for(let t of s){e(t);let r=t.children;r&&E(r,e)}}function ye(s){return s.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function h(s){let e=s.gameId?.trim();return e||ye(s.name)}function F(s){return`cast:${s}`}var T={open:"[",close:"]"},Z="SFX";function R(s){let e=new Set,t=[];for(let r of s)e.has(r)||(e.add(r),t.push(r));return t}function O(s){let e=new Map,t=(r,n)=>{let i=R([...n,...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,R([...i,...o.tags??[]]))};for(let r of Object.values(s.scenes)){let n=R(r.tags??[]);e.set(r.id,n);for(let i of r.blocks){let o=R([...n,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var Q=new WeakMap,B=class s{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,n=e.strings;this.allStrings=n,this.currentLocale=r;let i=n[r]??{},o=n[e.locales.default]??{},a=e.localisation,l=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 c=new Map;for(let d of e.cast??[])d.displayName&&c.set(d.name,d.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let g=new Map,u=new Map,L=new Map;for(let[d,m]of Object.entries(e.scenes)){this.sceneGameIdToId.set(h(m),d);let b=new Map;for(let f of m.blocks)u.set(f.id,{sceneId:d}),L.set(f.id,f),b.set(h(f),f.id),E(f.children,q=>g.set(q.id,q));this.blockGameIdToId.set(d,b)}let $=e.properties??[],N=$.filter(d=>d.shared??!0).map(Y),ie=$.filter(d=>!(d.shared??!0)).map(Y),oe=new Set(N.map(d=>d.name.toLowerCase())),P=new x().defineOwned("patter",N),H=new Set;if(t.world){let d=e.scopeRegistry?.scopes.find(b=>b.token==="world"),m=(d?.declarations??[]).map(ee);P.defineForeign("world",t.world,m,d?.writable??!0),H.add("world")}for(let d of e.scopeRegistry?.scopes??[]){if(H.has(d.token))continue;let m=(d.declarations??[]).map(ee);P.defineForeign(d.token,we(d.declarations??[]),m,d.writable??!0)}let _=new Map;for(let[d,m]of Object.entries(e.scenes)){let b=new Set((m.sceneProps??[]).filter(f=>f.shared??!1).map(f=>f.name.toLowerCase()));_.set(d,b)}this.host={bundle:e,emitIds:l,strings:i,defaultStrings:o,castDisplay:c,nodeIndex:g,blockIndex:u,blockById:L,tagIndex:O(e),shared:P,patterSharedDecls:N,patterLocalDecls:ie,patterSharedNames:oe,sceneSharedNames:_,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??T).open,captionClose:(e.closedCaptions??T).close,captionCharacter:e.closedCaptions?.character||Z,refSplitCache:new Map}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){let t=this.saveGame(),r=i=>(i.setLocale(this.currentLocale),i.setClosedCaptions(this.host.captionsOn),i),n=new s(e,this.creationOptions);try{return n.loadGame(t),r(n)}catch{let i=new s(e,this.creationOptions);for(let[o,a]of Object.entries(t.flows)){let l=a.cursor.currentSceneId;try{i.openFlow(o,l!==null?{scene:l}:{})}catch{}}return r(i)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){let r=this.resolveSceneRef(t.scene),n=this.resolveBlockRef(r,t.block),i=new v(e,this.host,t.seed??this.defaultSeed);return this.flowsById.set(e,i),i.start(r,n),i}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?h(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?h(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),n=this.resolveBlockRef(r,t);return(n!=null?this.host.tagIndex.get(n):void 0)??[]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...h(e)?{gameId:h(e)}:{},name:e.name,...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...h(t)?{gameId:h(t)}:{},name:t.name,...this.tagsField(t.id),children:t.children.map(r=>this.outlineNode(r))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let r of t.blocks)E(r.children,n=>{if(n.type==="snippet")for(let i of n.beats??[])e.push({sceneId:t.id,blockId:r.id,snippetId:n.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 n=this.host.defaultStrings[F(e.character)]??this.host.castDisplay.get(e.character);n!==void 0&&(r.characterName=n)}e.direction!==void 0&&(r.direction=e.direction)}if(e.kind==="line"||e.kind==="text"){let n=this.host.defaultStrings[e.id];n!==void 0&&(r.text=n)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=this.host.tagIndex.get(e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){this.flowsById.delete(e)}reset(){this.flowsById.clear(),this.host.shared.reseedOwned("patter",this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear(),this.host.stageBags.clear()}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:n}=this.splitShared(e);this.host.shared.set(r,n,t)}listProperties(){return this.host.patterSharedDecls.map(e=>({ref:`@${e.name}`,type:e.type,values:e.values,value:this.getProperty(`@${e.name}`),default:be(e)}))}splitShared(e){let t=this.host.refSplitCache.get(e);if(t||(t=V(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return this.host.shared.save()}load(e){this.host.shared.load(e)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:2,shared:this.host.shared.save(),sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:te(this.host.sharedSelectors),stageBags:Object.fromEntries([...this.host.stageBags].map(([t,r])=>[t,{...r}])),flows:e}}loadGame(e){if(e.version!==2)throw new Error(`unsupported save version: ${e.version}`);this.host.shared.load(e.shared),this.host.sharedVisits.clear();for(let[t,r]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(t,r);this.host.sharedSelectors.clear();for(let[t,r]of re(e.sharedSelectors))this.host.sharedSelectors.set(t,r);this.host.stageBags.clear();for(let[t,r]of Object.entries(e.stageBags??{}))this.host.stageBags.set(t,{...r});this.flowsById.clear();for(let[t,r]of Object.entries(e.flows)){let n=new v(t,this.host,this.defaultSeed);n.restore(r),this.flowsById.set(t,n)}}},v=class{id;host;local;rngState;started=!1;flowEnded=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPromptBeat=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.shared.get("patter",e):this.local.get("patter",e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.shared.set("patter",e,t):this.local.set("patter",e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;return t===null?void 0:(this.host.sceneSharedNames.get(t)?.has(e)?this.host.stageBags.get(t):this.sceneBags.get(t))?.[e]},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let n=this.host.sceneSharedNames.get(r)?.has(e)?this.host.stageBags.get(r):this.sceneBags.get(r);n&&(n[e]=t)}};evalCtx;constructor(e,t,r){this.id=e,this.host=t,this.rngState=r>>>0,this.local=this.freshLocal();let n={...t.shared.toEvalContext().scopes};n.patter=this.patterResolver,n.scene=this.sceneResolver,this.evalCtx={scopes:n,host:{nextRandom:this.rng,visits:i=>this.visitCounts.get(i)??0,patterVisits:i=>this.host.sharedVisits.get(i)??0}}}start(e,t){if(this.sceneBags.clear(),this.local=this.freshLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.started=!0,t){let r=this.host.blockIndex.get(t);if(!r)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(r.sceneId),this.stack=[{sceneId:r.sceneId,containerId:t,index:0}],this.enter(t)}else{let r=e??Object.keys(this.host.bundle.scenes)[0],n=r?this.host.bundle.scenes[r]:void 0;if(!n)throw new Error(r?`unknown scene: ${r}`:"no scenes in bundle");this.enterSceneSetup(r);let i=n.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)}get currentScene(){return this.currentSceneId}advance(){if(!this.started)throw new Error("flow has not been started");if(this.pendingPromptBeat){let e=this.pendingPromptBeat;return this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.beatResult(e)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit);let n=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(n);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let r=t.options.find(i=>i.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);let n=t.byId.get(e);this.pendingChoice=null,this.pendingPromptBeat=this.host.replayPromptOnChoose?this.promptBeatOf(n)??null:null,this.pendingPromptOwnerId=this.pendingPromptBeat?n.id:null,this.enterChild(n)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:r}=this.splitRef(e);return t==="patter"?this.patterResolver.get(r):t==="scene"?this.sceneResolver.get(r):this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:n}=this.splitRef(e);if(r==="patter")this.patterResolver.set(n,t);else if(r==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);this.sceneResolver.set(n,t)}else this.host.shared.set(r,n,t)}snapshot(){return{scopes:this.local.save(),sceneBags:Object.fromEntries([...this.sceneBags].map(([e,t])=>[e,{...t}])),rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,selectors:te(this.selectors)}}}restore(e){this.rngState=e.rngState>>>0,this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(r=>{let{nextId:n,...i}=r;if(n!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===n)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.sceneBags=new Map(Object.entries(e.sceneBags??{}).map(([r,n])=>[r,{...n}])),this.local=this.freshLocal(),this.local.load(e.scopes),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=re(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let r=new Map,n=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);o&&(r.set(i.id,o),n.push({...i}))}n.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:n,byId:r})}if(this.pendingPromptBeat=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId);this.pendingPromptBeat=r?this.promptBeatOf(r)??null:null,this.pendingPromptBeat||(this.pendingPromptOwnerId=null)}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let r=this.selectChild(e);r&&this.enterChild(r)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let r=this.host.nodeIndex.get(e);if(r&&r.type==="group")return r.children}beginSnippet(e){this.runEffects(e.onEnter),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],r=new Map,n=[];for(let o of e.children){if(o.fallback===!0){n.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),l=o.secretUntilEligible===!0;!a&&l||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData}),r.set(o.id,o))}if(t.length>0){this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=n.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(e==="END"){this.flowEnded=!0,this.stack=[];return}let r,n,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,n=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,n=e}this.enter(n);let o={sceneId:r,containerId:n,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.filter(n=>this.eligible(n));if(t.length===0)return null;let r=this.selectorState(e);switch(e.selector){case"branch":return t[0];case"sequence":{let n=e.options?.order??"sequential",i=e.options?.exhaust??"once";return n==="shuffle"?this.pickShuffle(t,i,r):n==="specificity"?this.pickSpecificity(t,i,r):this.pickSequential(t,i,r)}default:return null}}pickSequential(e,t,r){let n=e.length,i=r.seq??0;return r.seq=i+1,t==="repeat"?e[i%n]:i<n?e[i]:t==="stick"?e[n-1]:null}pickShuffle(e,t,r){let n=e.length,i=t==="stick",o=()=>(i?e.slice(0,n-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[n-1];return r.last=u.id,u}r.bag=o()}let a=r.bag,l=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,c=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&c>=l&&c++;let g=a[c];return a.splice(c,1),r.last=g,e.find(u=>u.id===g)}pickSpecificity(e,t,r){let n=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(g=>g.id));let c=new Set(r.bag);if(n=e.filter(g=>c.has(g.id)),n.length===0)return t==="stick"&&r.last!==void 0?e.find(g=>g.id===r.last)??null:null}let i=-1,a=n.map(c=>{let g=this.specScore(c);return g>i&&(i=g),{c,s:g}}).filter(c=>c.s===i).map(c=>c.c),l;if(a.length===1)l=a[0];else{let c=r.last!==void 0?a.findIndex(u=>u.id===r.last):-1,g=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&g>=c&&g++,l=a[g]}return t!=="repeat"&&(r.bag=r.bag.filter(c=>c!==l.id)),r.last=l.id,l}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0):0}matchedSpec(e,t){if(e.kind==="binary"&&(e.op==="and"||e.op==="or")){let r=e.op==="and"===t,n=this.matchedSpec(e.left,t),i=this.matchedSpec(e.right,t);return r?n>0&&i>0?n+i:0:Math.max(n,i)}if(e.kind==="unary"&&e.op==="not")return this.matchedSpec(e.operand,!t);if(e.kind==="call"&&e.name==="check_flags"){let r=Math.max(1,e.args.length-1),n=M(w(e,this.evalCtx,I));return t?n?r:0:n?0:1}return M(w(e,this.evalCtx,I))===t?1:0}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=t.get(e.id);return r||(r={},t.set(e.id,r)),r}runEffects(e){for(let t of e??[])this.setProperty(t.target,this.evalExpr(t.value))}eligible(e){return e.condition?M(this.evalExpr(e.condition)):!0}evalExpr(e){return w(this.conditionAst(e),this.evalCtx,I)}conditionAst(e){let t=Q.get(e);return t||(t=S(e.ast),Q.set(e,t)),t}enter(e){this.visitCounts.set(e,(this.visitCounts.get(e)??0)+1),this.host.sharedVisits.set(e,(this.host.sharedVisits.get(e)??0)+1)}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=this.rngState+1831565813|0;this.rngState=e;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296};beatResult(e){let t=this.host.tagIndex.get(e.id),r=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...r};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),gameData:e.gameData,...r};case"line":{let n=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?n:this.interpolate(n)),l=i&&a.length===0;return{type:"line",id:e.id,text:a,character:l?void 0:e.character,characterName:l?void 0:this.resolveCharacterName(e.character),direction:l?void 0:e.direction,gameData:e.gameData,...r}}}}interpolate(e){return X(e,t=>this.getProperty(t))}stripCaptions(e){return z(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let r=this.interpolate(this.resolveString(t.id));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(r=>r.kind==="line"||r.kind==="text")}firstTextSnippetIn(e){let t;return E(e,r=>{!t&&r.type==="snippet"&&(r.beats??[]).some(n=>n.kind==="line"||n.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=F(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){let t=this.host.refSplitCache.get(e);return t||(t=V(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t}freshLocal(){return new x().defineOwned("patter",this.host.patterLocalDecls)}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;if(!this.sceneBags.has(e.id)){let r={};for(let n of e.sceneProps??[]){let i=n.name.toLowerCase();t.has(i)||(r[i]=G(n))}this.sceneBags.set(e.id,r)}if(!this.host.stageBags.has(e.id)){let r={};for(let n of e.sceneProps??[]){let i=n.name.toLowerCase();t.has(i)&&(r[i]=G(n))}this.host.stageBags.set(e.id,r)}for(let r of e.sceneProps??[]){if(!r.temporary)continue;let n=r.name.toLowerCase(),i=t.has(n)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);i&&(i[n]=G(r))}}};function te(s){let e={};for(let[t,r]of s){let n={};r.seq!==void 0&&(n.seq=r.seq),r.bag&&(n.bag=[...r.bag]),r.last!==void 0&&(n.last=r.last),e[t]=n}return e}function re(s){let e=new Map;for(let[t,r]of Object.entries(s??{})){let n={};r.seq!==void 0&&(n.seq=r.seq),r.bag&&(n.bag=[...r.bag]),r.last!==void 0&&(n.last=r.last),e.set(t,n)}return e}function Y(s){return{name:s.name,type:s.type,values:s.values,default:s.default}}function be(s){if(s.default!==void 0)return s.default;switch(s.type){case"number":return 0;case"string":return"";case"flags":return[];case"enum":return s.values?.[0]??"";default:return!1}}function ee(s){return{name:s.name,type:s.type,values:s.values,default:s.default,writable:s.writable}}function ve(s){if(s.default!==void 0)return s.default;switch(s.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return s.values?.[0]??""}}function we(s){let e=new Map;for(let t of s)e.set(t.name,ve(t));return{get:t=>e.get(t),set:(t,r)=>{e.set(t,r)}}}function G(s){if(s.default!==void 0)return s.default;switch(s.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return s.values?.[0]??""}}function M(s){return typeof s=="boolean"?s:typeof s=="number"?s!==0:typeof s=="string"?s!=="":s.length>0}function ne(s,e){return s.gameDataFields?.[e]??[]}function j(s,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:s.find(r=>r.name===t)?.default}function se(s,e){let t={};for(let r of s){let n=j(s,e,r.name);n!==void 0&&(t[r.name]=n)}for(let[r,n]of Object.entries(e??{}))r in t||(t[r]=n);return t}return ge(xe);})();\n//# sourceMappingURL=patterplay.min.js.map';
335
+ var PLAYABLE_RUNTIME_JS = '"use strict";var Patterplay=(()=>{var P=Object.defineProperty;var le=Object.getOwnPropertyDescriptor;var pe=Object.getOwnPropertyNames;var de=Object.prototype.hasOwnProperty;var ue=(s,e)=>{for(var t in e)P(s,t,{get:e[t],enumerable:!0})},ge=(s,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of pe(e))!de.call(s,n)&&n!==t&&P(s,n,{get:()=>e[n],enumerable:!(r=le(e,n))||r.enumerable});return s};var fe=s=>ge(P({},"__esModule",{value:!0}),s);var De={};ue(De,{Engine:()=>B,Flow:()=>v,buildTagIndex:()=>O,effectiveGameData:()=>oe,gameDataFields:()=>ie,gameDataValue:()=>j});function S(s){switch(s[0]){case"b":return{kind:"bool",value:s[1]};case"n":return{kind:"number",value:s[1]};case"s":return{kind:"string",value:s[1]};case"sv":return{kind:"scopedvar",scope:s[1],name:s[2]};case"u":return{kind:"unary",op:s[1],operand:S(s[2])};case"bin":return{kind:"binary",op:s[1],left:S(s[2]),right:S(s[3])};case"call":{let e=s.slice(2).map(S);return{kind:"call",name:s[1],args:e}}case"fd":return{kind:"flagdelta",sign:s[1],name:s[2]}}}var p=class extends Error{constructor(e){super(e),this.name="EvalError"}};function E(s,e,t){let r=new Map(t.scopes.map(i=>[i.token,i.missing??"false"])),n=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 p(`@${i.scope}.${i.name} is not declared on the current ${i.scope}.`);return!1}return a}case"call":{let o=t.functions[i.name];if(!o)throw new p(`unknown function \'${i.name}\'`);return o.eval(i.args,{evaluate:n,ctx:e})}case"flagdelta":throw new p("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(i.op==="not"){let a=n(i.operand);if(typeof a!="boolean")throw new p(`\'not\' requires a boolean operand, got ${typeof a}`);return!a}let o=n(i.operand);if(typeof o!="number")throw new p(`unary \'-\' requires a numeric operand, got ${typeof o}`);return-o}case"binary":{if(i.op==="and"){let l=n(i.left);if(typeof l!="boolean")throw new p(`\'and\' requires boolean operands, left is ${typeof l}`);if(!l)return!1;let c=n(i.right);if(typeof c!="boolean")throw new p(`\'and\' requires boolean operands, right is ${typeof c}`);return c}if(i.op==="or"){let l=n(i.left);if(typeof l!="boolean")throw new p(`\'or\' requires boolean operands, left is ${typeof l}`);if(l)return!0;let c=n(i.right);if(typeof c!="boolean")throw new p(`\'or\' requires boolean operands, right is ${typeof c}`);return c}let o=n(i.left),a=n(i.right);switch(i.op){case"==":return U(o,a);case"!=":return!U(o,a);case">":return y(o,a,">"),o>a;case">=":return y(o,a,">="),o>=a;case"<":return y(o,a,"<"),o<a;case"<=":return y(o,a,"<="),o<=a;case"+":if(typeof o=="number"&&typeof a=="number"||typeof o=="string"&&typeof a=="string")return o+a;throw new p(`\'+\' requires two numbers or two strings, got ${typeof o} and ${typeof a}`);case"-":return y(o,a,"-"),o-a;case"*":return y(o,a,"*"),o*a;case"/":if(y(o,a,"/"),a===0)throw new p("division by zero");return o/a}}}};return n(s)}function U(s,e){if(Array.isArray(s)||Array.isArray(e)){if(!Array.isArray(s)||!Array.isArray(e)||s.length!==e.length)return!1;for(let t=0;t<s.length;t++)if(s[t]!==e[t])return!1;return!0}return s===e}function y(s,e,t){if(typeof s!="number"||typeof e!="number")throw new p(`\'${t}\' requires numeric operands, got ${typeof s} and ${typeof e}`)}var he={name:"check_flags",count:s=>Math.max(1,s.args.length-1)},me=[he];function W(s,e,t){let r=t?.countingCalls??me;return C(s,t?.want??!0,e,r)}function C(s,e,t,r){if(s.kind==="binary"&&(s.op==="and"||s.op==="or")){let n=C(s.left,e,t,r),i=C(s.right,e,t,r);return s.op==="and"===e?n>0&&i>0?n+i:0:Math.max(n,i)}if(s.kind==="unary"&&s.op==="not")return C(s.operand,!e,t,r);if(s.kind==="call"){let n=r.find(i=>i.name===s.name);if(n){let i=n.count(s),o=t(s);return e?o?i:0:o?0:1}}return t(s)===e?1:0}var w=class{scopes=new Map;defineOwned(e,t){this.assertFree(e);let r={},n=new Map;for(let i of t){let o=i.name.toLowerCase();n.set(o,i),r[o]=i.default??K(i)}return this.scopes.set(e,{kind:"owned",bag:r,decls:n}),this}reseedOwned(e,t){let r=this.scopes.get(e);if(!r||r.kind!=="owned")throw new Error(`\'@${e}\' is not an owned scope`);for(let n of Object.keys(r.bag))delete r.bag[n];r.decls.clear();for(let n of t){let i=n.name.toLowerCase();r.decls.set(i,n),r.bag[i]=n.default??K(n)}return this}defineForeign(e,t,r=[],n=!0){this.assertFree(e);let i=new Map;for(let o of r)i.set(o.name.toLowerCase(),o);return this.scopes.set(e,{kind:"foreign",resolver:t,decls:i,scopeWritable:n}),this}has(e){return this.scopes.has(e)}get(e,t){let r=this.scopes.get(e);if(!r)return;let n=t.toLowerCase();return r.kind==="owned"?r.bag[n]:r.resolver.get(n)}set(e,t,r){let n=this.scopes.get(e);if(!n)throw new Error(`unknown scope \'@${e}\'`);let i=t.toLowerCase();if(!this.writable(n,i))throw new Error(`\'@${e}.${t}\' is read-only`);n.kind==="owned"?n.bag[i]=r:n.resolver.set(i,r)}writable(e,t){return e.kind==="owned"?e.decls.get(t)?.writable??!0:e.resolver.set?e.decls.get(t)?.writable??e.scopeWritable:!1}toEvalContext(e){let t={};for(let[r,n]of this.scopes)t[r]=n.kind==="owned"?n.bag:n.resolver;return{scopes:t,host:e}}toSchema(){let e=new Map;for(let[t,r]of this.scopes){if(r.decls.size===0)continue;let n=new Map;for(let[i,o]of r.decls)n.set(i,{type:o.type,enumValues:o.values});e.set(t,n)}return{properties:e}}save(){let e={};for(let[t,r]of this.scopes)r.kind==="owned"&&(e[t]={...r.bag});return e}load(e){for(let[t,r]of Object.entries(e)){let n=this.scopes.get(t);n?.kind==="owned"&&Object.assign(n.bag,r)}}assertFree(e){if(this.scopes.has(e))throw new Error(`scope \'@${e}\' is already registered`)}};function K(s){if(s.default!==void 0)return s.default;switch(s.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return s.values?.[0]??"";case"flags":return[]}}function x(s){return s.ctx.host??{}}var T={defaultScope:"patter",scopes:[{token:"patter"},{token:"scene"}],functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(s,e){if(s.length!==2)throw new p("random(a, b) requires exactly 2 arguments");let t=x(e).nextRandom;if(!t)throw new p("random() called without a PRNG in context");let r=e.evaluate(s[0]),n=e.evaluate(s[1]);if(typeof r!="number"||typeof n!="number")throw new p("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(n))throw new p("random(a, b) arguments must be integers");let i=Math.min(r,n),o=Math.max(r,n);return Math.floor(t()*(o-i+1))+i}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,validate:z("check_flags"),eval(s,e){let t=J(s[0],e,"check_flags");for(let r=1;r<s.length;r++){let n=s[r];if(n.kind!=="flagdelta")throw new p("check_flags() flag args must be +flagName or -flagName");if(n.sign==="+"?!t.includes(n.name):t.includes(n.name))return!1}return!0}},set_flags:{minArgs:1,returnType:"flags",flagDeltaArgs:!0,validate:z("set_flags"),eval(s,e){let t=[...J(s[0],e,"set_flags")];for(let r=1;r<s.length;r++){let n=s[r];if(n.kind!=="flagdelta")throw new p("set_flags() flag args must be +flagName or -flagName");if(n.sign==="+")t.includes(n.name)||t.push(n.name);else{let i=t.indexOf(n.name);i>=0&&t.splice(i,1)}}return t}},visits:{minArgs:1,maxArgs:1,returnType:"number",validate:I("visits"),eval:(s,e)=>x(e).visits?.(D(s,e,"visits"))??0},seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:I("seen"),eval:(s,e)=>(x(e).visits?.(D(s,e,"seen"))??0)>0},patter_visits:{minArgs:1,maxArgs:1,returnType:"number",validate:I("patter_visits"),eval:(s,e)=>x(e).patterVisits?.(D(s,e,"patter_visits"))??0},patter_seen:{minArgs:1,maxArgs:1,returnType:"boolean",validate:I("patter_seen"),eval:(s,e)=>(x(e).patterVisits?.(D(s,e,"patter_seen"))??0)>0}}};function F(s,e){let t=s.replace(/^@/,"").split(".");return t.length===2&&e(t[0])?{scope:t[0],name:t[1].toLowerCase()}:{scope:"patter",name:t.join(".").toLowerCase()}}function D(s,e,t){let r=e.evaluate(s[0]);if(typeof r!="string")throw new p(`${t}(id) requires a string node id`);return r}function I(s){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:`${s}(id): the argument must be a string id literal (a scene / block / node id)`})}}function J(s,e,t){if(!s)throw new p(`${t}() requires at least one argument (the flags variable)`);let r=e.evaluate(s);if(Array.isArray(r))return r;if(r===!1||r===null||r===void 0)return[];throw new p(`${t}() first argument must be a flags property`)}function z(s){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:`${s}(): first argument must be a flags property reference (@name or @scope.name)`});return}let n=t.schema.properties.get(r.scope)?.get(r.name);if(n&&n.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:`${s}(): \'@${i}\' is not a flags property (got ${n.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:`${s}(): argument ${i+1} must be +flagName or -flagName`}):n?.type==="flags"&&n.enumValues&&!n.enumValues.includes(o.name)&&t.report({path:[...t.path,"args",i],kind:"unknown-flag-name",severity:"error",message:`${s}(): unknown flag \'${o.name}\'`,reference:o.name})}}}var Se=/^@[A-Za-z0-9_.]+$/;function*ye(s){let e="",t=0;for(;t<s.length;){let r=s[t];if(r==="{"&&s[t+1]==="{"){e+="{",t+=2;continue}if(r==="}"&&s[t+1]==="}"){e+="}",t+=2;continue}if(r==="{"){let n=s.indexOf("}",t+1);if(n!==-1){let i=s.slice(t,n+1),o=s.slice(t+1,n).trim();if(o.startsWith("@")){e&&(yield{kind:"text",value:e},e=""),yield{kind:"slot",raw:i,inner:o,ref:Se.test(o)?o:void 0},t=n+1;continue}e+=i,t=n+1;continue}}e+=r,t+=1}e&&(yield{kind:"text",value:e})}function be(s){return Array.isArray(s)?s.join(", "):typeof s=="boolean"?s?"true":"false":String(s)}function ve(s){return s===" "||s===" "||s===`\n`||s==="\\r"||s==="\\f"||s==="\\v"}function we(s){let e="",t=!1;for(let r of s){if(ve(r)){t=!0;continue}t&&e.length>0&&(e+=" "),t=!1,e+=r}return e}function X(s,e,t){if(e.length===0||s.indexOf(e)<0)return s;let r="",n=0,i=!1;for(;n<s.length;){if(s.startsWith(e,n)){let o=s.indexOf(t,n+e.length);if(o>=0){n=o+t.length,i=!0;continue}r+=s.slice(n);break}r+=s[n],n+=1}return i?we(r):s}function Z(s,e){if(s.indexOf("{")<0)return s;let t="";for(let r of ye(s)){if(r.kind==="text"){t+=r.value;continue}if(!r.ref){t+=r.raw;continue}let n=e(r.ref);t+=n===void 0?"":be(n)}return t}function k(s,e){for(let t of s){e(t);let r=t.children;r&&k(r,e)}}function xe(s){return s.toLowerCase().replace(/[\'\u2019]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function h(s){let e=s.gameId?.trim();return e||xe(s.name)}function V(s){return`cast:${s}`}var G={open:"[",close:"]"},Q="SFX";function R(s){let e=new Set,t=[];for(let r of s)e.has(r)||(e.add(r),t.push(r));return t}function O(s){let e=new Map,t=(r,n)=>{let i=R([...n,...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,R([...i,...o.tags??[]]))};for(let r of Object.values(s.scenes)){let n=R(r.tags??[]);e.set(r.id,n);for(let i of r.blocks){let o=R([...n,...i.tags??[]]);e.set(i.id,o);for(let a of i.children)t(a,o)}}return e}var Y=new WeakMap,B=class s{host;defaultSeed;flowsById=new Map;allStrings;currentLocale;sourceDebug;sceneGameIdToId=new Map;blockGameIdToId=new Map;creationOptions;constructor(e,t={}){this.creationOptions=t;let r=t.locale??e.locales.default,n=e.strings;this.allStrings=n,this.currentLocale=r;let i=n[r]??{},o=n[e.locales.default]??{},a=e.localisation,l=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 c=new Map;for(let d of e.cast??[])d.displayName&&c.set(d.name,d.displayName);this.defaultSeed=(t.seed??2654435769)>>>0;let u=new Map,g=new Map,L=new Map;for(let[d,m]of Object.entries(e.scenes)){this.sceneGameIdToId.set(h(m),d);let b=new Map;for(let f of m.blocks)g.set(f.id,{sceneId:d}),L.set(f.id,f),b.set(h(f),f.id),k(f.children,q=>u.set(q.id,q));this.blockGameIdToId.set(d,b)}let $=e.properties??[],N=$.filter(d=>d.shared??!0).map(ee),ae=$.filter(d=>!(d.shared??!0)).map(ee),ce=new Set(N.map(d=>d.name.toLowerCase())),A=new w().defineOwned("patter",N),_=new Set;if(t.world){let d=e.scopeRegistry?.scopes.find(b=>b.token==="world"),m=(d?.declarations??[]).map(te);A.defineForeign("world",t.world,m,d?.writable??!0),_.add("world")}for(let d of e.scopeRegistry?.scopes??[]){if(_.has(d.token))continue;let m=(d.declarations??[]).map(te);A.defineForeign(d.token,Ce(d.declarations??[]),m,d.writable??!0)}let H=new Map;for(let[d,m]of Object.entries(e.scenes)){let b=new Set((m.sceneProps??[]).filter(f=>f.shared??!1).map(f=>f.name.toLowerCase()));H.set(d,b)}this.host={bundle:e,emitIds:l,strings:i,defaultStrings:o,castDisplay:c,nodeIndex:u,blockIndex:g,blockById:L,tagIndex:O(e),shared:A,patterSharedDecls:N,patterLocalDecls:ae,patterSharedNames:ce,sceneSharedNames:H,sharedVisits:new Map,sharedSelectors:new Map,stageBags:new Map,customRng:t.rng,onDryChoice:t.onDryChoice,replayPromptOnChoose:t.replayPromptOnChoose??!1,captionsOn:t.closedCaptions??!0,captionOpen:(e.closedCaptions??G).open,captionClose:(e.closedCaptions??G).close,captionCharacter:e.closedCaptions?.character||Q,refSplitCache:new Map}}get locale(){return this.currentLocale}get isSourceDebug(){return this.sourceDebug}setLocale(e){this.currentLocale=e,this.host.strings=this.allStrings[e]??{}}replaceStrings(e){this.allStrings=e.strings,this.host.strings=this.allStrings[this.currentLocale]??{},this.host.defaultStrings=this.allStrings[this.host.bundle.locales.default]??{}}hotSwap(e){let t=this.saveGame(),r=i=>(i.setLocale(this.currentLocale),i.setClosedCaptions(this.host.captionsOn),i),n=new s(e,this.creationOptions);try{return n.loadGame(t),r(n)}catch{let i=new s(e,this.creationOptions);for(let[o,a]of Object.entries(t.flows)){let l=a.cursor.currentSceneId;try{i.openFlow(o,l!==null?{scene:l}:{})}catch{}}return r(i)}}get closedCaptions(){return this.host.captionsOn}setClosedCaptions(e){this.host.captionsOn=e}openFlow(e,t={}){let r=this.resolveSceneRef(t.scene),n=this.resolveBlockRef(r,t.block),i=new v(e,this.host,t.seed??this.defaultSeed);return this.flowsById.set(e,i),i.start(r,n),i}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?h(t):void 0}blockAddress(e){let t=this.host.blockById.get(e);return t?h(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),n=this.resolveBlockRef(r,t);return(n!=null?this.host.tagIndex.get(n):void 0)??[]}getOutline(){return Object.values(this.host.bundle.scenes).map(e=>({id:e.id,...h(e)?{gameId:h(e)}:{},name:e.name,...this.tagsField(e.id),blocks:e.blocks.map(t=>({id:t.id,...h(t)?{gameId:h(t)}:{},name:t.name,...this.tagsField(t.id),children:t.children.map(r=>this.outlineNode(r))}))}))}getBeatSequence(){let e=[];for(let t of Object.values(this.host.bundle.scenes))for(let r of t.blocks)k(r.children,n=>{if(n.type==="snippet")for(let i of n.beats??[])e.push({sceneId:t.id,blockId:r.id,snippetId:n.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 n=this.host.defaultStrings[V(e.character)]??this.host.castDisplay.get(e.character);n!==void 0&&(r.characterName=n)}e.direction!==void 0&&(r.direction=e.direction)}if(e.kind==="line"||e.kind==="text"){let n=this.host.defaultStrings[e.id];n!==void 0&&(r.text=n)}return e.gameData&&Object.keys(e.gameData).length&&(r.gameData=e.gameData),t&&t.length&&(r.tags=t),r}tagsField(e){let t=this.host.tagIndex.get(e);return t&&t.length?{tags:t}:{}}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){this.flowsById.delete(e)}reset(){this.flowsById.clear(),this.host.shared.reseedOwned("patter",this.host.patterSharedDecls),this.host.sharedVisits.clear(),this.host.sharedSelectors.clear(),this.host.stageBags.clear()}getProperty(e){let{scope:t,name:r}=this.splitShared(e);return this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:n}=this.splitShared(e);this.host.shared.set(r,n,t)}listProperties(){return this.host.patterSharedDecls.map(e=>({ref:`@${e.name}`,type:e.type,values:e.values,value:this.getProperty(`@${e.name}`),default:ke(e)}))}splitShared(e){let t=this.host.refSplitCache.get(e);if(t||(t=F(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t.scope==="scene")throw new Error(`\'${e}\': @scene properties are scene-scoped - read/write them on a Flow, not the Engine`);return t}save(){return this.host.shared.save()}load(e){this.host.shared.load(e)}saveGame(){let e={};for(let[t,r]of this.flowsById)e[t]=r.snapshot();return{version:2,shared:this.host.shared.save(),sharedVisits:Object.fromEntries(this.host.sharedVisits),sharedSelectors:ne(this.host.sharedSelectors),stageBags:Object.fromEntries([...this.host.stageBags].map(([t,r])=>[t,{...r}])),flows:e}}loadGame(e){if(e.version!==2)throw new Error(`unsupported save version: ${e.version}`);this.host.shared.load(e.shared),this.host.sharedVisits.clear();for(let[t,r]of Object.entries(e.sharedVisits??{}))this.host.sharedVisits.set(t,r);this.host.sharedSelectors.clear();for(let[t,r]of se(e.sharedSelectors))this.host.sharedSelectors.set(t,r);this.host.stageBags.clear();for(let[t,r]of Object.entries(e.stageBags??{}))this.host.stageBags.set(t,{...r});this.flowsById.clear();for(let[t,r]of Object.entries(e.flows)){let n=new v(t,this.host,this.defaultSeed);n.restore(r),this.flowsById.set(t,n)}}},v=class{id;host;local;rngState;started=!1;flowEnded=!1;currentSceneId=null;stack=[];activeSnippet=null;beatIndex=0;pendingChoice=null;pendingPromptBeat=null;pendingPromptOwnerId=null;selectors=new Map;visitCounts=new Map;sceneBags=new Map;patterResolver={get:e=>this.host.patterSharedNames.has(e)?this.host.shared.get("patter",e):this.local.get("patter",e),set:(e,t)=>{this.host.patterSharedNames.has(e)?this.host.shared.set("patter",e,t):this.local.set("patter",e,t)}};sceneResolver={get:e=>{let t=this.currentSceneId;return t===null?void 0:(this.host.sceneSharedNames.get(t)?.has(e)?this.host.stageBags.get(t):this.sceneBags.get(t))?.[e]},set:(e,t)=>{let r=this.currentSceneId;if(r===null)return;let n=this.host.sceneSharedNames.get(r)?.has(e)?this.host.stageBags.get(r):this.sceneBags.get(r);n&&(n[e]=t)}};evalCtx;constructor(e,t,r){this.id=e,this.host=t,this.rngState=r>>>0,this.local=this.freshLocal();let n={...t.shared.toEvalContext().scopes};n.patter=this.patterResolver,n.scene=this.sceneResolver,this.evalCtx={scopes:n,host:{nextRandom:this.rng,visits:i=>this.visitCounts.get(i)??0,patterVisits:i=>this.host.sharedVisits.get(i)??0}}}start(e,t){if(this.sceneBags.clear(),this.local=this.freshLocal(),this.selectors.clear(),this.visitCounts.clear(),this.stack=[],this.currentSceneId=null,this.flowEnded=!1,this.activeSnippet=null,this.beatIndex=0,this.pendingChoice=null,this.started=!0,t){let r=this.host.blockIndex.get(t);if(!r)throw new Error(`unknown block: ${t}`);this.enterSceneSetup(r.sceneId),this.stack=[{sceneId:r.sceneId,containerId:t,index:0}],this.enter(t)}else{let r=e??Object.keys(this.host.bundle.scenes)[0],n=r?this.host.bundle.scenes[r]:void 0;if(!n)throw new Error(r?`unknown scene: ${r}`:"no scenes in bundle");this.enterSceneSetup(r);let i=n.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)}get currentScene(){return this.currentSceneId}advance(){if(!this.started)throw new Error("flow has not been started");if(this.pendingPromptBeat){let e=this.pendingPromptBeat;return this.pendingPromptBeat=null,this.pendingPromptOwnerId=null,this.beatResult(e)}return this.settle(),this.flowEnded?{type:"end"}:this.pendingChoice?{type:"choice",groupId:this.pendingChoice.groupId,options:this.pendingChoice.options}:this.activeSnippet?this.beatResult(this.activeSnippet.beats[this.beatIndex++]):(this.flowEnded=!0,{type:"end"})}advanceToStop(){let e=[];for(;;){let t=this.advance();if(t.type==="choice"||t.type==="end")return{played:e,stop:t};e.push(t)}}settle(){let e=0;for(;;){if(++e>1e4)throw new Error("flow did not settle after 10000 transitions - likely a jump cycle with no deliverable content");if(this.flowEnded||this.pendingChoice)return;if(this.activeSnippet){if(this.beatIndex<(this.activeSnippet.beats?.length??0))return;this.runEffects(this.activeSnippet.onExit);let n=this.activeSnippet.jump;this.activeSnippet=null,this.beatIndex=0,this.resolveJump(n);continue}let t=this.stack[this.stack.length-1];if(!t){this.flowEnded=!0;return}t.sceneId!==this.currentSceneId&&(this.currentSceneId=t.sceneId);let r=this.childrenOf(t.containerId);if(!r){this.stack.pop();continue}for(;t.index<r.length&&!this.eligible(r[t.index]);)t.index++;if(t.index>=r.length){this.stack.pop();continue}this.enterChild(r[t.index++])}}getChoices(){return this.pendingChoice?.options??[]}choose(e){let t=this.pendingChoice;if(!t)throw new Error("no choice is pending");let r=t.options.find(i=>i.id===e);if(!r)throw new Error(`unknown choice option: ${e}`);if(!r.eligible)throw new Error(`choice option is not eligible: ${e}`);let n=t.byId.get(e);this.pendingChoice=null,this.pendingPromptBeat=this.host.replayPromptOnChoose?this.promptBeatOf(n)??null:null,this.pendingPromptOwnerId=this.pendingPromptBeat?n.id:null,this.enterChild(n)}isEnded(){return this.flowEnded}getProperty(e){let{scope:t,name:r}=this.splitRef(e);return t==="patter"?this.patterResolver.get(r):t==="scene"?this.sceneResolver.get(r):this.host.shared.get(t,r)}setProperty(e,t){let{scope:r,name:n}=this.splitRef(e);if(r==="patter")this.patterResolver.set(n,t);else if(r==="scene"){if(this.currentSceneId===null)throw new Error(`\'${e}\': the flow has not entered a scene yet`);this.sceneResolver.set(n,t)}else this.host.shared.set(r,n,t)}snapshot(){return{scopes:this.local.save(),sceneBags:Object.fromEntries([...this.sceneBags].map(([e,t])=>[e,{...t}])),rngState:this.rngState,visits:Object.fromEntries(this.visitCounts),cursor:{flowEnded:this.flowEnded,currentSceneId:this.currentSceneId,stack:this.stack.map(e=>{let t=this.childrenOf(e.containerId)?.[e.index];return t?{...e,nextId:t.id}:{...e}}),activeSnippetId:this.activeSnippet?.id??null,beatIndex:this.beatIndex,pendingChoice:this.pendingChoice?{groupId:this.pendingChoice.groupId,options:this.pendingChoice.options.map(e=>({...e}))}:null,pendingPromptOwnerId:this.pendingPromptOwnerId,selectors:ne(this.selectors)}}}restore(e){this.rngState=e.rngState>>>0,this.visitCounts=new Map(Object.entries(e.visits??{}));let t=e.cursor;if(this.started=!0,this.flowEnded=t.flowEnded,this.beatIndex=t.beatIndex,this.currentSceneId=t.currentSceneId,this.stack=t.stack.map(r=>{let{nextId:n,...i}=r;if(n!==void 0){let o=this.childrenOf(i.containerId)?.findIndex(a=>a.id===n)??-1;if(o>=0)return{...i,index:o}}return{...i}}),this.sceneBags=new Map(Object.entries(e.sceneBags??{}).map(([r,n])=>[r,{...n}])),this.local=this.freshLocal(),this.local.load(e.scopes),this.activeSnippet=null,t.activeSnippetId!==null){let r=this.host.nodeIndex.get(t.activeSnippetId);r&&r.type==="snippet"&&(this.activeSnippet=r)}if(this.selectors=se(t.selectors),this.pendingChoice=null,t.pendingChoice!==null){let r=new Map,n=[];for(let i of t.pendingChoice.options){let o=this.host.nodeIndex.get(i.id);o&&(r.set(i.id,o),n.push({...i}))}n.length>0&&(this.pendingChoice={groupId:t.pendingChoice.groupId,options:n,byId:r})}if(this.pendingPromptBeat=null,this.pendingPromptOwnerId=t.pendingPromptOwnerId??null,this.pendingPromptOwnerId){let r=this.host.nodeIndex.get(this.pendingPromptOwnerId);this.pendingPromptBeat=r?this.promptBeatOf(r)??null:null,this.pendingPromptBeat||(this.pendingPromptOwnerId=null)}}enterSceneSetup(e){let t=this.host.bundle.scenes[e];if(!t)throw new Error(`unknown scene: ${e}`);this.currentSceneId=e,this.enter(e),this.seedScene(t),this.runEffects(t.onEntry)}enterChild(e){if(this.enter(e.id),e.type==="snippet"){this.beginSnippet(e);return}let t=e.selector??"run";if(t==="run"){this.stack.push({sceneId:this.currentSceneId,containerId:e.id,index:0});return}if(t==="choice"){this.setupChoice(e);return}let r=this.selectChild(e);r&&this.enterChild(r)}childrenOf(e){let t=this.host.blockById.get(e);if(t)return t.children;let r=this.host.nodeIndex.get(e);if(r&&r.type==="group")return r.children}beginSnippet(e){this.runEffects(e.onEnter),this.activeSnippet=e,this.beatIndex=0}setupChoice(e){let t=[],r=new Map,n=[];for(let o of e.children){if(o.fallback===!0){n.push(o);continue}if(o.sticky!==!0&&(this.visitCounts.get(o.id)??0)>=1)continue;let a=this.eligible(o),l=o.secretUntilEligible===!0;!a&&l||(t.push({id:o.id,prompt:this.promptFor(o),eligible:a,gameData:o.gameData}),r.set(o.id,o))}if(t.length>0){this.pendingChoice={groupId:e.id,options:t,byId:r};return}let i=n.find(o=>this.eligible(o));if(i){this.enterChild(i);return}this.host.onDryChoice?.(e.id)}resolveJump(e){e&&this.enterTarget(e.to,e.mode==="call"?"call":"jump")}enterTarget(e,t){if(e==="END"){this.flowEnded=!0,this.stack=[];return}let r,n,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,n=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,n=e}this.enter(n);let o={sceneId:r,containerId:n,index:0};t==="call"?this.stack.push(o):this.stack=[o]}selectChild(e){let t=e.children.filter(n=>this.eligible(n));if(t.length===0)return null;let r=this.selectorState(e);switch(e.selector){case"branch":return t[0];case"sequence":{let n=e.options?.order??"sequential",i=e.options?.exhaust??"once";return n==="shuffle"?this.pickShuffle(t,i,r):n==="specificity"?this.pickSpecificity(t,i,r):this.pickSequential(t,i,r)}default:return null}}pickSequential(e,t,r){let n=e.length,i=r.seq??0;return r.seq=i+1,t==="repeat"?e[i%n]:i<n?e[i]:t==="stick"?e[n-1]:null}pickShuffle(e,t,r){let n=e.length,i=t==="stick",o=()=>(i?e.slice(0,n-1):e).map(g=>g.id);if(r.bag===void 0&&(r.bag=o()),r.bag.length===0){if(t==="once")return null;if(i){let g=e[n-1];return r.last=g.id,g}r.bag=o()}let a=r.bag,l=r.last!==void 0&&a.length>1?a.indexOf(r.last):-1,c=Math.floor(this.rng()*(l>=0?a.length-1:a.length));l>=0&&c>=l&&c++;let u=a[c];return a.splice(c,1),r.last=u,e.find(g=>g.id===u)}pickSpecificity(e,t,r){let n=e;if(t!=="repeat"){r.bag===void 0&&(r.bag=e.map(u=>u.id));let c=new Set(r.bag);if(n=e.filter(u=>c.has(u.id)),n.length===0)return t==="stick"&&r.last!==void 0?e.find(u=>u.id===r.last)??null:null}let i=-1,a=n.map(c=>{let u=this.specScore(c);return u>i&&(i=u),{c,s:u}}).filter(c=>c.s===i).map(c=>c.c),l;if(a.length===1)l=a[0];else{let c=r.last!==void 0?a.findIndex(g=>g.id===r.last):-1,u=Math.floor(this.rng()*(c>=0?a.length-1:a.length));c>=0&&u>=c&&u++,l=a[u]}return t!=="repeat"&&(r.bag=r.bag.filter(c=>c!==l.id)),r.last=l.id,l}specScore(e){return e.condition?this.matchedSpec(this.conditionAst(e.condition),!0):0}matchedSpec(e,t){return W(e,n=>re(E(n,this.evalCtx,T)),{want:t})}selectorState(e){let t=e.shared?this.host.sharedSelectors:this.selectors,r=t.get(e.id);return r||(r={},t.set(e.id,r)),r}runEffects(e){for(let t of e??[])this.setProperty(t.target,this.evalExpr(t.value))}eligible(e){return e.condition?re(this.evalExpr(e.condition)):!0}evalExpr(e){return E(this.conditionAst(e),this.evalCtx,T)}conditionAst(e){let t=Y.get(e);return t||(t=S(e.ast),Y.set(e,t)),t}enter(e){this.visitCounts.set(e,(this.visitCounts.get(e)??0)+1),this.host.sharedVisits.set(e,(this.host.sharedVisits.get(e)??0)+1)}rng=()=>{if(this.host.customRng)return this.host.customRng();let e=this.rngState+1831565813|0;this.rngState=e;let t=Math.imul(e^e>>>15,1|e);return t=t+Math.imul(t^t>>>7,61|t)^t,((t^t>>>14)>>>0)/4294967296};beatResult(e){let t=this.host.tagIndex.get(e.id),r=t&&t.length?{tags:t}:{};switch(e.kind){case"gameEvent":return{type:"gameEvent",id:e.id,gameData:e.gameData,...r};case"text":return{type:"text",id:e.id,text:this.interpolate(this.resolveString(e.id)),gameData:e.gameData,...r};case"line":{let n=this.resolveString(e.id),i=!this.host.captionsOn,a=i&&e.character===this.host.captionCharacter?"":this.captionLine(this.host.bundle.voiced?n:this.interpolate(n)),l=i&&a.length===0;return{type:"line",id:e.id,text:a,character:l?void 0:e.character,characterName:l?void 0:this.resolveCharacterName(e.character),direction:l?void 0:e.direction,gameData:e.gameData,...r}}}}interpolate(e){return Z(e,t=>this.getProperty(t))}stripCaptions(e){return X(e,this.host.captionOpen,this.host.captionClose)}captionLine(e){return this.host.captionsOn?e:this.stripCaptions(e)}promptFor(e){let t=this.promptBeatOf(e);if(!t)return;let r=this.interpolate(this.resolveString(t.id));return t.kind==="line"?{kind:"line",text:this.captionLine(r),character:t.character,characterName:this.resolveCharacterName(t.character),direction:t.direction}:{kind:"text",text:r}}promptBeatOf(e){return e.type==="group"&&e.prompt?e.prompt:((e.type==="snippet"?e:this.firstTextSnippetIn(e.children))?.beats??[]).find(r=>r.kind==="line"||r.kind==="text")}firstTextSnippetIn(e){let t;return k(e,r=>{!t&&r.type==="snippet"&&(r.beats??[]).some(n=>n.kind==="line"||n.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=V(e);return this.host.strings[t]??this.host.defaultStrings[t]??this.host.castDisplay.get(e)}splitRef(e){let t=this.host.refSplitCache.get(e);return t||(t=F(e,r=>r==="scene"||this.host.shared.has(r)),this.host.refSplitCache.set(e,t)),t}freshLocal(){return new w().defineOwned("patter",this.host.patterLocalDecls)}seedScene(e){let t=this.host.sceneSharedNames.get(e.id)??new Set;if(!this.sceneBags.has(e.id)){let r={};for(let n of e.sceneProps??[]){let i=n.name.toLowerCase();t.has(i)||(r[i]=M(n))}this.sceneBags.set(e.id,r)}if(!this.host.stageBags.has(e.id)){let r={};for(let n of e.sceneProps??[]){let i=n.name.toLowerCase();t.has(i)&&(r[i]=M(n))}this.host.stageBags.set(e.id,r)}for(let r of e.sceneProps??[]){if(!r.temporary)continue;let n=r.name.toLowerCase(),i=t.has(n)?this.host.stageBags.get(e.id):this.sceneBags.get(e.id);i&&(i[n]=M(r))}}};function ne(s){let e={};for(let[t,r]of s){let n={};r.seq!==void 0&&(n.seq=r.seq),r.bag&&(n.bag=[...r.bag]),r.last!==void 0&&(n.last=r.last),e[t]=n}return e}function se(s){let e=new Map;for(let[t,r]of Object.entries(s??{})){let n={};r.seq!==void 0&&(n.seq=r.seq),r.bag&&(n.bag=[...r.bag]),r.last!==void 0&&(n.last=r.last),e.set(t,n)}return e}function ee(s){return{name:s.name,type:s.type,values:s.values,default:s.default}}function ke(s){if(s.default!==void 0)return s.default;switch(s.type){case"number":return 0;case"string":return"";case"flags":return[];case"enum":return s.values?.[0]??"";default:return!1}}function te(s){return{name:s.name,type:s.type,values:s.values,default:s.default,writable:s.writable}}function Ee(s){if(s.default!==void 0)return s.default;switch(s.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return s.values?.[0]??""}}function Ce(s){let e=new Map;for(let t of s)e.set(t.name,Ee(t));return{get:t=>e.get(t),set:(t,r)=>{e.set(t,r)}}}function M(s){if(s.default!==void 0)return s.default;switch(s.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"flags":return[];case"enum":return s.values?.[0]??""}}function re(s){return typeof s=="boolean"?s:typeof s=="number"?s!==0:typeof s=="string"?s!=="":s.length>0}function ie(s,e){return s.gameDataFields?.[e]??[]}function j(s,e,t){return e&&Object.prototype.hasOwnProperty.call(e,t)?e[t]:s.find(r=>r.name===t)?.default}function oe(s,e){let t={};for(let r of s){let n=j(s,e,r.name);n!==void 0&&(t[r.name]=n)}for(let[r,n]of Object.entries(e??{}))r in t||(t[r]=n);return t}return fe(De);})();\n//# sourceMappingURL=patterplay.min.js.map';
336
336
 
337
337
  // src/export-html.ts
338
338
  var esc = (s) => s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
@@ -560,6 +560,10 @@ import { exportBundle as exportBundle3 } from "@patterkit/compiler";
560
560
  import { Engine } from "@patterkit/runtime";
561
561
 
562
562
  // src/loaded-helpers.ts
563
+ import { RERECORD_STATUS } from "@patterkit/model";
564
+ function effectiveRecording(id, base, rerecord, lowest) {
565
+ return rerecord.has(id) ? RERECORD_STATUS : base.get(id) ?? lowest;
566
+ }
563
567
  function tableFor(loaded, locale) {
564
568
  const table = {};
565
569
  for (const l of loaded.locales) if (l.locale === locale) Object.assign(table, l.strings);
@@ -586,12 +590,14 @@ function mergeAuthoring(loaded) {
586
590
  const writing = /* @__PURE__ */ new Map();
587
591
  const recording = /* @__PURE__ */ new Map();
588
592
  const cut = /* @__PURE__ */ new Set();
593
+ const rerecord = /* @__PURE__ */ new Set();
589
594
  const documentation = /* @__PURE__ */ new Map();
590
595
  const edits = /* @__PURE__ */ new Map();
591
596
  for (const a of loaded.authoring) {
592
597
  for (const [id, v] of Object.entries(a.writing ?? {})) writing.set(id, v);
593
598
  for (const [id, v] of Object.entries(a.recording ?? {})) recording.set(id, v);
594
599
  for (const [id, v] of Object.entries(a.cut ?? {})) if (v) cut.add(id);
600
+ for (const [id, v] of Object.entries(a.rerecord ?? {})) if (v) rerecord.add(id);
595
601
  for (const [id, lines] of Object.entries(a.documentation ?? {})) {
596
602
  const cur = documentation.get(id);
597
603
  if (cur) cur.push(...lines);
@@ -599,7 +605,7 @@ function mergeAuthoring(loaded) {
599
605
  }
600
606
  for (const [id, v] of Object.entries(a.edits ?? {})) edits.set(id, v);
601
607
  }
602
- return { writing, recording, cut, documentation, edits };
608
+ return { writing, recording, cut, rerecord, documentation, edits };
603
609
  }
604
610
 
605
611
  // src/play.ts
@@ -991,7 +997,7 @@ function runFormat(files) {
991
997
  }
992
998
 
993
999
  // src/report.ts
994
- import { walkNodes as walkNodes2, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2 } from "@patterkit/model";
1000
+ import { walkNodes as walkNodes2, DEFAULT_WRITING_STATUSES, DEFAULT_RECORDING_STATUSES as DEFAULT_RECORDING_STATUSES2, RERECORD_STATUS as RERECORD_STATUS2 } from "@patterkit/model";
995
1001
  var DEFAULT_WORDS_PER_LINE = 6;
996
1002
  var UNATTRIBUTED = "(unattributed)";
997
1003
  function allocateLargestRemainder(total, weights) {
@@ -1021,8 +1027,9 @@ function runReport(loaded, recordingOverride) {
1021
1027
  const thresholdIdx = estimatingOn ? writingIndex.get(est?.thresholdStatus ?? "") ?? 0 : -1;
1022
1028
  const defaultLines = est?.defaultLines ?? 0;
1023
1029
  const tagMap = new Map((est?.tagEstimates ?? []).map((t) => [t.tag, t.lines]));
1024
- const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, edits: editsOf } = mergeAuthoring(loaded);
1025
- const recordingOf = recordingOverride ?? manualRecordingOf;
1030
+ const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, edits: editsOf } = mergeAuthoring(loaded);
1031
+ const recordingBase = recordingOverride ?? manualRecordingOf;
1032
+ const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
1026
1033
  const byLocale = stringsByLocale(loaded);
1027
1034
  const sourceStrings2 = byLocale.get(project.locales.default) ?? {};
1028
1035
  const wordsOf = (id) => {
@@ -1135,7 +1142,7 @@ function runReport(loaded, recordingOverride) {
1135
1142
  const wi = writingIndex.get(ws) ?? 0;
1136
1143
  if (recordThreshold !== -1 && wi >= recordThreshold) voiced.readyToRecord++;
1137
1144
  if (shipThreshold !== -1 && wi >= shipThreshold) voiced.readyToShip++;
1138
- const rec = recordingOf.get(u.id) ?? recordingLadder[0];
1145
+ const rec = recordingOf(u.id);
1139
1146
  voiced.byRecording[rec] = (voiced.byRecording[rec] ?? 0) + 1;
1140
1147
  const speaker = u.character ?? "(narrator)";
1141
1148
  const ch = getChar(speaker);
@@ -1170,13 +1177,14 @@ function runReport(loaded, recordingOverride) {
1170
1177
  const scenesByStatus = Object.fromEntries(writingLadder.map((s) => [s, 0]));
1171
1178
  for (const s of reports) if (s.status) scenesByStatus[s.status] = (scenesByStatus[s.status] ?? 0) + 1;
1172
1179
  const coverage = { totalScenes: reports.length, estimated: reports.filter((s) => s.estimated).length };
1180
+ const reportedRecordingLadder = (totals.voiced.byRecording[RERECORD_STATUS2] ?? 0) > 0 ? [...recordingLadder, RERECORD_STATUS2] : recordingLadder;
1173
1181
  return {
1174
1182
  project: { id: project.project.id, name: project.project.name },
1175
1183
  voiced: project.voiced ?? false,
1176
1184
  // Recording tracking is opt-in (default off) even for a voiced project (#206).
1177
1185
  recordingTracked: (project.voiced ?? false) && (project.trackAudioStatus ?? false),
1178
1186
  writingLadder,
1179
- recordingLadder,
1187
+ recordingLadder: reportedRecordingLadder,
1180
1188
  scenes: reports,
1181
1189
  characters: [...characters.values()].sort((a, b) => b.lines + b.estimatedLines - (a.lines + a.estimatedLines)),
1182
1190
  locales,
@@ -1420,12 +1428,18 @@ function extractLoc(loaded, opts = {}) {
1420
1428
  const localised = e?.localisedAt?.[targetLocale];
1421
1429
  return !!(e?.modifiedAt && localised && e.modifiedAt > localised);
1422
1430
  };
1431
+ const genderOf = /* @__PURE__ */ new Map();
1432
+ for (const c of loaded.project.cast ?? []) if (c.gender) genderOf.set(c.name, c.gender);
1433
+ const withGender = (context) => {
1434
+ const g = context?.character ? genderOf.get(context.character) : void 0;
1435
+ return g ? { ...context, gender: g } : context;
1436
+ };
1423
1437
  const entries = [];
1424
1438
  const push = (id, scene, context) => {
1425
1439
  const src = source[id];
1426
1440
  if (src === void 0) return;
1427
1441
  const translation = isTemplate ? "" : target[id] ?? "";
1428
- entries.push({ id, scene, source: src, translation, comments: commentsOf(id), context, stale: staleFor(id, translation) });
1442
+ entries.push({ id, scene, source: src, translation, comments: commentsOf(id), context: withGender(context), stale: staleFor(id, translation) });
1429
1443
  };
1430
1444
  for (const scene of loaded.scenes) {
1431
1445
  for (const block of scene.blocks) {
@@ -1446,7 +1460,7 @@ function extractLoc(loaded, opts = {}) {
1446
1460
  const id = castStringKey(c.name);
1447
1461
  const src = source[id] ?? c.displayName;
1448
1462
  const translation = isTemplate ? "" : target[id] ?? "";
1449
- entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: commentsOf(id), context: { character: c.name }, stale: staleFor(id, translation) });
1463
+ entries.push({ id, scene: PROJECT_LOCALE_SCENE, source: src, translation, comments: commentsOf(id), context: withGender({ character: c.name }), stale: staleFor(id, translation) });
1450
1464
  }
1451
1465
  return { project: loaded.project.project.id, defaultLocale, locale: targetLocale, entries };
1452
1466
  }
@@ -1508,8 +1522,8 @@ function applyLoc(loaded, catalog, opts = {}) {
1508
1522
  const existing = findFile(loaded.localeFiles, loaded.locales, path);
1509
1523
  const merged = { ...existing?.strings ?? {} };
1510
1524
  for (const [id, text] of strings) {
1525
+ if (merged[id] !== text) updated++;
1511
1526
  merged[id] = text;
1512
- updated++;
1513
1527
  }
1514
1528
  const file = { schema: existing?.schema ?? "patter/strings@0", scene, locale, strings: merged };
1515
1529
  writes.push({ path, content: canonicalStringify3(file) });
@@ -1569,6 +1583,7 @@ function catalogToPo(catalog) {
1569
1583
  if (e.context?.character || e.context?.kind) {
1570
1584
  out.push(`#. [${[e.context.kind, e.context.character].filter(Boolean).join(" ")}]`);
1571
1585
  }
1586
+ if (e.context?.gender) out.push(`#. Gender: ${e.context.gender}`);
1572
1587
  out.push(`#: ${e.scene}`);
1573
1588
  if (e.stale) out.push("#, fuzzy");
1574
1589
  out.push(poField("msgctxt", e.id), poField("msgid", e.source), poField("msgstr", e.translation), "");
@@ -1662,7 +1677,7 @@ function poToCatalog(text) {
1662
1677
  }
1663
1678
 
1664
1679
  // src/loc-xlsx.ts
1665
- var HEADERS = ["ID", "Source", "Translation", "Comments", "Status"];
1680
+ var HEADERS = ["ID", "Source", "Translation", "Comments", "Status", "Gender"];
1666
1681
  var sheetName = (scene) => scene.replace(/[:\\/?*[\]]/g, "-").slice(0, 31);
1667
1682
  async function catalogToXlsx(catalog) {
1668
1683
  const { default: ExcelJS } = await import("exceljs");
@@ -1677,7 +1692,8 @@ async function catalogToXlsx(catalog) {
1677
1692
  { header: "Source", key: "source", width: 40 },
1678
1693
  { header: "Translation", key: "translation", width: 40 },
1679
1694
  { header: "Comments", key: "comments", width: 30 },
1680
- { header: "Status", key: "status", width: 10 }
1695
+ { header: "Status", key: "status", width: 10 },
1696
+ { header: "Gender", key: "gender", width: 12 }
1681
1697
  ];
1682
1698
  ws.getRow(1).font = { bold: true };
1683
1699
  for (const e of entries) {
@@ -1686,7 +1702,8 @@ async function catalogToXlsx(catalog) {
1686
1702
  source: e.source,
1687
1703
  translation: e.translation,
1688
1704
  comments: e.comments.join("\n"),
1689
- status: e.stale ? "stale" : e.translation ? "translated" : ""
1705
+ status: e.stale ? "stale" : e.translation ? "translated" : "",
1706
+ gender: e.context?.gender ?? ""
1690
1707
  });
1691
1708
  }
1692
1709
  }
@@ -1742,8 +1759,9 @@ function runVoiceScript(loaded, opts = {}) {
1742
1759
  const stub = writingLadder[0];
1743
1760
  const recordThreshold = ladderDecls.findIndex((s) => s.readyToRecord);
1744
1761
  const writingIndex = new Map(writingLadder.map((name, i) => [name, i]));
1745
- const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, documentation: docsOf } = mergeAuthoring(loaded);
1746
- const recordingOf = opts.recordingOverride ?? manualRecordingOf;
1762
+ const { writing: writingOf, recording: manualRecordingOf, cut: cutSet, rerecord: rerecordSet, documentation: docsOf } = mergeAuthoring(loaded);
1763
+ const recordingBase = opts.recordingOverride ?? manualRecordingOf;
1764
+ const recordingOf = (id) => effectiveRecording(id, recordingBase, rerecordSet, recordingLadder[0]);
1747
1765
  const source = sourceStrings(loaded);
1748
1766
  const actorOf = /* @__PURE__ */ new Map();
1749
1767
  for (const c of project.cast ?? []) if (c.actor) actorOf.set(c.name, c.actor);
@@ -1774,7 +1792,7 @@ function runVoiceScript(loaded, opts = {}) {
1774
1792
  text: plainVoice(source[beat.id] ?? ""),
1775
1793
  comments: leading ? [...ancestorVo, ...own] : own,
1776
1794
  // first line of the run gets the enclosing context
1777
- recordingStatus: recordingOf.get(beat.id) ?? recordingLadder[0]
1795
+ recordingStatus: recordingOf(beat.id)
1778
1796
  });
1779
1797
  leading = false;
1780
1798
  }
@@ -2607,6 +2625,7 @@ function runStatusBrowse(loaded, status, dimension = "writing", focus, recording
2607
2625
  const statusOf = recording ? recordingOverride ?? merged.recording : merged.writing;
2608
2626
  const ladder = recording ? loaded.project.recordingStatuses ?? DEFAULT_RECORDING_STATUSES4 : loaded.project.writingStatuses ?? DEFAULT_WRITING_STATUSES3;
2609
2627
  const lowest = ladder[0]?.name;
2628
+ const statusAt = (id) => recording ? effectiveRecording(id, statusOf, merged.rerecord, lowest ?? "") : statusOf.get(id) ?? lowest;
2610
2629
  const out = [];
2611
2630
  for (const scene of loaded.scenes) {
2612
2631
  const file = loaded.sceneFiles[scene.id];
@@ -2615,7 +2634,7 @@ function runStatusBrowse(loaded, status, dimension = "writing", focus, recording
2615
2634
  const consider = (beat) => {
2616
2635
  const tracked = recording ? beat.kind === "line" : beat.kind === "line" || beat.kind === "text";
2617
2636
  if (!tracked) return;
2618
- if ((statusOf.get(beat.id) ?? lowest) !== status) return;
2637
+ if (statusAt(beat.id) !== status) return;
2619
2638
  out.push({ id: beat.id, kind: "beat", text: strings[beat.id], location: segments, sceneId: scene.id, file });
2620
2639
  };
2621
2640
  walkNodes4(block.children, (node) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patterkit/ops",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "The shared operations layer: every project operation (load, validate, export, format, play, ...) as a pure function, consumed by the patter CLI and Patterpad alike.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,10 +27,10 @@
27
27
  "build": "tsup src/index.ts --format esm --dts --clean"
28
28
  },
29
29
  "dependencies": {
30
- "@patterkit/compiler": "0.1.2",
31
- "@patterkit/core": "0.1.2",
32
- "@patterkit/model": "0.1.2",
33
- "@patterkit/runtime": "0.2.0",
30
+ "@patterkit/compiler": "0.2.1",
31
+ "@patterkit/core": "0.1.4",
32
+ "@patterkit/model": "0.2.0",
33
+ "@patterkit/runtime": "0.2.2",
34
34
  "docx": "^9.7.1",
35
35
  "exceljs": "^4.4.0",
36
36
  "jszip": "^3.10.1",