@storylet-studio/play-helpers 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- "use strict";var StoryletEngine=(()=>{var ge=Object.defineProperty;var St=Object.getOwnPropertyDescriptor;var Et=Object.getOwnPropertyNames;var kt=Object.prototype.hasOwnProperty;var Pt=(n,e)=>{for(var t in e)ge(n,t,{get:e[t],enumerable:!0})},Ct=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Et(e))!kt.call(n,s)&&s!==t&&ge(n,s,{get:()=>e[s],enumerable:!(r=St(e,s))||r.enumerable});return n};var Bt=n=>Ct(ge({},"__esModule",{value:!0}),n);var Zt={};Pt(Zt,{Engine:()=>q,Flow:()=>X,applyLiveBundle:()=>bt,boardFrame:()=>Ie,createBundleInspector:()=>yt,createKernelStateLogger:()=>Q,createLiveLink:()=>wt,createPropertyInspector:()=>mt,createStateLogger:()=>gt,createWorldContainer:()=>vt,describeBundle:()=>oe,deserializeState:()=>ie,diffState:()=>J,ensureInspectorStyle:()=>Z,formatLogEntry:()=>Ce,formatPropertySummary:()=>Be,formatScopeLabel:()=>$e,loadState:()=>Pe,makePrng:()=>H,saveState:()=>ke,serializeState:()=>ae,shuffleInPlace:()=>A,snapshotState:()=>pt});function T(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:T(n[2])};case"bin":return{kind:"binary",op:n[1],left:T(n[2]),right:T(n[3])};case"call":{let e=n.slice(2).map(T);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var b=class extends Error{constructor(e){super(e),this.name="EvalError"}};function te(n,e,t){let r=new Map(t.scopes.map(o=>[o.token,o.missing??"false"])),s=o=>{switch(o.kind){case"bool":return o.value;case"number":return o.value;case"string":return o.value;case"scopedvar":{let a=e.scopes[o.scope];if(a===void 0)return!1;let i=typeof a.get=="function"?a.get(o.name):a[o.name];if(i===void 0){if(r.get(o.scope)==="throw")throw new b(`@${o.scope}.${o.name} is not declared on the current ${o.scope}.`);return!1}return i}case"call":{if(o.name==="advance"&&!t.functions[o.name]){let i=o.args[0];if(o.args.length!==1||i===void 0)throw new b(`advance() takes exactly 1 argument, got ${o.args.length}`);let l=fe(i,e);if(l===void 0)throw new b("advance() needs a quality reference (@scope.name of a quality property)");let d=R(s(i),l,"advance");return l[Math.min(d+1,l.length-1)]}let a=t.functions[o.name];if(!a)throw new b(`unknown function '${o.name}'`);return a.eval(o.args,{evaluate:s,ctx:e})}case"flagdelta":throw new b("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(o.op==="not"){let i=s(o.operand);if(typeof i!="boolean")throw new b(`'not' requires a boolean operand, got ${typeof i}`);return!i}let a=s(o.operand);if(typeof a!="number")throw new b(`unary '-' requires a numeric operand, got ${typeof a}`);return-a}case"binary":{if(o.op==="and"){let c=s(o.left);if(typeof c!="boolean")throw new b(`'and' requires boolean operands, left is ${typeof c}`);if(!c)return!1;let f=s(o.right);if(typeof f!="boolean")throw new b(`'and' requires boolean operands, right is ${typeof f}`);return f}if(o.op==="or"){let c=s(o.left);if(typeof c!="boolean")throw new b(`'or' requires boolean operands, left is ${typeof c}`);if(c)return!0;let f=s(o.right);if(typeof f!="boolean")throw new b(`'or' requires boolean operands, right is ${typeof f}`);return f}let a=s(o.left),i=s(o.right),l=fe(o.left,e),d=fe(o.right,e),g=l??d;if(g!==void 0){if(l&&d&&!$t(l,d)&&(o.op===">"||o.op===">="||o.op==="<"||o.op==="<="))throw new b(`'${o.op}' compares two different qualities, whose stage orders are unrelated`);switch(o.op){case">":return R(a,g,">")>R(i,g,">");case">=":return R(a,g,">=")>=R(i,g,">=");case"<":return R(a,g,"<")<R(i,g,"<");case"<=":return R(a,g,"<=")<=R(i,g,"<=");case"+":case"-":case"*":case"/":throw new b(`'${o.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(o.op){case"==":return Ve(a,i);case"!=":return!Ve(a,i);case">":return D(a,i,">"),a>i;case">=":return D(a,i,">="),a>=i;case"<":return D(a,i,"<"),a<i;case"<=":return D(a,i,"<="),a<=i;case"+":if(typeof a=="number"&&typeof i=="number"||typeof a=="string"&&typeof i=="string")return a+i;throw new b(`'+' requires two numbers or two strings, got ${typeof a} and ${typeof i}`);case"-":return D(a,i,"-"),a-i;case"*":return D(a,i,"*"),a*i;case"/":if(D(a,i,"/"),i===0)throw new b("division by zero");return a/i}}}};return s(n)}function Ve(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 D(n,e,t){if(typeof n!="number"||typeof e!="number")throw new b(`'${t}' requires numeric operands, got ${typeof n} and ${typeof e}`)}function fe(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function R(n,e,t){if(typeof n!="string")throw new b(`'${t}' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new b(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var $t=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);function Fe(n){if(Number.isNaN(n)||!Number.isFinite(n))return 0;let e=Math.trunc(n)%4294967296;return e<0?e+4294967296:e}function H(n){let e=Fe(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}}}function A(n,e){for(let t=n.length-1;t>0;t--){let r=Math.floor(e.next()*(t+1));[n[t],n[r]]=[n[r],n[t]]}}var It={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},Rt=[It];function je(n,e,t){let r=t?.countingCalls??Rt;return re(n,t?.want??!0,e,r)}function re(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=re(n.left,e,t,r),o=re(n.right,e,t,r);return n.op==="and"===e?s>0&&o>0?s+o:0:Math.max(s,o)}if(n.kind==="unary"&&n.op==="not")return re(n.operand,!e,t,r);if(n.kind==="call"){let s=r.find(o=>o.name===n.name);if(s){let o=s.count(n),a=t(n);return e?a?o:0:a?0:1}}return t(n)===e?1:0}var ne=9999,W=n=>n.ctx.host??{},j=(n,e,t,r)=>{let s=t.evaluate(e[r]);if(typeof s!="string"||s==="")throw new b(`${n}() argument ${r+1} must be a non-empty string`);return s},_e=(n,e,t)=>{if(e.length===0)throw new b(`${n}() requires at least one argument (the flags property)`);let r=t.evaluate(e[0]);if(Array.isArray(r))return r;if(r===!1)return[];throw new b(`${n}() first argument must be a flags property`)},he={scopes:[{token:"story",missing:"throw"},{token:"world",missing:"throw"},{token:"box",missing:"throw"},{token:"deck",missing:"throw"},{token:"hand",missing:"throw"}],defaultScope:"story",functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new b("random(a, b) requires exactly 2 arguments");let t=W(e).nextRandom;if(!t)throw new b("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 b("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new b("random(a, b) arguments must be integers");let o=Math.min(r,s),a=Math.max(r,s);return Math.floor(t()*(a-o+1))+o}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,eval(n,e){let t=_e("check_flags",n,e);for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new b("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,eval(n,e){let t=[..._e("set_flags",n,e)];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new b("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let o=t.indexOf(s.name);o>=0&&t.splice(o,1)}}return t.sort()}},count_played:{minArgs:1,maxArgs:1,returnType:"number",eval(n,e){let t=j("count_played",n,e,0),r=W(e).countPlayed;if(!r)throw new b("count_played() called without a play log in context");return r(t)}},turns_since_played:{minArgs:1,maxArgs:1,returnType:"number",eval(n,e){let t=j("turns_since_played",n,e,0),r=W(e).turnsSincePlayed;if(!r)throw new b("turns_since_played() called without a play log in context");return r(t)}},count_played_in:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){let t=j("count_played_in",n,e,0),r=j("count_played_in",n,e,1),s=W(e).countPlayedIn;if(!s)throw new b("count_played_in() called without a play log in context");return s(t,r)}},turns_since_played_in:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){let t=j("turns_since_played_in",n,e,0),r=j("turns_since_played_in",n,e,1),s=W(e).turnsSincePlayedIn;if(!s)throw new b("turns_since_played_in() called without a play log in context");return s(t,r)}}}};function Lt(n){return n.toLowerCase().replace(/['’]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function m(n){let e=n.gameId?.trim();return e||(n.title?Lt(n.title):"")||n.id}function Ge(n){let e=[];for(let a of n.boxes){let i=m(a);for(let l of a.tagGroups)for(let d of l.tags){let g=m(d);e.push({id:d.id,gameId:g,qualified:`${i}/${g}`})}}let t=new Map;for(let a of e){let i=t.get(a.gameId)??[];i.includes(a.qualified)||i.push(a.qualified),t.set(a.gameId,i)}let r=new Map,s=new Map,o=new Map;for(let a of e){let i=t.get(a.gameId)??[a.qualified],l=i.length>1;r.set(a.id,l?a.qualified:a.gameId),s.has(a.qualified)||s.set(a.qualified,a.id),!l&&!s.has(a.gameId)&&s.set(a.gameId,a.id),l&&o.set(a.gameId,i)}return{print:r,accept:s,repeated:o}}function qe(n,e,t){let r=t.map(o=>`"value.${o}.${e}"`),s=r.length<=1?r[0]??"":`${r.slice(0,-1).join(", ")} or ${r[r.length-1]}`;return`"value.${n}.${e}" names a tag in ${t.length} boxes; write ${s}`}var L="place",Mt=/^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/,K=n=>n.startsWith("@"),ze=n=>{let e=Mt.exec(n);return e===null?void 0:{scope:e[1],name:e[2]}};var Ue="storylets/save@1",se="storylets/savefile@1";function J(n,e){let t=[],r=new Set([...Object.keys(n),...Object.keys(e)]);for(let s of[...r].sort()){let o=n[s],a=e[s];JSON.stringify(o)!==JSON.stringify(a)&&t.push({path:s,from:o,to:a})}return t}var We=n=>n===void 0?"<unset>":JSON.stringify(n),Ke=n=>n.pathPrefix??n.bag.pathPrefix;function Q(n,e={}){let t=e.sink??(c=>console.log(c)),r=e.label??"",s=c=>{t(`${r}${c.path}: ${We(c.from)} -> ${We(c.to)}`)},o=()=>{let c={};for(let f of n.mounts()){let p=Ke(f);for(let[u,h]of Object.entries(f.bag.values))c[p+u]=h}return Object.assign(c,n.extra?.()??{}),structuredClone(c)},a=o(),i=[],l=[],d=(c,f)=>f.onAudit(p=>{let u=structuredClone({path:c+p.name,from:p.prev,to:p.next});s(u),i.push(u),a[u.path]=structuredClone(p.next)}),g=()=>{let c=n.mounts();if(!(l.length===c.length&&c.every((p,u)=>l[u].bag===p.bag))){for(let p of l)p.off();l=c.map(p=>({bag:p.bag,off:d(Ke(p),p.bag)}))}};return g(),{snapshot:o,capture(){let c=o(),f=J(a,c);for(let u of f)s(u);let p=[...i,...f];return i=[],a=c,g(),p},dispose(){for(let c of l)c.off();l=[],i=[]}}}var _=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??Je(t))}}get(e){return this.values[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 o={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let a of this.auditors)a(o);if(!o.silent)for(let a of this.subscribers)a(o);return o}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])=>Ot(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 Ot(n,e,t,r,s=""){let o=r??n.name.toLowerCase();return{name:o,path:s+o,type:n.type,value:e,default:n.default??Je(n),...n.values!==void 0?{values:n.values}:{},...n.stages!==void 0?{stages:n.stages}:{},writable:t??n.writable??!0}}function Je(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 Qe=(n,e)=>`${n}${e}`,ve=(n,e)=>n.shared??e,tt=n=>n.sharedCopies??n.copies??1,G=(n,e)=>new _(n,{normalise:t=>t,pathPrefix:e});function Ye(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Tt={story:!0,box:!1,deck:!1,hand:!1,value:!1},rt=(n,e)=>e.shared??Tt[n],Xe=(n,e)=>e.filter(t=>rt(n,t)),nt=(n,e)=>e.filter(t=>!rt(n,t)),st=["box","deck","hand","value"],Dt=()=>({box:{gameId:new Map,id:new Map,repeated:new Map},deck:{gameId:new Map,id:new Map,repeated:new Map},hand:{gameId:new Map,id:new Map,repeated:new Map},value:{gameId:new Map,id:new Map,repeated:new Map}}),me=(n,e)=>{let t=m(e);n.gameId.set(e.id,t),n.id.has(t)||n.id.set(t,e.id)},Ht=(n,e)=>{let t=Ge(e);for(let[r,s]of t.print)n.gameId.set(r,s);for(let[r,s]of t.accept)n.id.set(r,s);for(let[r,s]of t.repeated)n.repeated.set(r,s)},xe=(n,e)=>e.template!==void 0?n.templatesById.get(e.template)?.properties??n.bundle.boxes.flatMap(t=>t.handTemplates).find(t=>t.id===e.template)?.properties??[]:e.properties??[],M=(n,e,t)=>`${e}.${n.owners[e].gameId.get(t)??t}`,At=(n,e,t)=>{let r=n.owners[e].repeated.get(t);if(r!==void 0)return{ambiguous:r};let s=n.owners[e].id.get(t);if(s!==void 0)return{id:s,legacy:!1};if(n.owners[e].gameId.has(t))return{id:t,legacy:!0}},ot=(n,e,t,r)=>{let s=At(n,e,t);if(s===void 0)throw new Error(`no ${e} store "${t}"`);if("ambiguous"in s)throw new Error(qe(t,r,s.ambiguous));return s},at=(n,e,t,r)=>`"${e}.${t}.${r}" names the ${e} by its internal id; write "${M(n,e,t)}.${r}". The internal-id form is refused after the next release.`,it=(n,e)=>{let t=n.bundle,r=(s,o)=>`${M(n,s,o)}.`;return{story:G(e("story",t.story.properties),"story."),box:new Map(t.boxes.map(s=>[s.id,G(e("box",s.properties),r("box",s.id))])),deck:new Map(t.boxes.flatMap(s=>s.decks.map(o=>[o.id,G(e("deck",o.properties),r("deck",o.id))]))),hand:new Map(t.boxes.flatMap(s=>s.hands.map(o=>[o.id,G(e("hand",xe(n,o)),r("hand",o.id))]))),value:new Map(t.boxes.flatMap(s=>s.tagGroups.flatMap(o=>o.tags.map(a=>[a.id,G(e("value",a.properties??[]),r("value",a.id))]))))}},dt=n=>({story:n.story.values,box:Object.fromEntries([...n.box].map(([e,t])=>[e,t.values])),deck:Object.fromEntries([...n.deck].map(([e,t])=>[e,t.values])),hand:Object.fromEntries([...n.hand].map(([e,t])=>[e,t.values])),value:Object.fromEntries([...n.value].map(([e,t])=>[e,t.values]))}),lt=(n,e)=>{n.story.load(e?.story??{});for(let t of["box","deck","hand","value"])for(let[r,s]of Object.entries(e?.[t]??{}))n[t].get(r)?.load(s)},ye=()=>({evicted:[],droppedCooldowns:[],droppedSpent:[],droppedProperties:[],defaultedProperties:[],retypedProperties:[]}),Se="",Y=(n,e)=>[...n].map(t=>({item:t,k:e(t)})).sort((t,r)=>t.k<r.k?-1:t.k>r.k?1:0).map(t=>t.item),we=n=>`${n.flow??""}${Se}${n.path}`;function Nt(n,e){switch(n.type){case"boolean":return typeof e=="boolean";case"number":return typeof e=="number";case"string":return typeof e=="string";case"enum":return typeof e=="string"&&(n.values===void 0||n.values.includes(e));case"quality":return typeof e=="string"&&(n.stages===void 0||n.stages.includes(e));case"flags":return Array.isArray(e)&&(n.values===void 0||e.every(t=>n.values.includes(t)));default:return!0}}function Ze(n,e,t,r,s){let o=d=>({...r!==void 0?{flow:r}:{},path:t(d)}),a=new Map((n??[]).map(d=>[d.name,d])),i=e??{},l={};for(let[d,g]of Object.entries(i)){let c=a.get(d);if(c===void 0){s.droppedProperties.push(o(d));continue}if(!Nt(c,g)){s.retypedProperties.push(o(d));continue}l[d]=g}for(let d of n??[])d.name in i||s.defaultedProperties.push(o(d.name));return l}function et(n,e,t,r,s){let o={story:Ze(e.story,t?.story,a=>`story.${a}`,r,s),box:{},deck:{},hand:{},value:{}};for(let a of["box","deck","hand","value"]){let i=t?.[a]??{},l=[...new Set([...e[a].keys(),...Object.keys(i)])].sort();for(let d of l){let g=M(n,a,d);o[a][d]=Ze(e[a].get(d),i[d],c=>`${g}.${c}`,r,s)}}return o}function be(n,e,t,r){let s=e.version!==n.version||e.hash!==n.hash,o=Y(r.evicted,c=>[c.flow,c.hand,c.card,c.reason].join(Se)),a=Y(r.droppedCooldowns,c=>`${c.flow}${Se}${c.card}`),i=[...r.droppedSpent].sort(),l=Y(r.droppedProperties,we),d=Y(r.defaultedProperties,we),g=Y(r.retypedProperties,we);return{exact:!s&&o.length===0&&a.length===0&&i.length===0&&l.length===0&&d.length===0&&g.length===0,project:n.project,version:{saved:e.version,bundle:n.version},hash:{saved:e.hash,bundle:n.hash},flows:t,evicted:o,droppedCooldowns:a,droppedSpent:i,droppedProperties:l,defaultedProperties:d,retypedProperties:g}}var q=class{internals;seed;onReplacedFlow;flowsById=new Map;engineTraceHandlers=new Set;hostWorld;constructor(e,t={}){this.seed=t.seed??0,this.onReplacedFlow=t.onReplacedFlow,t.world!==void 0&&(this.hostWorld=t.world);let r={bundle:e,...t.log?{logCap:typeof t.log=="object"?t.log.cap??1e3:1e3}:{},cardsById:new Map,cardsByGameId:new Map,boxesByGameId:new Map,boxesById:new Map,handsById:new Map,handsByGameId:new Map,owners:Dt(),templatesById:new Map,groupsById:new Map,requiredGroups:new Set,nodeCache:new WeakMap,ladders:{world:new Map,story:new Map,box:new Map,deck:new Map,value:new Map,hand:new Map},hasQualities:!1,hasShared:!1,flowDecls:{story:[],box:new Map,deck:new Map,hand:new Map,value:new Map},sharedDecls:{story:[],box:new Map,deck:new Map,hand:new Map,value:new Map},shared:void 0,worldResolver:void 0,worldReadOnly:new Set,emitEngine:(o,a,i)=>{this.internals.logCap!==void 0&&(this.engineLog.push({...a,flow:o,seq:this.engineSeq++,...i!==void 0?{turn:i}:{}}),this.engineLog.length>this.internals.logCap&&this.engineLog.splice(0,this.engineLog.length-this.internals.logCap));for(let l of this.engineTraceHandlers)l(o,a)},engineTracing:()=>this.engineTraceHandlers.size>0};this.internals=r,Ht(r.owners.value,e);for(let o of e.boxes){r.boxesById.set(o.id,o),r.boxesByGameId.set(m(o),o),me(r.owners.box,o);for(let a of o.tagGroups)r.groupsById.set(a.id,{group:a,box:o}),a.required===!0&&r.requiredGroups.add(a.id);for(let a of o.decks){me(r.owners.deck,a),a.shared===!0&&(r.hasShared=!0);for(let i of a.cards){let l={card:i,deck:a,box:o};r.cardsById.set(i.id,l),r.cardsByGameId.set(m(i),l),i.shared===!0&&(r.hasShared=!0)}}for(let a of o.handTemplates)r.templatesById.set(a.id,a);for(let a of o.hands)r.handsById.set(a.id,{hand:a,box:o}),r.handsByGameId.set(m(a),{hand:a,box:o}),me(r.owners.hand,a)}this.initLadders();let s=o=>({story:o("story",e.story.properties),box:new Map(e.boxes.map(a=>[a.id,o("box",a.properties)])),deck:new Map(e.boxes.flatMap(a=>a.decks.map(i=>[i.id,o("deck",i.properties)]))),hand:new Map(e.boxes.flatMap(a=>a.hands.map(i=>[i.id,o("hand",xe(r,i))]))),value:new Map(e.boxes.flatMap(a=>a.tagGroups.flatMap(i=>i.tags.map(l=>[l.id,o("value",l.properties??[])]))))});r.flowDecls=s(nt),r.sharedDecls=s(Xe),this.initShared(this.hostWorld)}initShared(e){let t=this.internals;if(t.shared=it(t,Xe),t.worldReadOnly=new Set(t.bundle.world.properties.filter(r=>r.writable===!1).map(r=>r.name)),e!==void 0){t.worldResolver=e;let r=e.set;t.worldSet=r!==void 0?(s,o)=>{r(s,o)}:void 0}else{let r=G(t.bundle.world.properties,"world.");t.worldResolver={get:s=>r.get(s),set:(s,o)=>{r.set(s,o)}},t.worldSet=(s,o,a)=>{r.set(s,o,a===!0?{host:!0}:void 0)}}}initLadders(){let e=this.internals,t=o=>{let a=new Map;for(let i of o??[])i.type==="quality"&&i.stages!==void 0&&a.set(i.name,i.stages);return a},r=e.bundle;e.ladders.world=t(r.world.properties),e.ladders.story=t(r.story.properties);for(let o of r.boxes){e.ladders.box.set(o.id,t(o.properties));for(let a of o.decks)e.ladders.deck.set(a.id,t(a.properties));for(let a of o.tagGroups)for(let i of a.tags)e.ladders.value.set(i.id,t(i.properties));for(let a of o.hands)e.ladders.hand.set(a.id,t(xe(e,a)))}let s=o=>[...o.values()].some(a=>a.size>0);e.hasQualities=e.ladders.world.size>0||e.ladders.story.size>0||s(e.ladders.box)||s(e.ladders.deck)||s(e.ladders.value)||s(e.ladders.hand)}openFlow(e,t={}){let r=t.restore!==void 0?this.sharedClaimsExcept(e):void 0,s=this.flowsById.get(e);if(s){let a=s.heldCardIds().length;a>0&&this.onReplacedFlow?.(e,a),s.markClosed()}let o=new X(this,this.internals,e,t.seed??this.seed);if(this.flowsById.set(e,o),t.restore!==void 0){let a=ye(),i=this.planFlowRestore(e,structuredClone(t.restore),r,a);o.restore(i);let l=this.internals.bundle.content;t.onRestoreReport?.(be(l,l,[e],a))}return o}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){let t=this.flowsById.get(e);t&&(this.flowsById.delete(e),t.markClosed())}dropFlow(e,t){this.flowsById.get(e)===t&&this.flowsById.delete(e)}reset(){this.engineLog=[];for(let e of this.flowsById.values())e.markClosed();this.flowsById.clear(),this.spent.clear(),this.initShared(this.hostWorld)}spent=new Set;isTaken(e){return this.spent.has(e)}markTaken(e){this.spent.add(e)}engineLog=[];engineSeq=0;log(){return this.engineLog}clearLog(){this.engineLog=[]}sharedClaims(){let e=new Map;for(let t of this.flowsById.values())for(let r of t.heldCardIds())e.set(r,(e.get(r)??0)+1);return e}sharedClaimsExcept(e){let t=new Map;for(let[r,s]of this.flowsById)if(r!==e)for(let o of s.heldCardIds())t.set(o,(t.get(o)??0)+1);return t}getProperty(e){let t=this.resolveShared(e),r=t.kind==="world"?this.internals.worldResolver.get(t.name):t.bag.get(t.name);if(r===void 0)throw new Error(`no property at "${e}"`);return r}setProperty(e,t){let r=this.resolveShared(e);if(r.kind==="world"){if(!this.internals.worldSet)throw new Error("@world is read-only here: the host bound no write");this.internals.worldSet(r.name,t,!0);return}r.bag.set(r.name,t,{silent:!0,reason:"host setProperty",host:!0})}resolveShared(e){let t=e.split("."),r=()=>{throw new Error(`"${e}" is per-flow state - read it on a Flow, not the Engine`)};if(t.length===2&&t[0]==="world")return{kind:"world",name:t[1]};if(t.length===2&&t[0]==="story"){let s=t[1];if(this.internals.shared.story.get(s)!==void 0)return{kind:"bag",bag:this.internals.shared.story,name:s};throw this.internals.flowDecls.story.some(o=>o.name===s)&&r(),new Error(`no property at "${e}"`)}if(t.length===3&&(t[0]==="box"||t[0]==="deck"||t[0]==="hand"||t[0]==="value")){let s=t[0],[,o,a]=t,i=ot(this.internals,s,o,a);i.legacy&&this.diagnose(at(this.internals,s,o,a));let l=i.id,d=this.internals.shared[s].get(l);if(d!==void 0&&d.get(a)!==void 0)return{kind:"bag",bag:d,name:a};throw this.internals.flowDecls[s].get(l)?.some(g=>g.name===a)&&r(),d===void 0&&!this.internals.flowDecls[s].has(l)?new Error(`no ${s} store "${o}"`):new Error(`no property at "${e}"`)}throw new Error(`bad property path "${e}"`)}diagnose(e){this.internals.emitEngine("",{type:"diagnostic",where:"property address",message:e})}listProperties(){let e=[];for(let r of this.internals.bundle.world.properties){let s=this.internals.worldResolver.get(r.name);e.push({path:`world.${r.name}`,name:r.name,type:r.type,value:s??r.default,default:r.default,...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:this.internals.worldSet!==void 0&&!this.internals.worldReadOnly.has(r.name)})}let t=(r,s)=>{for(let o of s.rows())e.push(o)};t("story",this.internals.shared.story);for(let r of st)for(let[s,o]of this.internals.shared[r])t(M(this.internals,r,s),o);return e}listBags(){let e=[{prefix:"story",bag:this.internals.shared.story}];for(let t of["box","deck","hand","value"])for(let[r,s]of this.internals.shared[t])e.push({prefix:M(this.internals,t,r),bag:s});return e}subscribeTrace(e){return this.engineTraceHandlers.add(e),()=>this.engineTraceHandlers.delete(e)}saveGame(){return structuredClone({schema:"storylets/save@1",content:this.internals.bundle.content,shared:{props:dt(this.internals.shared),spent:[...this.spent].sort()},flows:Object.fromEntries([...this.flowsById].map(([e,t])=>[e,t.snapshot()]))})}saveFlow(e){let t=this.flowsById.get(e);if(!t)throw new Error(`unknown flow "${e}"`);return structuredClone(t.snapshot())}previewLoad(e){return this.assertSameProject(e),this.planLoad(e).report}previewFlowRestore(e,t){let r=ye();this.planFlowRestore(e,t,this.sharedClaimsExcept(e),r);let s=this.internals.bundle.content;return be(s,s,[e],r)}loadGame(e){this.assertSameProject(e);let t=this.planLoad(structuredClone(e));this.reset(),lt(this.internals.shared,t.shared);for(let r of t.spent)this.spent.add(r);for(let[r,s]of t.flows)this.openFlow(r).restore(s);return t.report}assertSameProject(e){if(e.content.project!==this.internals.bundle.content.project)throw new Error(`save is for project "${e.content.project}", bundle is "${this.internals.bundle.content.project}"`)}planLoad(e){let t=ye(),r=et(this.internals,this.internals.sharedDecls,e.shared?.props,void 0,t),s=[];for(let a of e.shared?.spent??[])this.internals.cardsById.has(a)?s.push(a):t.droppedSpent.push(a);let o=[];for(let[a,i]of Object.entries(e.flows??{}))o.push([a,this.planFlowRestore(a,i,void 0,t)]);return{report:be(this.internals.bundle.content,e.content,o.map(([a])=>a),t),shared:r,spent:s,flows:o}}planFlowRestore(e,t,r,s){let o=this.internals,a=et(o,o.flowDecls,t.props,e,s),i={};for(let[c,f]of Object.entries(t.cooldowns??{}))o.cardsById.has(c)?i[c]=f:s.droppedCooldowns.push({flow:e,card:c});let l=c=>{let f=o.cardsById.get(c);return f?m(f.card):c},d={},g=new Map;for(let[c,f]of Object.entries(t.board??{})){let p=o.handsById.get(c);if(p===void 0){for(let y of f)s.evicted.push({flow:e,hand:c,card:l(y),reason:"hand-vanished"});continue}let u=m(p.hand),h=[];for(let y of f){let E=o.cardsById.get(y);if(E===void 0){s.evicted.push({flow:e,hand:u,card:y,reason:"vanished"});continue}if(r!==void 0&&ve(E.card,E.deck.shared??!1)){if((r.get(y)??0)+(g.get(y)??0)>=tt(E.card)){s.evicted.push({flow:e,hand:u,card:m(E.card),reason:"claimed-elsewhere"});continue}g.set(y,(g.get(y)??0)+1)}h.push(y)}d[c]=h}return{props:a,turns:t.turns??{},prng:t.prng,cooldowns:i,board:d,playLog:t.playLog??[]}}},X=class{id;engine;internals;closed=!1;prng;turnCounts=new Map;cooldowns={};boardContents=new Map;playLog=[];playCount=new Map;lastPlayOf=new Map;tagPlayCount=new Map;lastPlayInTag=new Map;stores;traceHandlers=new Set;logEntries=[];logSeq=0;storyReader;boxReaders=new Map;deckReaders=new Map;constructor(e,t,r,s){this.engine=e,this.internals=t,this.id=r,this.prng=H(s),this.stores=it(t,nt);for(let a of t.bundle.boxes){this.turnCounts.set(a.id,0);for(let i of a.hands)this.boardContents.set(i.id,[])}let o=(a,i)=>({get:l=>a?.get(l)??i?.get(l)});this.storyReader=o(this.stores.story,t.shared.story);for(let a of t.bundle.boxes){this.boxReaders.set(a.id,o(this.stores.box.get(a.id),t.shared.box.get(a.id)));for(let i of a.decks)this.deckReaders.set(i.id,o(this.stores.deck.get(i.id),t.shared.deck.get(i.id)))}}get isClosed(){return this.closed}close(){this.closed||(this.engine.dropFlow(this.id,this),this.markClosed())}markClosed(){this.closed=!0}assertOpen(){if(this.closed)throw new Error(`flow "${this.id}" is closed`)}turn(e){this.assertOpen();let t=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!t)throw new Error(`unknown box "${e}"`);return this.turnCounts.get(t.id)??0}subscribeTrace(e){return this.traceHandlers.add(e),()=>this.traceHandlers.delete(e)}get tracing(){return this.traceHandlers.size>0||this.internals.logCap!==void 0||this.internals.engineTracing()}emit(e,t){this.internals.logCap!==void 0&&(this.logEntries.push({...e,seq:this.logSeq++,...t!==void 0?{turn:t}:{}}),this.logEntries.length>this.internals.logCap&&this.logEntries.splice(0,this.logEntries.length-this.internals.logCap));for(let r of this.traceHandlers)r(e);this.internals.emitEngine(this.id,e,t)}log(){return this.logEntries}clearLog(){this.logEntries=[]}node(e){let t=this.internals.nodeCache.get(e);return t||(t=T(e.ast),this.internals.nodeCache.set(e,t)),t}groupInBox(e,t){return e.tagGroups.find(r=>m(r)===t)??e.tagGroups.find(r=>r.id===t)}indexPlay(e){this.playCount.set(e.card,(this.playCount.get(e.card)??0)+1),this.lastPlayOf.set(e.card,e);let t=this.internals.cardsByGameId.get(e.card);if(t)for(let[r,s]of Object.entries(t.card.tags??{}))for(let o of s){let a=Qe(r,o);this.tagPlayCount.set(a,(this.tagPlayCount.get(a)??0)+1),this.lastPlayInTag.set(a,e)}}rebuildPlayIndex(){this.playCount.clear(),this.lastPlayOf.clear(),this.tagPlayCount.clear(),this.lastPlayInTag.clear();for(let e of this.playLog)this.indexPlay(e)}hostsByBox=new Map;host(e){let t=this.hostsByBox.get(e.id);if(t!==void 0)return t;let r=this.makeHost(e);return this.hostsByBox.set(e.id,r),r}makeHost(e){let t=(s,o)=>{let a=this.groupInBox(e,s),i=a?.tags.find(l=>l.gameId===o);return a&&i?Qe(a.id,i.id):void 0},r=s=>{let o=this.internals.cardsByGameId.get(s.card);return o?(this.turnCounts.get(o.box.id)??0)-s.turn:ne};return{nextRandom:()=>this.prng.next(),countPlayed:s=>this.playCount.get(s)??0,turnsSincePlayed:s=>{let o=this.lastPlayOf.get(s);return o?r(o):ne},countPlayedIn:(s,o)=>{let a=t(s,o);return a===void 0?0:this.tagPlayCount.get(a)??0},turnsSincePlayedIn:(s,o)=>{let a=t(s,o),i=a===void 0?void 0:this.lastPlayInTag.get(a);return i?r(i):ne}}}evalCtx(e,t,r){return{scopes:{world:this.internals.worldResolver,story:this.storyReader,box:this.boxReaders.get(e.id)??{},deck:t?this.deckReaders.get(t.id)??{}:{},hand:r.bag},host:this.host(e),...this.internals.hasQualities?{qualities:(s,o)=>s==="world"?this.internals.ladders.world.get(o):s==="story"?this.internals.ladders.story.get(o):s==="box"?this.internals.ladders.box.get(e.id)?.get(o):s==="deck"&&t?this.internals.ladders.deck.get(t.id)?.get(o):s==="hand"?this.handLadder(r,o):void 0}:{}}}handLadder(e,t){let r=e.sources.get(t);if(r)return r.kind==="value"?this.internals.ladders.value.get(r.id)?.get(t):r.kind==="hand"?this.internals.ladders.hand.get(r.id)?.get(t):void 0}eval(e,t){return te(this.node(e),t,he)}passes(e,t,r){if(!e)return!0;try{return Ye(this.eval(e,t))}catch(s){return this.tracing&&this.emit({type:"diagnostic",where:r??"condition",message:s instanceof Error?s.message:String(s)}),!1}}tagByGameId(e,t){return e.tags.find(r=>r.gameId===t)}askForHand(e,t){let r=new Map,s={},o;if(e.template!==void 0){let a=this.internals.templatesById.get(e.template);if(!a)throw new Error(`hand "${m(e)}": unknown template "${e.template}"`);for(let[i,l]of Object.entries(a.bindings??{}))r.set(i,l);for(let[i,l]of Object.entries(e.chosen??{})){if(K(l)){this.fillHoleFromProperty(e,i,l,r,s);continue}r.set(i,l);let d=this.internals.groupsById.get(i),g=d?.group.tags.find(c=>c.id===l);d&&g&&(s[m(d.group)]=m(g))}o=a.condition}else{for(let[a,i]of Object.entries(e.rule?.bindings??{})){if(K(i)){this.fillHoleFromProperty(e,a,i,r,s);continue}r.set(a,i);let l=this.internals.groupsById.get(a),d=l?.group.tags.find(g=>g.id===i);l&&d&&(s[m(l.group)]=m(d))}o=e.rule?.condition}return r.set(L,e.id),this.bindStateGroups(t,r,s),{box:t,hand:e,...o!==void 0?{condition:o}:{},boundTags:r,askNames:s}}askForPeek(e,t){let r=new Map,s={};for(let[o,a]of Object.entries(t)){if(o===L){let d=this.internals.handsByGameId.get(a)??this.internals.handsById.get(a);if(!d)throw new Error(`peek: unknown hand "${a}" in home criteria`);r.set(L,d.hand.id);continue}let i=this.groupInBox(e,o);if(!i)throw new Error(`peek: unknown tag group "${o}" in box "${m(e)}"`);let l=this.tagByGameId(i,a)??i.tags.find(d=>d.id===a);if(!l)throw new Error(`peek: unknown tag "${a}" in group "${m(i)}"`);r.set(i.id,l.id),s[m(i)]=m(l)}return this.bindStateGroups(e,r,s),{box:e,boundTags:r,askNames:s}}fillHoleFromProperty(e,t,r,s,o){let a=this.internals.groupsById.get(t),i=a?m(a.group):t,l=`hand ${m(e)}, tag group ${i}`,d=ze(r);if(!d){this.emit({type:"diagnostic",where:l,message:`"${r}" is not a @hand, @world or @story property reference`});return}if(!a){this.emit({type:"diagnostic",where:l,message:`"${r}" fills a tag group that is not in this box`});return}let g;if(d.scope==="hand")g=this.valuesOf("hand",e.id)[d.name];else try{g=this.getProperty(`${d.scope}.${d.name}`)}catch{g=void 0}if(g===void 0){this.emit({type:"diagnostic",where:l,message:`"${r}" names a property that is not declared`});return}let c=typeof g=="string"?g:String(g),f=a.group.tags.find(p=>m(p)===c);if(!f){this.emit({type:"diagnostic",where:l,message:`${r} is "${c}", which is not one of the tags of "${i}"`});return}s.set(t,f.id),o[i]=m(f)}bindStateGroups(e,t,r){for(let s of e.tagGroups){if(s.boundBy===void 0||t.has(s.id))continue;let o=/^@(world|story)\.([a-z][a-z0-9_-]*)$/.exec(s.boundBy);if(!o){this.emit({type:"diagnostic",where:`tag group ${m(s)}`,message:`boundBy "${s.boundBy}" is not a @world or @story property reference`});continue}let a;try{a=this.getProperty(`${o[1]}.${o[2]}`)}catch{this.emit({type:"diagnostic",where:`tag group ${m(s)}`,message:`boundBy "${s.boundBy}" names a property that is not declared`});continue}let i=typeof a=="string"?a:String(a),l=s.tags.find(d=>m(d)===i);if(!l){this.emit({type:"diagnostic",where:`tag group ${m(s)}`,message:`${s.boundBy} is "${i}", which is not one of its tags`});continue}t.set(s.id,l.id),r[m(s)]=m(l)}}valuesOf(e,t){return{...this.internals.shared[e].get(t)?.values??{},...this.stores[e].get(t)?.values??{}}}buildHandEnv(e){let t={},r=new Map;for(let[s,o]of e.boundTags)if(s!==L)for(let[a,i]of Object.entries(this.valuesOf("value",o)))t[a]=i,r.set(a,{kind:"value",id:o});if(e.hand)for(let[s,o]of Object.entries(this.valuesOf("hand",e.hand.id)))t[s]=o,r.set(s,{kind:"hand",id:e.hand.id});for(let[s,o]of Object.entries(e.askNames))t[s]=o,r.set(s,{kind:"criteria"});return{bag:t,sources:r,boundTags:e.boundTags}}claims(){let e=new Map;for(let t of this.boardContents.values())for(let r of t)e.set(r,(e.get(r)??0)+1);return e}heldCardIds(){return[...this.boardContents.values()].flat()}copiesOf(e){return e.copies??1}claimVerdict(e,t,r,s){let o=e.id;return(r.get(o)??0)>=this.copiesOf(e)?"claimed":t&&(s.get(o)??0)>=tt(e)?"claimed-elsewhere":null}tagsMatch(e,t){let r=e.tags?.[L];if(r!==void 0&&r.length>0){let s=t.get(L);if(s===void 0||!r.includes(s))return!1}for(let[s,o]of t){if(s===L)continue;let a=e.tags?.[s];if(a===void 0){if(this.internals.requiredGroups.has(s))return!1;continue}if(!a.includes(o))return!1}return!0}runAsk(e,t,r){let{box:s}=e,o=this.buildHandEnv(e),a=(c,f)=>{r?.push({id:m(c),verdict:f})};if(!this.passes(e.condition,this.evalCtx(s,void 0,o),`hand ${e.hand?m(e.hand):""} condition`))return{ordered:[],handEnv:o};let i=new Map;for(let c of s.decks)i.set(c.id,this.passes(c.condition,this.evalCtx(s,c,o),`deck ${c.gameId} gate`));let l=this.turnCounts.get(s.id)??0,d=[];for(let c of s.decks){let f=this.evalCtx(s,c,o),p=c.shared??!1;for(let u of c.cards){let h=ve(u,p);if(!i.get(c.id)){a(u,"deck-gate");continue}if(h&&this.engine.isTaken(u.id)){a(u,"taken");continue}if((this.cooldowns[u.id]??0)>l){a(u,"cooldown");continue}if(!this.tagsMatch(u,o.boundTags)){a(u,"tags");continue}if(u.condition&&!this.passes(u.condition,f,this.tracing?`card ${u.gameId} condition`:void 0)){a(u,"condition");continue}let y=t(u,h);if(y){a(u,y);continue}let E;if(typeof u.priority=="number")E=u.priority;else try{let v=this.eval(u.priority,f);if(typeof v!="number"){a(u,"priority");continue}E=v}catch(v){this.tracing&&this.emit({type:"diagnostic",where:`card ${u.gameId} priority`,message:v instanceof Error?v.message:String(v)}),a(u,"priority");continue}let w=0;if(s.ranking.specificity&&u.condition){let v=this.node(u.condition);w=je(v,k=>{try{return Ye(te(k,f,he))}catch{return!1}})}d.push({entry:{card:u,deck:c,box:s},priority:E,spec:w})}}d.sort((c,f)=>f.priority-c.priority||f.spec-c.spec);let g=0;for(;g<d.length;){let c=g+1;for(;c<d.length&&d[c].priority===d[g].priority&&d[c].spec===d[g].spec;)c++;if(c-g>1){let f=d.slice(g,c);A(f,this.prng);for(let p=0;p<f.length;p++)d[g+p]=f[p]}g=c}for(let c of d)r?.push({id:m(c.entry.card),verdict:"dealt",priority:c.priority,specificity:c.spec});return{ordered:d.map(c=>c.entry),handEnv:o}}capTrace(e,t){for(let r of e)r.verdict==="dealt"&&!t.has(r.id)&&(r.verdict="capped")}view(e){let{card:t}=e;return{id:t.id,gameId:m(t),...t.title!==void 0?{title:t.title}:{},...t.purpose!==void 0?{purpose:t.purpose}:{},...t.fields!==void 0?{fields:t.fields}:{}}}handCapacity(e){if(e.slots!==void 0)return e.slots;let t=e.template!==void 0?this.internals.templatesById.get(e.template)?.slots:e.rule?.slots;return t===void 0||t==="unbounded"?1/0:t}resolveHand(e){let t=this.internals.handsByGameId.get(e)??this.internals.handsById.get(e);if(!t)throw new Error(`unknown hand "${e}"`);return t}peek(e,t={},r){this.assertOpen();let s=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!s)throw new Error(`unknown box "${e}"`);let o=this.askForPeek(s,t),a=this.claims(),i=this.internals.hasShared?this.engine.sharedClaims():new Map,l=this.tracing?[]:void 0,{ordered:d}=this.runAsk(o,(c,f)=>this.claimVerdict(c,f,a,i),l),g=r===void 0?d:d.slice(0,Math.max(r,0));return l&&(this.capTrace(l,new Set(g.map(c=>m(c.card)))),this.emit({type:"peek",box:m(s),criteria:t,cards:l},this.turnCounts.get(s.id)??0)),{box:m(s),cards:g.map(c=>this.view(c))}}deal(e){this.assertOpen();let{hand:t}=this.resolveHand(e);return this.dealMany([e])[m(t)]??[]}dealMany(e){this.assertOpen();let t=(e??[...this.internals.handsById.keys()].sort()).map(o=>this.resolveHand(o));A(t,this.prng);for(let{hand:o,box:a}of t){let i=this.askForHand(o,a),l=this.buildHandEnv(i),d=this.passes(i.condition,this.evalCtx(a,void 0,l)),g=new Map;for(let h of a.decks)g.set(h.id,this.passes(h.condition,this.evalCtx(a,h,l)));let c=this.turnCounts.get(a.id)??0,f=[],p=(h,y)=>{let E=this.internals.cardsById.get(h);return f.push({card:E?m(E.card):h,reason:y}),!1},u=(this.boardContents.get(o.id)??[]).filter(h=>{if(!d)return p(h,"hand-condition");let y=this.internals.cardsById.get(h);return y?g.get(y.deck.id)?(this.cooldowns[h]??0)>c?p(h,"cooldown"):this.tagsMatch(y.card,l.boundTags)?this.passes(y.card.condition,this.evalCtx(a,y.deck,l),`card ${y.card.gameId} condition`)?!0:p(h,"condition"):p(h,"tags"):p(h,"deck-gate"):p(h,"vanished")});if(this.boardContents.set(o.id,u),this.tracing)for(let h of f)this.emit({type:"evict",hand:m(o),card:h.card,reason:h.reason},c)}let r=this.claims(),s=this.internals.hasShared?this.engine.sharedClaims():new Map;for(let{hand:o,box:a}of t){let i=this.boardContents.get(o.id)??[],l=this.handCapacity(o)-i.length;if(l<=0)continue;let d=this.askForHand(o,a),g=new Set(i),c=this.tracing?[]:void 0,{ordered:f}=this.runAsk(d,(h,y)=>g.has(h.id)?"claimed":this.claimVerdict(h,y,r,s),c),p=f.slice(0,l),u=p.map(h=>h.card.id);this.boardContents.set(o.id,[...i,...u]);for(let h of u)r.set(h,(r.get(h)??0)+1),s.set(h,(s.get(h)??0)+1);c&&(this.capTrace(c,new Set(p.map(h=>m(h.card)))),this.emit({type:"deal",hand:m(o),cards:c},this.turnCounts.get(a.id)??0))}return Object.fromEntries(t.map(({hand:o})=>[m(o),(this.boardContents.get(o.id)??[]).map(a=>this.view(this.internals.cardsById.get(a)))]))}board(e){this.assertOpen();let t;if(e!==void 0){let r=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!r)throw new Error(`unknown box "${e}"`);t=r.id}return Object.fromEntries([...this.boardContents.entries()].filter(([r])=>t===void 0||this.internals.handsById.get(r).box.id===t).map(([r,s])=>[m(this.internals.handsById.get(r).hand),s.map(o=>this.view(this.internals.cardsById.get(o)))]))}resolveDealt(e,t){let r=this.internals.cardsById.get(e)??this.internals.cardsByGameId.get(e);if(!r)throw new Error(`unknown card "${e}"`);let{hand:s,box:o}=this.resolveHand(t);if(!(this.boardContents.get(s.id)??[]).includes(r.card.id))throw new Error(`card "${m(r.card)}" is not dealt to hand "${m(s)}"`);return{entry:r,ask:this.askForHand(s,o)}}outcomes(e,t){this.assertOpen();let{entry:r,ask:s}=this.resolveDealt(e,t),o=this.evalCtx(r.box,r.deck,this.buildHandEnv(s));return r.card.outcomes.map(a=>({id:a.id,gameId:m(a),...a.title!==void 0?{title:a.title}:{},...a.purpose!==void 0?{purpose:a.purpose}:{},...a.fields!==void 0?{fields:a.fields}:{},available:this.passes(a.condition,o)}))}play(e,t,r,s={}){this.assertOpen();let{entry:o,ask:a}=this.resolveDealt(e,r),i=o.card.outcomes.find(h=>m(h)===t);if(!i)throw new Error(`card "${m(o.card)}" has no outcome "${t}"`);let l=this.buildHandEnv(a),d=this.evalCtx(o.box,o.deck,l);if(!this.passes(i.condition,d))throw new Error(`outcome "${t}" on "${m(o.card)}" is gated shut`);let g=o.box.turn!==void 0?0:this.internals.bundle.settings.playAdvancesTurns,c=(this.turnCounts.get(o.box.id)??0)+(s.advanceTurns??g),f=[];for(let[h,y]of Object.entries(i.changes))f.push({target:h,value:this.eval(y,d)});for(let{target:h,value:y}of f){let{path:E,prev:w}=this.applyWrite(h,y,o,l);this.tracing&&this.emit({type:"write",target:h,path:E,value:y,...w!==void 0?{prev:w}:{}},c)}let p={card:m(o.card),outcome:m(i),turn:c};this.playLog.push(p),this.indexPlay(p),o.card.redraw==="never"?ve(o.card,o.deck.shared??!1)?this.engine.markTaken(o.card.id):this.cooldowns[o.card.id]=Number.MAX_SAFE_INTEGER:typeof o.card.redraw=="number"&&(this.cooldowns[o.card.id]=c+o.card.redraw);let u=a.hand.id;this.boardContents.set(u,(this.boardContents.get(u)??[]).filter(h=>h!==o.card.id)),this.turnCounts.set(o.box.id,c),this.tracing&&this.emit({type:"play",card:m(o.card),outcome:m(i),turn:c},c)}address(e,t){return M(this.internals,e,t)}landIn(e,t,r,s,o){let a=e==="story"?this.stores.story:t!==void 0?this.stores[e].get(t):void 0,i=e==="story"?this.internals.shared.story:t!==void 0?this.internals.shared[e].get(t):void 0,l=a!==void 0&&a.get(r)!==void 0?a:i!==void 0&&i.get(r)!==void 0?i:void 0;if(l===void 0)throw new Error(`no property at "${o}"`);let d=l.set(r,s);return{path:o,...d.prev!==void 0?{prev:d.prev}:{}}}applyWrite(e,t,r,s){let o=/^@([a-z]+)\.([A-Za-z_][A-Za-z0-9_-]*)$/.exec(e);if(!o)throw new Error(`bad change target "${e}"`);let[,a,i]=o;switch(a){case"world":{let l=this.internals.worldSet;if(!l)throw new Error(`@world.${i} cannot be written: the host bound @world read-only`);if(this.internals.worldReadOnly.has(i))throw new Error(`'@world.${i}' is read-only (writable: false)`);let d=this.internals.worldResolver.get(i);return l(i,t),{path:`world.${i}`,...d!==void 0?{prev:d}:{}}}case"story":return this.landIn("story",void 0,i,t,`story.${i}`);case"box":return this.landIn("box",r.box.id,i,t,`${this.address("box",r.box.id)}.${i}`);case"deck":return this.landIn("deck",r.deck.id,i,t,`${this.address("deck",r.deck.id)}.${i}`);case"hand":{let l=s.sources.get(i);if(!l)throw new Error(`@hand.${i} is not composed in this ask`);if(l.kind==="criteria")throw new Error(`@hand.${i} is a chosen tag / criteria name and cannot be written`);return this.landIn(l.kind,l.id,i,t,`${this.address(l.kind,l.id)}.${i}`)}default:throw new Error(`bad change target scope "@${a}"`)}}advanceTurns(e,t=1){this.assertOpen();let r=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!r)throw new Error(`unknown box "${e}"`);let s=(this.turnCounts.get(r.id)??0)+t;this.turnCounts.set(r.id,s),this.tracing&&this.emit({type:"turns",box:m(r),turn:s},s)}listBoxes(){return this.assertOpen(),this.internals.bundle.boxes.map(e=>({id:e.id,gameId:m(e),...e.title!==void 0?{title:e.title}:{},turn:this.turnCounts.get(e.id)??0}))}listBags(){this.assertOpen();let e=[{prefix:"story",bag:this.stores.story}];for(let t of["box","deck","hand","value"])for(let[r,s]of this.stores[t])e.push({prefix:M(this.internals,t,r),bag:s});return e}listProperties(){this.assertOpen();let e=[];for(let r of this.internals.bundle.world.properties){let s=this.internals.worldResolver.get(r.name);e.push({path:`world.${r.name}`,name:r.name,type:r.type,value:s??r.default,default:r.default,...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:this.internals.worldSet!==void 0&&!this.internals.worldReadOnly.has(r.name)})}let t=(r,s,o)=>{for(let a of[s,o])if(a!==void 0)for(let i of a.rows())e.push(i)};t("story",this.internals.shared.story,this.stores.story);for(let r of st){let s=new Set([...this.internals.shared[r].keys(),...this.stores[r].keys()]);for(let o of s)t(M(this.internals,r,o),this.internals.shared[r].get(o),this.stores[r].get(o))}return e}getProperty(e){this.assertOpen();let t=this.resolvePath(e),r=t.kind==="world"?this.internals.worldResolver.get(t.name):t.own?.get(t.name)??t.shared?.get(t.name);if(r===void 0)throw new Error(`no property at "${e}"`);return r}setProperty(e,t){this.assertOpen();let r=this.resolvePath(e);if(r.kind==="world"){if(!this.internals.worldSet)throw new Error("@world is read-only here: the host bound no write");this.internals.worldSet(r.name,t,!0);return}let s=r.own!==void 0&&r.own.get(r.name)!==void 0?r.own:r.shared!==void 0&&r.shared.get(r.name)!==void 0?r.shared:void 0;if(s===void 0)throw new Error(`no property at "${e}"`);s.set(r.name,t,{silent:!0,reason:"host setProperty",host:!0})}resolvePath(e){let t=e.split(".");if(t.length===2&&t[0]==="world")return{kind:"world",name:t[1]};if(t.length===2&&t[0]==="story")return{kind:"bag",own:this.stores.story,shared:this.internals.shared.story,name:t[1]};if(t.length===3&&(t[0]==="box"||t[0]==="deck"||t[0]==="hand"||t[0]==="value")){let r=t[0],[,s,o]=t,a=ot(this.internals,r,s,o);a.legacy&&this.tracing&&this.emit({type:"diagnostic",where:"property address",message:at(this.internals,r,s,o)});let i=this.stores[r].get(a.id),l=this.internals.shared[r].get(a.id);if(i===void 0&&l===void 0)throw new Error(`no ${r} store "${s}"`);return{kind:"bag",...i!==void 0?{own:i}:{},...l!==void 0?{shared:l}:{},name:o}}throw new Error(`bad property path "${e}"`)}snapshot(){return{props:dt(this.stores),turns:Object.fromEntries(this.turnCounts),prng:this.prng.state(),cooldowns:this.cooldowns,board:Object.fromEntries(this.boardContents),playLog:this.playLog}}restore(e){lt(this.stores,e.props),this.turnCounts=new Map(this.internals.bundle.boxes.map(t=>[t.id,0]));for(let[t,r]of Object.entries(e.turns??{}))this.turnCounts.has(t)&&this.turnCounts.set(t,r);this.prng=H(e.prng),this.cooldowns=e.cooldowns??{},this.playLog=e.playLog??[],this.rebuildPlayIndex(),this.boardContents=new Map(Object.entries(e.board??{}).filter(([t])=>this.internals.handsById.has(t)).map(([t,r])=>[t,r.filter(s=>this.internals.cardsById.has(s))]));for(let t of this.internals.handsById.keys())this.boardContents.has(t)||this.boardContents.set(t,[])}};var Ee=n=>n.map(e=>({name:e.name,type:e.type,default:e.default,...e.values!==void 0?{values:e.values}:{},...e.durable===!0?{durable:!0}:{},...e.purpose!==void 0?{purpose:e.purpose}:{}})),ct=n=>n.decks.reduce((e,t)=>e+t.cards.filter(r=>(r.durable??t.durable)===!0).length,0),Vt=(n,e)=>n.template!==void 0?e.handTemplates.find(t=>t.id===n.template)?.properties??[]:n.properties??[],Ft=(n,e)=>{let t=n.template!==void 0?n.chosen:n.rule?.bindings,r=[];for(let[s,o]of Object.entries(t??{})){if(!K(o))continue;let a=e.tagGroups.find(i=>i.id===s);a!==void 0&&r.push({group:m(a),from:o})}return r},jt=(n,e)=>{if(n.slots!==void 0)return n.slots;let t=n.template!==void 0?e.handTemplates.find(r=>r.id===n.template)?.slots:n.rule?.slots;return t===void 0?"unbounded":t};function oe(n){let e=[],t=[],r=[{scope:"world",owner:"",properties:Ee(n.world.properties)},{scope:"story",owner:"",properties:Ee(n.story.properties)}],s={boxes:0,decks:0,cards:0,hands:0,templates:0,tagGroups:0};for(let o of n.boxes){let a=m(o),i=o.decks.reduce((d,g)=>d+g.cards.length,0);e.push({gameId:a,...o.title!==void 0?{title:o.title}:{},ranking:{specificity:o.ranking.specificity},...o.turn!==void 0?{turn:{seconds:o.turn.seconds}}:{},...ct(o)>0?{durableCards:ct(o)}:{},tagGroups:o.tagGroups.map(d=>({gameId:m(d),tags:d.tags.map(g=>m(g))})),counts:{decks:o.decks.length,cards:i,hands:o.hands.length,templates:o.handTemplates.length,tagGroups:o.tagGroups.length}}),s.boxes+=1,s.decks+=o.decks.length,s.cards+=i,s.hands+=o.hands.length,s.templates+=o.handTemplates.length,s.tagGroups+=o.tagGroups.length;for(let d of o.hands){let g=d.template!==void 0?o.handTemplates.find(f=>f.id===d.template):void 0,c=Ft(d,o);t.push({gameId:m(d),...d.title!==void 0?{title:d.title}:{},box:a,slots:jt(d,o),...g!==void 0?{template:m(g)}:{},...c.length>0?{movable:c}:{}})}let l=(d,g,c,f)=>{c.length!==0&&r.push({scope:d,owner:g,box:a,...f!==void 0?{group:f}:{},properties:Ee(c)})};l("box",a,o.properties);for(let d of o.decks)l("deck",m(d),d.properties);for(let d of o.hands)l("hand",m(d),Vt(d,o));for(let d of o.tagGroups)for(let g of d.tags)l("tag",m(g),g.properties??[],m(d))}return{identity:{schema:n.schema,project:n.content.project,version:n.content.version,hash:n.content.hash,metadata:n.metadata},totals:s,boxes:e,hands:t,properties:r,maps:(n.maps??[]).map(o=>({box:o.box,group:o.group,zones:o.zones.length,backgrounds:o.backgrounds?.length??0,sites:o.sites?.length??0}))}}function pt(n,e){let t={};for(let{bag:r}of[...n.listBags(),...e.listBags()])for(let s of r.rows())s.value!==void 0&&(t[s.path]=s.value);return Object.assign(t,ut(n.saveGame().flows[e.id])),t}function ut(n){let e={};if(n===void 0)return e;for(let[t,r]of Object.entries(n.turns))e[`turn:${t}`]=r;for(let[t,r]of Object.entries(n.cooldowns))e[`cooldown:${t}`]=r;for(let[t,r]of Object.entries(n.board))e[`board:${t}`]=[...r];return e}function gt(n,e,t={}){let r=e.id,s=()=>n.getFlow(r);return Q({mounts:()=>[...n.listBags(),...s()?.listBags()??[]].map(({bag:o})=>({bag:o})),extra:()=>ut(n.saveGame().flows[r])},t)}function ae(n,e){return JSON.stringify(ke(n,e),null,2)}function ke(n,e){return{schema:se,engine:n.saveGame(),...e!==void 0?{world:e}:{}}}function Pe(n,e){if(!e||typeof e!="object"||e.schema!==se||e.engine?.schema!==Ue)throw new Error(`not a storylets save (expected schema "${se}")`);return n.loadGame(e.engine),e.world}function ie(n,e){let t;try{t=JSON.parse(e)}catch{throw new Error("not valid JSON")}return Pe(n,t)}var ft="sl-inspector-style",_t=`
1
+ "use strict";var StoryletEngine=(()=>{var ge=Object.defineProperty;var St=Object.getOwnPropertyDescriptor;var Et=Object.getOwnPropertyNames;var kt=Object.prototype.hasOwnProperty;var Pt=(n,e)=>{for(var t in e)ge(n,t,{get:e[t],enumerable:!0})},Ct=(n,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Et(e))!kt.call(n,s)&&s!==t&&ge(n,s,{get:()=>e[s],enumerable:!(r=St(e,s))||r.enumerable});return n};var Bt=n=>Ct(ge({},"__esModule",{value:!0}),n);var Zt={};Pt(Zt,{Engine:()=>q,Flow:()=>X,applyLiveBundle:()=>bt,boardFrame:()=>Ie,createBundleInspector:()=>yt,createKernelStateLogger:()=>Q,createLiveLink:()=>wt,createPropertyInspector:()=>mt,createStateLogger:()=>gt,createWorldContainer:()=>vt,describeBundle:()=>oe,deserializeState:()=>ie,diffState:()=>J,ensureInspectorStyle:()=>Z,formatLogEntry:()=>Ce,formatPropertySummary:()=>Be,formatScopeLabel:()=>$e,loadState:()=>Pe,makePrng:()=>H,saveState:()=>ke,serializeState:()=>ae,shuffleInPlace:()=>A,snapshotState:()=>pt});function T(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:T(n[2])};case"bin":return{kind:"binary",op:n[1],left:T(n[2]),right:T(n[3])};case"call":{let e=n.slice(2).map(T);return{kind:"call",name:n[1],args:e}}case"fd":return{kind:"flagdelta",sign:n[1],name:n[2]}}}var b=class extends Error{constructor(e){super(e),this.name="EvalError"}};function te(n,e,t){let r=new Map(t.scopes.map(o=>[o.token,o.missing??"false"])),s=o=>{switch(o.kind){case"bool":return o.value;case"number":return o.value;case"string":return o.value;case"scopedvar":{let a=e.scopes[o.scope];if(a===void 0)return!1;let i=typeof a.get=="function"?a.get(o.name):a[o.name];if(i===void 0){if(r.get(o.scope)==="throw")throw new b(`@${o.scope}.${o.name} is not declared on the current ${o.scope}.`);return!1}return i}case"call":{if(o.name==="advance"&&!t.functions[o.name]){let i=o.args[0];if(o.args.length!==1||i===void 0)throw new b(`advance() takes exactly 1 argument, got ${o.args.length}`);let d=fe(i,e);if(d===void 0)throw new b("advance() needs a quality reference (@scope.name of a quality property)");let l=R(s(i),d,"advance");return d[Math.min(l+1,d.length-1)]}let a=t.functions[o.name];if(!a)throw new b(`unknown function '${o.name}'`);return a.eval(o.args,{evaluate:s,ctx:e})}case"flagdelta":throw new b("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(o.op==="not"){let i=s(o.operand);if(typeof i!="boolean")throw new b(`'not' requires a boolean operand, got ${typeof i}`);return!i}let a=s(o.operand);if(typeof a!="number")throw new b(`unary '-' requires a numeric operand, got ${typeof a}`);return-a}case"binary":{if(o.op==="and"){let c=s(o.left);if(typeof c!="boolean")throw new b(`'and' requires boolean operands, left is ${typeof c}`);if(!c)return!1;let f=s(o.right);if(typeof f!="boolean")throw new b(`'and' requires boolean operands, right is ${typeof f}`);return f}if(o.op==="or"){let c=s(o.left);if(typeof c!="boolean")throw new b(`'or' requires boolean operands, left is ${typeof c}`);if(c)return!0;let f=s(o.right);if(typeof f!="boolean")throw new b(`'or' requires boolean operands, right is ${typeof f}`);return f}let a=s(o.left),i=s(o.right),d=fe(o.left,e),l=fe(o.right,e),p=d??l;if(p!==void 0){if(d&&l&&!$t(d,l)&&(o.op===">"||o.op===">="||o.op==="<"||o.op==="<="))throw new b(`'${o.op}' compares two different qualities, whose stage orders are unrelated`);switch(o.op){case">":return R(a,p,">")>R(i,p,">");case">=":return R(a,p,">=")>=R(i,p,">=");case"<":return R(a,p,"<")<R(i,p,"<");case"<=":return R(a,p,"<=")<=R(i,p,"<=");case"+":case"-":case"*":case"/":throw new b(`'${o.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(o.op){case"==":return Ve(a,i);case"!=":return!Ve(a,i);case">":return D(a,i,">"),a>i;case">=":return D(a,i,">="),a>=i;case"<":return D(a,i,"<"),a<i;case"<=":return D(a,i,"<="),a<=i;case"+":if(typeof a=="number"&&typeof i=="number"||typeof a=="string"&&typeof i=="string")return a+i;throw new b(`'+' requires two numbers or two strings, got ${typeof a} and ${typeof i}`);case"-":return D(a,i,"-"),a-i;case"*":return D(a,i,"*"),a*i;case"/":if(D(a,i,"/"),i===0)throw new b("division by zero");return a/i}}}};return s(n)}function Ve(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 D(n,e,t){if(typeof n!="number"||typeof e!="number")throw new b(`'${t}' requires numeric operands, got ${typeof n} and ${typeof e}`)}function fe(n,e){if(!(n.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(n.scope,n.name)}function R(n,e,t){if(typeof n!="string")throw new b(`'${t}' on a quality compares stages, got ${typeof n}`);let r=e.indexOf(n);if(r<0)throw new b(`"${n}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var $t=(n,e)=>n.length===e.length&&n.every((t,r)=>t===e[r]);function Fe(n){if(Number.isNaN(n)||!Number.isFinite(n))return 0;let e=Math.trunc(n)%4294967296;return e<0?e+4294967296:e}function H(n){let e=Fe(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}}}function A(n,e){for(let t=n.length-1;t>0;t--){let r=Math.floor(e.next()*(t+1));[n[t],n[r]]=[n[r],n[t]]}}var It={name:"check_flags",count:n=>Math.max(1,n.args.length-1)},Rt=[It];function je(n,e,t){let r=t?.countingCalls??Rt;return re(n,t?.want??!0,e,r)}function re(n,e,t,r){if(n.kind==="binary"&&(n.op==="and"||n.op==="or")){let s=re(n.left,e,t,r),o=re(n.right,e,t,r);return n.op==="and"===e?s>0&&o>0?s+o:0:Math.max(s,o)}if(n.kind==="unary"&&n.op==="not")return re(n.operand,!e,t,r);if(n.kind==="call"){let s=r.find(o=>o.name===n.name);if(s){let o=s.count(n),a=t(n);return e?a?o:0:a?0:1}}return t(n)===e?1:0}var ne=9999,W=n=>n.ctx.host??{},j=(n,e,t,r)=>{let s=t.evaluate(e[r]);if(typeof s!="string"||s==="")throw new b(`${n}() argument ${r+1} must be a non-empty string`);return s},_e=(n,e,t)=>{if(e.length===0)throw new b(`${n}() requires at least one argument (the flags property)`);let r=t.evaluate(e[0]);if(Array.isArray(r))return r;if(r===!1)return[];throw new b(`${n}() first argument must be a flags property`)},he={scopes:[{token:"story",missing:"throw"},{token:"world",missing:"throw"},{token:"box",missing:"throw"},{token:"deck",missing:"throw"},{token:"hand",missing:"throw"}],defaultScope:"story",functions:{random:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){if(n.length!==2)throw new b("random(a, b) requires exactly 2 arguments");let t=W(e).nextRandom;if(!t)throw new b("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 b("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new b("random(a, b) arguments must be integers");let o=Math.min(r,s),a=Math.max(r,s);return Math.floor(t()*(a-o+1))+o}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,eval(n,e){let t=_e("check_flags",n,e);for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new b("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,eval(n,e){let t=[..._e("set_flags",n,e)];for(let r=1;r<n.length;r++){let s=n[r];if(s.kind!=="flagdelta")throw new b("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let o=t.indexOf(s.name);o>=0&&t.splice(o,1)}}return t.sort()}},count_played:{minArgs:1,maxArgs:1,returnType:"number",eval(n,e){let t=j("count_played",n,e,0),r=W(e).countPlayed;if(!r)throw new b("count_played() called without a play log in context");return r(t)}},turns_since_played:{minArgs:1,maxArgs:1,returnType:"number",eval(n,e){let t=j("turns_since_played",n,e,0),r=W(e).turnsSincePlayed;if(!r)throw new b("turns_since_played() called without a play log in context");return r(t)}},count_played_in:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){let t=j("count_played_in",n,e,0),r=j("count_played_in",n,e,1),s=W(e).countPlayedIn;if(!s)throw new b("count_played_in() called without a play log in context");return s(t,r)}},turns_since_played_in:{minArgs:2,maxArgs:2,returnType:"number",eval(n,e){let t=j("turns_since_played_in",n,e,0),r=j("turns_since_played_in",n,e,1),s=W(e).turnsSincePlayedIn;if(!s)throw new b("turns_since_played_in() called without a play log in context");return s(t,r)}}}};function Lt(n){return n.toLowerCase().replace(/['’]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function h(n){let e=n.gameId?.trim();return e||(n.title?Lt(n.title):"")||n.id}function Ge(n){let e=[];for(let a of n.boxes){let i=h(a);for(let d of a.tagGroups)for(let l of d.tags){let p=h(l);e.push({id:l.id,gameId:p,qualified:`${i}/${p}`})}}let t=new Map;for(let a of e){let i=t.get(a.gameId)??[];i.includes(a.qualified)||i.push(a.qualified),t.set(a.gameId,i)}let r=new Map,s=new Map,o=new Map;for(let a of e){let i=t.get(a.gameId)??[a.qualified],d=i.length>1;r.set(a.id,d?a.qualified:a.gameId),s.has(a.qualified)||s.set(a.qualified,a.id),!d&&!s.has(a.gameId)&&s.set(a.gameId,a.id),d&&o.set(a.gameId,i)}return{print:r,accept:s,repeated:o}}function qe(n,e,t){let r=t.map(o=>`"value.${o}.${e}"`),s=r.length<=1?r[0]??"":`${r.slice(0,-1).join(", ")} or ${r[r.length-1]}`;return`"value.${n}.${e}" names a tag in ${t.length} boxes; write ${s}`}var L="place",Mt=/^@(hand|world|story)\.([a-z][a-z0-9_-]*)$/,K=n=>n.startsWith("@"),ze=n=>{let e=Mt.exec(n);return e===null?void 0:{scope:e[1],name:e[2]}};var Ue="storylets/save@1",se="storylets/savefile@1";function J(n,e){let t=[],r=new Set([...Object.keys(n),...Object.keys(e)]);for(let s of[...r].sort()){let o=n[s],a=e[s];JSON.stringify(o)!==JSON.stringify(a)&&t.push({path:s,from:o,to:a})}return t}var We=n=>n===void 0?"<unset>":JSON.stringify(n),Ke=n=>n.pathPrefix??n.bag.pathPrefix;function Q(n,e={}){let t=e.sink??(c=>console.log(c)),r=e.label??"",s=c=>{t(`${r}${c.path}: ${We(c.from)} -> ${We(c.to)}`)},o=()=>{let c={};for(let f of n.mounts()){let u=Ke(f);for(let[g,m]of Object.entries(f.bag.values))c[u+g]=m}return Object.assign(c,n.extra?.()??{}),structuredClone(c)},a=o(),i=[],d=[],l=(c,f)=>f.onAudit(u=>{let g=structuredClone({path:c+u.name,from:u.prev,to:u.next});s(g),i.push(g),a[g.path]=structuredClone(u.next)}),p=()=>{let c=n.mounts();if(!(d.length===c.length&&c.every((u,g)=>d[g].bag===u.bag))){for(let u of d)u.off();d=c.map(u=>({bag:u.bag,off:l(Ke(u),u.bag)}))}};return p(),{snapshot:o,capture(){let c=o(),f=J(a,c);for(let g of f)s(g);let u=[...i,...f];return i=[],a=c,p(),u},dispose(){for(let c of d)c.off();d=[],i=[]}}}var _=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??Je(t))}}get(e){return this.values[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 o={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let a of this.auditors)a(o);if(!o.silent)for(let a of this.subscribers)a(o);return o}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])=>Ot(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 Ot(n,e,t,r,s=""){let o=r??n.name.toLowerCase();return{name:o,path:s+o,type:n.type,value:e,default:n.default??Je(n),...n.values!==void 0?{values:n.values}:{},...n.stages!==void 0?{stages:n.stages}:{},writable:t??n.writable??!0}}function Je(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 Qe=(n,e)=>`${n}${e}`,ve=(n,e)=>n.shared??e,tt=n=>n.sharedCopies??n.copies??1,G=(n,e)=>new _(n,{normalise:t=>t,pathPrefix:e});function Ye(n){return typeof n=="boolean"?n:typeof n=="number"?n!==0:typeof n=="string"?n!=="":n.length>0}var Tt={story:!0,box:!1,deck:!1,hand:!1,value:!1},rt=(n,e)=>e.shared??Tt[n],Xe=(n,e)=>e.filter(t=>rt(n,t)),nt=(n,e)=>e.filter(t=>!rt(n,t)),st=["box","deck","hand","value"],Dt=()=>({box:{gameId:new Map,id:new Map,repeated:new Map},deck:{gameId:new Map,id:new Map,repeated:new Map},hand:{gameId:new Map,id:new Map,repeated:new Map},value:{gameId:new Map,id:new Map,repeated:new Map}}),me=(n,e)=>{let t=h(e);n.gameId.set(e.id,t),n.id.has(t)||n.id.set(t,e.id)},Ht=(n,e)=>{let t=Ge(e);for(let[r,s]of t.print)n.gameId.set(r,s);for(let[r,s]of t.accept)n.id.set(r,s);for(let[r,s]of t.repeated)n.repeated.set(r,s)},xe=(n,e)=>e.template!==void 0?n.templatesById.get(e.template)?.properties??n.bundle.boxes.flatMap(t=>t.handTemplates).find(t=>t.id===e.template)?.properties??[]:e.properties??[],M=(n,e,t)=>`${e}.${n.owners[e].gameId.get(t)??t}`,At=(n,e,t)=>{let r=n.owners[e].repeated.get(t);if(r!==void 0)return{ambiguous:r};let s=n.owners[e].id.get(t);if(s!==void 0)return{id:s,legacy:!1};if(n.owners[e].gameId.has(t))return{id:t,legacy:!0}},ot=(n,e,t,r)=>{let s=At(n,e,t);if(s===void 0)throw new Error(`no ${e} store "${t}"`);if("ambiguous"in s)throw new Error(qe(t,r,s.ambiguous));return s},at=(n,e,t,r)=>`"${e}.${t}.${r}" names the ${e} by its internal id; write "${M(n,e,t)}.${r}". The internal-id form is refused after the next release.`,it=(n,e)=>{let t=n.bundle,r=(s,o)=>`${M(n,s,o)}.`;return{story:G(e("story",t.story.properties),"story."),box:new Map(t.boxes.map(s=>[s.id,G(e("box",s.properties),r("box",s.id))])),deck:new Map(t.boxes.flatMap(s=>s.decks.map(o=>[o.id,G(e("deck",o.properties),r("deck",o.id))]))),hand:new Map(t.boxes.flatMap(s=>s.hands.map(o=>[o.id,G(e("hand",xe(n,o)),r("hand",o.id))]))),value:new Map(t.boxes.flatMap(s=>s.tagGroups.flatMap(o=>o.tags.map(a=>[a.id,G(e("value",a.properties??[]),r("value",a.id))]))))}},dt=n=>({story:n.story.values,box:Object.fromEntries([...n.box].map(([e,t])=>[e,t.values])),deck:Object.fromEntries([...n.deck].map(([e,t])=>[e,t.values])),hand:Object.fromEntries([...n.hand].map(([e,t])=>[e,t.values])),value:Object.fromEntries([...n.value].map(([e,t])=>[e,t.values]))}),lt=(n,e)=>{n.story.load(e?.story??{});for(let t of["box","deck","hand","value"])for(let[r,s]of Object.entries(e?.[t]??{}))n[t].get(r)?.load(s)},ye=()=>({evicted:[],droppedCooldowns:[],droppedSpent:[],droppedProperties:[],defaultedProperties:[],retypedProperties:[]}),Se="",Y=(n,e)=>[...n].map(t=>({item:t,k:e(t)})).sort((t,r)=>t.k<r.k?-1:t.k>r.k?1:0).map(t=>t.item),we=n=>`${n.flow??""}${Se}${n.path}`;function Nt(n,e){switch(n.type){case"boolean":return typeof e=="boolean";case"number":return typeof e=="number";case"string":return typeof e=="string";case"enum":return typeof e=="string"&&(n.values===void 0||n.values.includes(e));case"quality":return typeof e=="string"&&(n.stages===void 0||n.stages.includes(e));case"flags":return Array.isArray(e)&&(n.values===void 0||e.every(t=>n.values.includes(t)));default:return!0}}function Ze(n,e,t,r,s){let o=l=>({...r!==void 0?{flow:r}:{},path:t(l)}),a=new Map((n??[]).map(l=>[l.name,l])),i=e??{},d={};for(let[l,p]of Object.entries(i)){let c=a.get(l);if(c===void 0){s.droppedProperties.push(o(l));continue}if(!Nt(c,p)){s.retypedProperties.push(o(l));continue}d[l]=p}for(let l of n??[])l.name in i||s.defaultedProperties.push(o(l.name));return d}function et(n,e,t,r,s){let o={story:Ze(e.story,t?.story,a=>`story.${a}`,r,s),box:{},deck:{},hand:{},value:{}};for(let a of["box","deck","hand","value"]){let i=t?.[a]??{},d=[...new Set([...e[a].keys(),...Object.keys(i)])].sort();for(let l of d){let p=M(n,a,l);o[a][l]=Ze(e[a].get(l),i[l],c=>`${p}.${c}`,r,s)}}return o}function be(n,e,t,r){let s=e.version!==n.version||e.hash!==n.hash,o=Y(r.evicted,c=>[c.flow,c.hand,c.card,c.reason].join(Se)),a=Y(r.droppedCooldowns,c=>`${c.flow}${Se}${c.card}`),i=[...r.droppedSpent].sort(),d=Y(r.droppedProperties,we),l=Y(r.defaultedProperties,we),p=Y(r.retypedProperties,we);return{exact:!s&&o.length===0&&a.length===0&&i.length===0&&d.length===0&&l.length===0&&p.length===0,project:n.project,version:{saved:e.version,bundle:n.version},hash:{saved:e.hash,bundle:n.hash},flows:t,evicted:o,droppedCooldowns:a,droppedSpent:i,droppedProperties:d,defaultedProperties:l,retypedProperties:p}}var q=class{internals;seed;onReplacedFlow;flowsById=new Map;engineTraceHandlers=new Set;hostWorld;constructor(e,t={}){this.seed=t.seed??0,this.onReplacedFlow=t.onReplacedFlow,t.world!==void 0&&(this.hostWorld=t.world);let r={bundle:e,...t.log?{logCap:typeof t.log=="object"?t.log.cap??1e3:1e3}:{},cardsById:new Map,cardsByGameId:new Map,boxesByGameId:new Map,boxesById:new Map,handsById:new Map,handsByGameId:new Map,owners:Dt(),templatesById:new Map,groupsById:new Map,requiredGroups:new Set,nodeCache:new WeakMap,ladders:{world:new Map,story:new Map,box:new Map,deck:new Map,value:new Map,hand:new Map},hasQualities:!1,hasShared:!1,flowDecls:{story:[],box:new Map,deck:new Map,hand:new Map,value:new Map},sharedDecls:{story:[],box:new Map,deck:new Map,hand:new Map,value:new Map},shared:void 0,worldResolver:void 0,worldReadOnly:new Set,emitEngine:(o,a,i)=>{this.internals.logCap!==void 0&&(this.engineLog.push({...a,flow:o,seq:this.engineSeq++,...i!==void 0?{turn:i}:{}}),this.engineLog.length>this.internals.logCap&&this.engineLog.splice(0,this.engineLog.length-this.internals.logCap));for(let d of this.engineTraceHandlers)d(o,a)},engineTracing:()=>this.engineTraceHandlers.size>0};this.internals=r,Ht(r.owners.value,e);for(let o of e.boxes){r.boxesById.set(o.id,o),r.boxesByGameId.set(h(o),o),me(r.owners.box,o);for(let a of o.tagGroups)r.groupsById.set(a.id,{group:a,box:o}),a.required===!0&&r.requiredGroups.add(a.id);for(let a of o.decks){me(r.owners.deck,a),a.shared===!0&&(r.hasShared=!0);for(let i of a.cards){let d={card:i,deck:a,box:o};r.cardsById.set(i.id,d),r.cardsByGameId.set(h(i),d),i.shared===!0&&(r.hasShared=!0)}}for(let a of o.handTemplates)r.templatesById.set(a.id,a);for(let a of o.hands)r.handsById.set(a.id,{hand:a,box:o}),r.handsByGameId.set(h(a),{hand:a,box:o}),me(r.owners.hand,a)}this.initLadders();let s=o=>({story:o("story",e.story.properties),box:new Map(e.boxes.map(a=>[a.id,o("box",a.properties)])),deck:new Map(e.boxes.flatMap(a=>a.decks.map(i=>[i.id,o("deck",i.properties)]))),hand:new Map(e.boxes.flatMap(a=>a.hands.map(i=>[i.id,o("hand",xe(r,i))]))),value:new Map(e.boxes.flatMap(a=>a.tagGroups.flatMap(i=>i.tags.map(d=>[d.id,o("value",d.properties??[])]))))});r.flowDecls=s(nt),r.sharedDecls=s(Xe),this.initShared(this.hostWorld)}initShared(e){let t=this.internals;if(t.shared=it(t,Xe),t.worldReadOnly=new Set(t.bundle.world.properties.filter(r=>r.writable===!1).map(r=>r.name)),e!==void 0){t.worldResolver=e;let r=e.set;t.worldSet=r!==void 0?(s,o)=>{r(s,o)}:void 0}else{let r=G(t.bundle.world.properties,"world.");t.worldResolver={get:s=>r.get(s),set:(s,o)=>{r.set(s,o)}},t.worldSet=(s,o,a)=>{r.set(s,o,a===!0?{host:!0}:void 0)}}}initLadders(){let e=this.internals,t=o=>{let a=new Map;for(let i of o??[])i.type==="quality"&&i.stages!==void 0&&a.set(i.name,i.stages);return a},r=e.bundle;e.ladders.world=t(r.world.properties),e.ladders.story=t(r.story.properties);for(let o of r.boxes){e.ladders.box.set(o.id,t(o.properties));for(let a of o.decks)e.ladders.deck.set(a.id,t(a.properties));for(let a of o.tagGroups)for(let i of a.tags)e.ladders.value.set(i.id,t(i.properties));for(let a of o.hands)e.ladders.hand.set(a.id,t(xe(e,a)))}let s=o=>[...o.values()].some(a=>a.size>0);e.hasQualities=e.ladders.world.size>0||e.ladders.story.size>0||s(e.ladders.box)||s(e.ladders.deck)||s(e.ladders.value)||s(e.ladders.hand)}openFlow(e,t={}){let r=t.restore!==void 0?this.sharedClaimsExcept(e):void 0,s=this.flowsById.get(e);if(s){let a=s.heldCardIds().length;a>0&&this.onReplacedFlow?.(e,a),s.markClosed()}let o=new X(this,this.internals,e,t.seed??this.seed);if(this.flowsById.set(e,o),t.restore!==void 0){let a=ye(),i=this.planFlowRestore(e,structuredClone(t.restore),r,a);o.restore(i);let d=this.internals.bundle.content;t.onRestoreReport?.(be(d,d,[e],a))}return o}getFlow(e){return this.flowsById.get(e)}flows(){return[...this.flowsById.values()]}closeFlow(e){let t=this.flowsById.get(e);t&&(this.flowsById.delete(e),t.markClosed())}dropFlow(e,t){this.flowsById.get(e)===t&&this.flowsById.delete(e)}reset(){this.engineLog=[];for(let e of this.flowsById.values())e.markClosed();this.flowsById.clear(),this.spent.clear(),this.initShared(this.hostWorld)}spent=new Set;isTaken(e){return this.spent.has(e)}markTaken(e){this.spent.add(e)}engineLog=[];engineSeq=0;log(){return this.engineLog}clearLog(){this.engineLog=[]}sharedClaims(){let e=new Map;for(let t of this.flowsById.values())for(let r of t.heldCardIds())e.set(r,(e.get(r)??0)+1);return e}sharedClaimsExcept(e){let t=new Map;for(let[r,s]of this.flowsById)if(r!==e)for(let o of s.heldCardIds())t.set(o,(t.get(o)??0)+1);return t}getProperty(e){let t=this.resolveShared(e),r=t.kind==="world"?this.internals.worldResolver.get(t.name):t.bag.get(t.name);if(r===void 0)throw new Error(`no property at "${e}"`);return r}setProperty(e,t){let r=this.resolveShared(e);if(r.kind==="world"){if(!this.internals.worldSet)throw new Error("@world is read-only here: the host bound no write");this.internals.worldSet(r.name,t,!0);return}r.bag.set(r.name,t,{silent:!0,reason:"host setProperty",host:!0})}resolveShared(e){let t=e.split("."),r=()=>{throw new Error(`"${e}" is per-flow state - read it on a Flow, not the Engine`)};if(t.length===2&&t[0]==="world")return{kind:"world",name:t[1]};if(t.length===2&&t[0]==="story"){let s=t[1];if(this.internals.shared.story.get(s)!==void 0)return{kind:"bag",bag:this.internals.shared.story,name:s};throw this.internals.flowDecls.story.some(o=>o.name===s)&&r(),new Error(`no property at "${e}"`)}if(t.length===3&&(t[0]==="box"||t[0]==="deck"||t[0]==="hand"||t[0]==="value")){let s=t[0],[,o,a]=t,i=ot(this.internals,s,o,a);i.legacy&&this.diagnose(at(this.internals,s,o,a));let d=i.id,l=this.internals.shared[s].get(d);if(l!==void 0&&l.get(a)!==void 0)return{kind:"bag",bag:l,name:a};throw this.internals.flowDecls[s].get(d)?.some(p=>p.name===a)&&r(),l===void 0&&!this.internals.flowDecls[s].has(d)?new Error(`no ${s} store "${o}"`):new Error(`no property at "${e}"`)}throw new Error(`bad property path "${e}"`)}diagnose(e){this.internals.emitEngine("",{type:"diagnostic",where:"property address",message:e})}listProperties(){let e=[];for(let r of this.internals.bundle.world.properties){let s=this.internals.worldResolver.get(r.name);e.push({path:`world.${r.name}`,name:r.name,type:r.type,value:s??r.default,default:r.default,...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:this.internals.worldSet!==void 0&&!this.internals.worldReadOnly.has(r.name)})}let t=(r,s)=>{for(let o of s.rows())e.push(o)};t("story",this.internals.shared.story);for(let r of st)for(let[s,o]of this.internals.shared[r])t(M(this.internals,r,s),o);return e}listBags(){let e=[{prefix:"story",bag:this.internals.shared.story}];for(let t of["box","deck","hand","value"])for(let[r,s]of this.internals.shared[t])e.push({prefix:M(this.internals,t,r),bag:s});return e}subscribeTrace(e){return this.engineTraceHandlers.add(e),()=>this.engineTraceHandlers.delete(e)}saveGame(){return structuredClone({schema:"storylets/save@1",content:this.internals.bundle.content,shared:{props:dt(this.internals.shared),spent:[...this.spent].sort()},flows:Object.fromEntries([...this.flowsById].map(([e,t])=>[e,t.snapshot()]))})}saveFlow(e){let t=this.flowsById.get(e);if(!t)throw new Error(`unknown flow "${e}"`);return structuredClone(t.snapshot())}previewLoad(e){return this.assertSameProject(e),this.planLoad(e).report}previewFlowRestore(e,t){let r=ye();this.planFlowRestore(e,t,this.sharedClaimsExcept(e),r);let s=this.internals.bundle.content;return be(s,s,[e],r)}loadGame(e){this.assertSameProject(e);let t=this.planLoad(structuredClone(e));this.reset(),lt(this.internals.shared,t.shared);for(let r of t.spent)this.spent.add(r);for(let[r,s]of t.flows)this.openFlow(r).restore(s);return t.report}assertSameProject(e){if(e.content.project!==this.internals.bundle.content.project)throw new Error(`save is for project "${e.content.project}", bundle is "${this.internals.bundle.content.project}"`)}planLoad(e){let t=ye(),r=et(this.internals,this.internals.sharedDecls,e.shared?.props,void 0,t),s=[];for(let a of e.shared?.spent??[])this.internals.cardsById.has(a)?s.push(a):t.droppedSpent.push(a);let o=[];for(let[a,i]of Object.entries(e.flows??{}))o.push([a,this.planFlowRestore(a,i,void 0,t)]);return{report:be(this.internals.bundle.content,e.content,o.map(([a])=>a),t),shared:r,spent:s,flows:o}}planFlowRestore(e,t,r,s){let o=this.internals,a=et(o,o.flowDecls,t.props,e,s),i={};for(let[c,f]of Object.entries(t.cooldowns??{}))o.cardsById.has(c)?i[c]=f:s.droppedCooldowns.push({flow:e,card:c});let d=c=>{let f=o.cardsById.get(c);return f?h(f.card):c},l={},p=new Map;for(let[c,f]of Object.entries(t.board??{})){let u=o.handsById.get(c);if(u===void 0){for(let w of f)s.evicted.push({flow:e,hand:c,card:d(w),reason:"hand-vanished"});continue}let g=h(u.hand),m=[];for(let w of f){let v=o.cardsById.get(w);if(v===void 0){s.evicted.push({flow:e,hand:g,card:w,reason:"vanished"});continue}if(r!==void 0&&ve(v.card,v.deck.shared??!1)){if((r.get(w)??0)+(p.get(w)??0)>=tt(v.card)){s.evicted.push({flow:e,hand:g,card:h(v.card),reason:"claimed-elsewhere"});continue}p.set(w,(p.get(w)??0)+1)}m.push(w)}l[c]=m}return{props:a,turns:t.turns??{},prng:t.prng,cooldowns:i,board:l,playLog:t.playLog??[]}}},X=class{id;engine;internals;closed=!1;prng;turnCounts=new Map;cooldowns={};boardContents=new Map;playLog=[];playCount=new Map;lastPlayOf=new Map;tagPlayCount=new Map;lastPlayInTag=new Map;stores;traceHandlers=new Set;logEntries=[];logSeq=0;storyReader;boxReaders=new Map;deckReaders=new Map;constructor(e,t,r,s){this.engine=e,this.internals=t,this.id=r,this.prng=H(s),this.stores=it(t,nt);for(let a of t.bundle.boxes){this.turnCounts.set(a.id,0);for(let i of a.hands)this.boardContents.set(i.id,[])}let o=(a,i)=>({get:d=>a?.get(d)??i?.get(d)});this.storyReader=o(this.stores.story,t.shared.story);for(let a of t.bundle.boxes){this.boxReaders.set(a.id,o(this.stores.box.get(a.id),t.shared.box.get(a.id)));for(let i of a.decks)this.deckReaders.set(i.id,o(this.stores.deck.get(i.id),t.shared.deck.get(i.id)))}}get isClosed(){return this.closed}close(){this.closed||(this.engine.dropFlow(this.id,this),this.markClosed())}markClosed(){this.closed=!0}assertOpen(){if(this.closed)throw new Error(`flow "${this.id}" is closed`)}turn(e){this.assertOpen();let t=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!t)throw new Error(`unknown box "${e}"`);return this.turnCounts.get(t.id)??0}subscribeTrace(e){return this.traceHandlers.add(e),()=>this.traceHandlers.delete(e)}get tracing(){return this.traceHandlers.size>0||this.internals.logCap!==void 0||this.internals.engineTracing()}emit(e,t){this.internals.logCap!==void 0&&(this.logEntries.push({...e,seq:this.logSeq++,...t!==void 0?{turn:t}:{}}),this.logEntries.length>this.internals.logCap&&this.logEntries.splice(0,this.logEntries.length-this.internals.logCap));for(let r of this.traceHandlers)r(e);this.internals.emitEngine(this.id,e,t)}log(){return this.logEntries}clearLog(){this.logEntries=[]}node(e){let t=this.internals.nodeCache.get(e);return t||(t=T(e.ast),this.internals.nodeCache.set(e,t)),t}groupInBox(e,t){return e.tagGroups.find(r=>h(r)===t)??e.tagGroups.find(r=>r.id===t)}indexPlay(e){this.playCount.set(e.card,(this.playCount.get(e.card)??0)+1),this.lastPlayOf.set(e.card,e);let t=this.internals.cardsByGameId.get(e.card);if(t)for(let[r,s]of Object.entries(t.card.tags??{}))for(let o of s){let a=Qe(r,o);this.tagPlayCount.set(a,(this.tagPlayCount.get(a)??0)+1),this.lastPlayInTag.set(a,e)}}rebuildPlayIndex(){this.playCount.clear(),this.lastPlayOf.clear(),this.tagPlayCount.clear(),this.lastPlayInTag.clear();for(let e of this.playLog)this.indexPlay(e)}hostsByBox=new Map;host(e){let t=this.hostsByBox.get(e.id);if(t!==void 0)return t;let r=this.makeHost(e);return this.hostsByBox.set(e.id,r),r}makeHost(e){let t=(s,o)=>{let a=this.groupInBox(e,s),i=a?.tags.find(d=>d.gameId===o);return a&&i?Qe(a.id,i.id):void 0},r=s=>{let o=this.internals.cardsByGameId.get(s.card);return o?(this.turnCounts.get(o.box.id)??0)-s.turn:ne};return{nextRandom:()=>this.prng.next(),countPlayed:s=>this.playCount.get(s)??0,turnsSincePlayed:s=>{let o=this.lastPlayOf.get(s);return o?r(o):ne},countPlayedIn:(s,o)=>{let a=t(s,o);return a===void 0?0:this.tagPlayCount.get(a)??0},turnsSincePlayedIn:(s,o)=>{let a=t(s,o),i=a===void 0?void 0:this.lastPlayInTag.get(a);return i?r(i):ne}}}evalCtx(e,t,r){return{scopes:{world:this.internals.worldResolver,story:this.storyReader,box:this.boxReaders.get(e.id)??{},deck:t?this.deckReaders.get(t.id)??{}:{},hand:r.bag},host:this.host(e),...this.internals.hasQualities?{qualities:(s,o)=>s==="world"?this.internals.ladders.world.get(o):s==="story"?this.internals.ladders.story.get(o):s==="box"?this.internals.ladders.box.get(e.id)?.get(o):s==="deck"&&t?this.internals.ladders.deck.get(t.id)?.get(o):s==="hand"?this.handLadder(r,o):void 0}:{}}}handLadder(e,t){let r=e.sources.get(t);if(r)return r.kind==="value"?this.internals.ladders.value.get(r.id)?.get(t):r.kind==="hand"?this.internals.ladders.hand.get(r.id)?.get(t):void 0}eval(e,t){return te(this.node(e),t,he)}passes(e,t,r){if(!e)return!0;try{return Ye(this.eval(e,t))}catch(s){return this.tracing&&this.emit({type:"diagnostic",where:r??"condition",message:s instanceof Error?s.message:String(s)}),!1}}tagByGameId(e,t){return e.tags.find(r=>r.gameId===t)}askForHand(e,t){let r=new Map,s={},o;if(e.template!==void 0){let a=this.internals.templatesById.get(e.template);if(!a)throw new Error(`hand "${h(e)}": unknown template "${e.template}"`);for(let[i,d]of Object.entries(a.bindings??{}))r.set(i,d);for(let[i,d]of Object.entries(e.chosen??{})){if(K(d)){this.fillHoleFromProperty(e,i,d,r,s);continue}r.set(i,d);let l=this.internals.groupsById.get(i),p=l?.group.tags.find(c=>c.id===d);l&&p&&(s[h(l.group)]=h(p))}o=a.condition}else{for(let[a,i]of Object.entries(e.rule?.bindings??{})){if(K(i)){this.fillHoleFromProperty(e,a,i,r,s);continue}r.set(a,i);let d=this.internals.groupsById.get(a),l=d?.group.tags.find(p=>p.id===i);d&&l&&(s[h(d.group)]=h(l))}o=e.rule?.condition}return r.set(L,e.id),this.bindStateGroups(t,r,s),{box:t,hand:e,...o!==void 0?{condition:o}:{},boundTags:r,askNames:s}}askForPeek(e,t){let r=new Map,s={};for(let[o,a]of Object.entries(t)){if(o===L){let l=this.internals.handsByGameId.get(a)??this.internals.handsById.get(a);if(!l)throw new Error(`peek: unknown hand "${a}" in home criteria`);r.set(L,l.hand.id);continue}let i=this.groupInBox(e,o);if(!i)throw new Error(`peek: unknown tag group "${o}" in box "${h(e)}"`);let d=this.tagByGameId(i,a)??i.tags.find(l=>l.id===a);if(!d)throw new Error(`peek: unknown tag "${a}" in group "${h(i)}"`);r.set(i.id,d.id),s[h(i)]=h(d)}return this.bindStateGroups(e,r,s),{box:e,boundTags:r,askNames:s}}fillHoleFromProperty(e,t,r,s,o){let a=this.internals.groupsById.get(t),i=a?h(a.group):t,d=`hand ${h(e)}, tag group ${i}`,l=ze(r);if(!l){this.emit({type:"diagnostic",where:d,message:`"${r}" is not a @hand, @world or @story property reference`});return}if(!a){this.emit({type:"diagnostic",where:d,message:`"${r}" fills a tag group that is not in this box`});return}let p;if(l.scope==="hand")p=this.valuesOf("hand",e.id)[l.name];else try{p=this.getProperty(`${l.scope}.${l.name}`)}catch{p=void 0}if(p===void 0){this.emit({type:"diagnostic",where:d,message:`"${r}" names a property that is not declared`});return}let c=typeof p=="string"?p:String(p),f=a.group.tags.find(u=>h(u)===c);if(!f){this.emit({type:"diagnostic",where:d,message:`${r} is "${c}", which is not one of the tags of "${i}"`});return}s.set(t,f.id),o[i]=h(f)}bindStateGroups(e,t,r){for(let s of e.tagGroups){if(s.boundBy===void 0||t.has(s.id))continue;let o=/^@(world|story)\.([a-z][a-z0-9_-]*)$/.exec(s.boundBy);if(!o){this.emit({type:"diagnostic",where:`tag group ${h(s)}`,message:`boundBy "${s.boundBy}" is not a @world or @story property reference`});continue}let a;try{a=this.getProperty(`${o[1]}.${o[2]}`)}catch{this.emit({type:"diagnostic",where:`tag group ${h(s)}`,message:`boundBy "${s.boundBy}" names a property that is not declared`});continue}let i=typeof a=="string"?a:String(a),d=s.tags.find(l=>h(l)===i);if(!d){this.emit({type:"diagnostic",where:`tag group ${h(s)}`,message:`${s.boundBy} is "${i}", which is not one of its tags`});continue}t.set(s.id,d.id),r[h(s)]=h(d)}}valuesOf(e,t){return{...this.internals.shared[e].get(t)?.values??{},...this.stores[e].get(t)?.values??{}}}buildHandEnv(e){let t={},r=new Map;for(let[s,o]of e.boundTags)if(s!==L)for(let[a,i]of Object.entries(this.valuesOf("value",o)))t[a]=i,r.set(a,{kind:"value",id:o});if(e.hand)for(let[s,o]of Object.entries(this.valuesOf("hand",e.hand.id)))t[s]=o,r.set(s,{kind:"hand",id:e.hand.id});for(let[s,o]of Object.entries(e.askNames))t[s]=o,r.set(s,{kind:"criteria"});return{bag:t,sources:r,boundTags:e.boundTags}}claims(){let e=new Map;for(let t of this.boardContents.values())for(let r of t)e.set(r,(e.get(r)??0)+1);return e}heldCardIds(){return[...this.boardContents.values()].flat()}copiesOf(e){return e.copies??1}claimVerdict(e,t,r,s){let o=e.id;return(r.get(o)??0)>=this.copiesOf(e)?"claimed":t&&(s.get(o)??0)>=tt(e)?"claimed-elsewhere":null}tagsMatch(e,t){let r=e.tags?.[L];if(r!==void 0&&r.length>0){let s=t.get(L);if(s===void 0||!r.includes(s))return!1}for(let[s,o]of t){if(s===L)continue;let a=e.tags?.[s];if(a===void 0){if(this.internals.requiredGroups.has(s))return!1;continue}if(!a.includes(o))return!1}return!0}runAsk(e,t,r){let{box:s}=e,o=this.buildHandEnv(e),a=(c,f)=>{r?.push({id:h(c),verdict:f})};if(!this.passes(e.condition,this.evalCtx(s,void 0,o),`hand ${e.hand?h(e.hand):""} condition`))return{ordered:[],handEnv:o};let i=new Map;for(let c of s.decks)i.set(c.id,this.passes(c.condition,this.evalCtx(s,c,o),`deck ${c.gameId} gate`));let d=this.turnCounts.get(s.id)??0,l=[];for(let c of s.decks){let f=this.evalCtx(s,c,o),u=c.shared??!1;for(let g of c.cards){let m=ve(g,u);if(!i.get(c.id)){a(g,"deck-gate");continue}if(m&&this.engine.isTaken(g.id)){a(g,"taken");continue}if((this.cooldowns[g.id]??0)>d){a(g,"cooldown");continue}if(!this.tagsMatch(g,o.boundTags)){a(g,"tags");continue}if(g.condition&&!this.passes(g.condition,f,this.tracing?`card ${g.gameId} condition`:void 0)){a(g,"condition");continue}let w=t(g,m);if(w){a(g,w);continue}let v;if(typeof g.priority=="number")v=g.priority;else try{let x=this.eval(g.priority,f);if(typeof x!="number"){a(g,"priority");continue}v=x}catch(x){this.tracing&&this.emit({type:"diagnostic",where:`card ${g.gameId} priority`,message:x instanceof Error?x.message:String(x)}),a(g,"priority");continue}let y=0;if(s.ranking.specificity&&g.condition){let x=this.node(g.condition);y=je(x,k=>{try{return Ye(te(k,f,he))}catch{return!1}})}l.push({entry:{card:g,deck:c,box:s},priority:v,spec:y})}}l.sort((c,f)=>f.priority-c.priority||f.spec-c.spec);let p=0;for(;p<l.length;){let c=p+1;for(;c<l.length&&l[c].priority===l[p].priority&&l[c].spec===l[p].spec;)c++;if(c-p>1){let f=l.slice(p,c);A(f,this.prng);for(let u=0;u<f.length;u++)l[p+u]=f[u]}p=c}for(let c of l)r?.push({id:h(c.entry.card),verdict:"dealt",priority:c.priority,specificity:c.spec});return{ordered:l.map(c=>c.entry),handEnv:o}}capTrace(e,t){for(let r of e)r.verdict==="dealt"&&!t.has(r.id)&&(r.verdict="capped")}view(e){let{card:t}=e;return{id:t.id,gameId:h(t),...t.title!==void 0?{title:t.title}:{},...t.purpose!==void 0?{purpose:t.purpose}:{},...t.fields!==void 0?{fields:t.fields}:{}}}handCapacity(e){if(e.slots!==void 0)return e.slots;let t=e.template!==void 0?this.internals.templatesById.get(e.template)?.slots:e.rule?.slots;return t===void 0||t==="unbounded"?1/0:t}resolveHand(e){let t=this.internals.handsByGameId.get(e)??this.internals.handsById.get(e);if(!t)throw new Error(`unknown hand "${e}"`);return t}peek(e,t={},r){this.assertOpen();let s=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!s)throw new Error(`unknown box "${e}"`);let o=this.askForPeek(s,t),a=this.claims(),i=this.internals.hasShared?this.engine.sharedClaims():new Map,d=this.tracing?[]:void 0,{ordered:l}=this.runAsk(o,(c,f)=>this.claimVerdict(c,f,a,i),d),p=r===void 0?l:l.slice(0,Math.max(r,0));return d&&(this.capTrace(d,new Set(p.map(c=>h(c.card)))),this.emit({type:"peek",box:h(s),criteria:t,cards:d},this.turnCounts.get(s.id)??0)),{box:h(s),cards:p.map(c=>this.view(c))}}deal(e){this.assertOpen();let{hand:t}=this.resolveHand(e);return this.dealMany([e])[h(t)]??[]}dealMany(e){this.assertOpen();let t=(e??[...this.internals.handsById.keys()].sort()).map(o=>this.resolveHand(o));A(t,this.prng);for(let{hand:o,box:a}of t){let i=this.askForHand(o,a),d=this.buildHandEnv(i),l=this.passes(i.condition,this.evalCtx(a,void 0,d)),p=new Map;for(let m of a.decks)p.set(m.id,this.passes(m.condition,this.evalCtx(a,m,d)));let c=this.turnCounts.get(a.id)??0,f=[],u=(m,w)=>{let v=this.internals.cardsById.get(m);return f.push({card:v?h(v.card):m,reason:w}),!1},g=(this.boardContents.get(o.id)??[]).filter(m=>{if(!l)return u(m,"hand-condition");let w=this.internals.cardsById.get(m);return w?p.get(w.deck.id)?(this.cooldowns[m]??0)>c?u(m,"cooldown"):this.tagsMatch(w.card,d.boundTags)?this.passes(w.card.condition,this.evalCtx(a,w.deck,d),`card ${w.card.gameId} condition`)?!0:u(m,"condition"):u(m,"tags"):u(m,"deck-gate"):u(m,"vanished")});if(this.boardContents.set(o.id,g),this.tracing)for(let m of f)this.emit({type:"evict",hand:h(o),card:m.card,reason:m.reason},c)}let r=this.claims(),s=this.internals.hasShared?this.engine.sharedClaims():new Map;for(let{hand:o,box:a}of t){let i=this.boardContents.get(o.id)??[],d=this.handCapacity(o)-i.length;if(d<=0)continue;let l=this.askForHand(o,a),p=new Set(i),c=this.tracing?[]:void 0,{ordered:f}=this.runAsk(l,(m,w)=>p.has(m.id)?"claimed":this.claimVerdict(m,w,r,s),c),u=f.slice(0,d),g=u.map(m=>m.card.id);this.boardContents.set(o.id,[...i,...g]);for(let m of g)r.set(m,(r.get(m)??0)+1),s.set(m,(s.get(m)??0)+1);c&&(this.capTrace(c,new Set(u.map(m=>h(m.card)))),this.emit({type:"deal",hand:h(o),cards:c},this.turnCounts.get(a.id)??0))}return Object.fromEntries(t.map(({hand:o})=>[h(o),(this.boardContents.get(o.id)??[]).map(a=>this.view(this.internals.cardsById.get(a)))]))}board(e){this.assertOpen();let t;if(e!==void 0){let r=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!r)throw new Error(`unknown box "${e}"`);t=r.id}return Object.fromEntries([...this.boardContents.entries()].filter(([r])=>t===void 0||this.internals.handsById.get(r).box.id===t).map(([r,s])=>[h(this.internals.handsById.get(r).hand),s.map(o=>this.view(this.internals.cardsById.get(o)))]))}resolveDealt(e,t){let r=this.internals.cardsById.get(e)??this.internals.cardsByGameId.get(e);if(!r)throw new Error(`unknown card "${e}"`);let{hand:s,box:o}=this.resolveHand(t);if(!(this.boardContents.get(s.id)??[]).includes(r.card.id))throw new Error(`card "${h(r.card)}" is not dealt to hand "${h(s)}"`);return{entry:r,ask:this.askForHand(s,o)}}outcomes(e,t){this.assertOpen();let{entry:r,ask:s}=this.resolveDealt(e,t),o=this.evalCtx(r.box,r.deck,this.buildHandEnv(s));return r.card.outcomes.map(a=>({id:a.id,gameId:h(a),...a.title!==void 0?{title:a.title}:{},...a.purpose!==void 0?{purpose:a.purpose}:{},...a.fields!==void 0?{fields:a.fields}:{},available:this.passes(a.condition,o)}))}play(e,t,r,s={}){this.assertOpen();let{entry:o,ask:a}=this.resolveDealt(e,r),i=t==="";if(i&&o.card.outcomes.length>0)throw new Error(`card "${h(o.card)}" has outcomes (${o.card.outcomes.map(v=>h(v)).join(", ")}); name the one played`);let d=i?void 0:o.card.outcomes.find(v=>h(v)===t);if(!i&&!d)throw new Error(`card "${h(o.card)}" has no outcome "${t}"`);let l=this.buildHandEnv(a),p=this.evalCtx(o.box,o.deck,l);if(d&&!this.passes(d.condition,p))throw new Error(`outcome "${t}" on "${h(o.card)}" is gated shut`);let c=o.box.turn!==void 0?0:this.internals.bundle.settings.playAdvancesTurns,f=(this.turnCounts.get(o.box.id)??0)+(s.advanceTurns??c),u=[];for(let[v,y]of Object.entries(d?.changes??{}))u.push({target:v,value:this.eval(y,p)});for(let{target:v,value:y}of u){let{path:x,prev:k}=this.applyWrite(v,y,o,l);this.tracing&&this.emit({type:"write",target:v,path:x,value:y,...k!==void 0?{prev:k}:{}},f)}let g=d?h(d):"",m={card:h(o.card),outcome:g,turn:f};this.playLog.push(m),this.indexPlay(m),o.card.redraw==="never"?ve(o.card,o.deck.shared??!1)?this.engine.markTaken(o.card.id):this.cooldowns[o.card.id]=Number.MAX_SAFE_INTEGER:typeof o.card.redraw=="number"&&(this.cooldowns[o.card.id]=f+o.card.redraw);let w=a.hand.id;this.boardContents.set(w,(this.boardContents.get(w)??[]).filter(v=>v!==o.card.id)),this.turnCounts.set(o.box.id,f),this.tracing&&this.emit({type:"play",card:h(o.card),outcome:g,turn:f},f)}address(e,t){return M(this.internals,e,t)}landIn(e,t,r,s,o){let a=e==="story"?this.stores.story:t!==void 0?this.stores[e].get(t):void 0,i=e==="story"?this.internals.shared.story:t!==void 0?this.internals.shared[e].get(t):void 0,d=a!==void 0&&a.get(r)!==void 0?a:i!==void 0&&i.get(r)!==void 0?i:void 0;if(d===void 0)throw new Error(`no property at "${o}"`);let l=d.set(r,s);return{path:o,...l.prev!==void 0?{prev:l.prev}:{}}}applyWrite(e,t,r,s){let o=/^@([a-z]+)\.([A-Za-z_][A-Za-z0-9_-]*)$/.exec(e);if(!o)throw new Error(`bad change target "${e}"`);let[,a,i]=o;switch(a){case"world":{let d=this.internals.worldSet;if(!d)throw new Error(`@world.${i} cannot be written: the host bound @world read-only`);if(this.internals.worldReadOnly.has(i))throw new Error(`'@world.${i}' is read-only (writable: false)`);let l=this.internals.worldResolver.get(i);return d(i,t),{path:`world.${i}`,...l!==void 0?{prev:l}:{}}}case"story":return this.landIn("story",void 0,i,t,`story.${i}`);case"box":return this.landIn("box",r.box.id,i,t,`${this.address("box",r.box.id)}.${i}`);case"deck":return this.landIn("deck",r.deck.id,i,t,`${this.address("deck",r.deck.id)}.${i}`);case"hand":{let d=s.sources.get(i);if(!d)throw new Error(`@hand.${i} is not composed in this ask`);if(d.kind==="criteria")throw new Error(`@hand.${i} is a chosen tag / criteria name and cannot be written`);return this.landIn(d.kind,d.id,i,t,`${this.address(d.kind,d.id)}.${i}`)}default:throw new Error(`bad change target scope "@${a}"`)}}advanceTurns(e,t=1){this.assertOpen();let r=this.internals.boxesByGameId.get(e)??this.internals.boxesById.get(e);if(!r)throw new Error(`unknown box "${e}"`);let s=(this.turnCounts.get(r.id)??0)+t;this.turnCounts.set(r.id,s),this.tracing&&this.emit({type:"turns",box:h(r),turn:s},s)}listBoxes(){return this.assertOpen(),this.internals.bundle.boxes.map(e=>({id:e.id,gameId:h(e),...e.title!==void 0?{title:e.title}:{},turn:this.turnCounts.get(e.id)??0}))}listBags(){this.assertOpen();let e=[{prefix:"story",bag:this.stores.story}];for(let t of["box","deck","hand","value"])for(let[r,s]of this.stores[t])e.push({prefix:M(this.internals,t,r),bag:s});return e}listProperties(){this.assertOpen();let e=[];for(let r of this.internals.bundle.world.properties){let s=this.internals.worldResolver.get(r.name);e.push({path:`world.${r.name}`,name:r.name,type:r.type,value:s??r.default,default:r.default,...r.values!==void 0?{values:r.values}:{},...r.stages!==void 0?{stages:r.stages}:{},writable:this.internals.worldSet!==void 0&&!this.internals.worldReadOnly.has(r.name)})}let t=(r,s,o)=>{for(let a of[s,o])if(a!==void 0)for(let i of a.rows())e.push(i)};t("story",this.internals.shared.story,this.stores.story);for(let r of st){let s=new Set([...this.internals.shared[r].keys(),...this.stores[r].keys()]);for(let o of s)t(M(this.internals,r,o),this.internals.shared[r].get(o),this.stores[r].get(o))}return e}getProperty(e){this.assertOpen();let t=this.resolvePath(e),r=t.kind==="world"?this.internals.worldResolver.get(t.name):t.own?.get(t.name)??t.shared?.get(t.name);if(r===void 0)throw new Error(`no property at "${e}"`);return r}setProperty(e,t){this.assertOpen();let r=this.resolvePath(e);if(r.kind==="world"){if(!this.internals.worldSet)throw new Error("@world is read-only here: the host bound no write");this.internals.worldSet(r.name,t,!0);return}let s=r.own!==void 0&&r.own.get(r.name)!==void 0?r.own:r.shared!==void 0&&r.shared.get(r.name)!==void 0?r.shared:void 0;if(s===void 0)throw new Error(`no property at "${e}"`);s.set(r.name,t,{silent:!0,reason:"host setProperty",host:!0})}resolvePath(e){let t=e.split(".");if(t.length===2&&t[0]==="world")return{kind:"world",name:t[1]};if(t.length===2&&t[0]==="story")return{kind:"bag",own:this.stores.story,shared:this.internals.shared.story,name:t[1]};if(t.length===3&&(t[0]==="box"||t[0]==="deck"||t[0]==="hand"||t[0]==="value")){let r=t[0],[,s,o]=t,a=ot(this.internals,r,s,o);a.legacy&&this.tracing&&this.emit({type:"diagnostic",where:"property address",message:at(this.internals,r,s,o)});let i=this.stores[r].get(a.id),d=this.internals.shared[r].get(a.id);if(i===void 0&&d===void 0)throw new Error(`no ${r} store "${s}"`);return{kind:"bag",...i!==void 0?{own:i}:{},...d!==void 0?{shared:d}:{},name:o}}throw new Error(`bad property path "${e}"`)}snapshot(){return{props:dt(this.stores),turns:Object.fromEntries(this.turnCounts),prng:this.prng.state(),cooldowns:this.cooldowns,board:Object.fromEntries(this.boardContents),playLog:this.playLog}}restore(e){lt(this.stores,e.props),this.turnCounts=new Map(this.internals.bundle.boxes.map(t=>[t.id,0]));for(let[t,r]of Object.entries(e.turns??{}))this.turnCounts.has(t)&&this.turnCounts.set(t,r);this.prng=H(e.prng),this.cooldowns=e.cooldowns??{},this.playLog=e.playLog??[],this.rebuildPlayIndex(),this.boardContents=new Map(Object.entries(e.board??{}).filter(([t])=>this.internals.handsById.has(t)).map(([t,r])=>[t,r.filter(s=>this.internals.cardsById.has(s))]));for(let t of this.internals.handsById.keys())this.boardContents.has(t)||this.boardContents.set(t,[])}};var Ee=n=>n.map(e=>({name:e.name,type:e.type,default:e.default,...e.values!==void 0?{values:e.values}:{},...e.durable===!0?{durable:!0}:{},...e.purpose!==void 0?{purpose:e.purpose}:{}})),ct=n=>n.decks.reduce((e,t)=>e+t.cards.filter(r=>(r.durable??t.durable)===!0).length,0),Vt=(n,e)=>n.template!==void 0?e.handTemplates.find(t=>t.id===n.template)?.properties??[]:n.properties??[],Ft=(n,e)=>{let t=n.template!==void 0?n.chosen:n.rule?.bindings,r=[];for(let[s,o]of Object.entries(t??{})){if(!K(o))continue;let a=e.tagGroups.find(i=>i.id===s);a!==void 0&&r.push({group:h(a),from:o})}return r},jt=(n,e)=>{if(n.slots!==void 0)return n.slots;let t=n.template!==void 0?e.handTemplates.find(r=>r.id===n.template)?.slots:n.rule?.slots;return t===void 0?"unbounded":t};function oe(n){let e=[],t=[],r=[{scope:"world",owner:"",properties:Ee(n.world.properties)},{scope:"story",owner:"",properties:Ee(n.story.properties)}],s={boxes:0,decks:0,cards:0,hands:0,templates:0,tagGroups:0};for(let o of n.boxes){let a=h(o),i=o.decks.reduce((l,p)=>l+p.cards.length,0);e.push({gameId:a,...o.title!==void 0?{title:o.title}:{},ranking:{specificity:o.ranking.specificity},...o.turn!==void 0?{turn:{seconds:o.turn.seconds}}:{},...ct(o)>0?{durableCards:ct(o)}:{},tagGroups:o.tagGroups.map(l=>({gameId:h(l),tags:l.tags.map(p=>h(p))})),counts:{decks:o.decks.length,cards:i,hands:o.hands.length,templates:o.handTemplates.length,tagGroups:o.tagGroups.length}}),s.boxes+=1,s.decks+=o.decks.length,s.cards+=i,s.hands+=o.hands.length,s.templates+=o.handTemplates.length,s.tagGroups+=o.tagGroups.length;for(let l of o.hands){let p=l.template!==void 0?o.handTemplates.find(f=>f.id===l.template):void 0,c=Ft(l,o);t.push({gameId:h(l),...l.title!==void 0?{title:l.title}:{},box:a,slots:jt(l,o),...p!==void 0?{template:h(p)}:{},...c.length>0?{movable:c}:{}})}let d=(l,p,c,f)=>{c.length!==0&&r.push({scope:l,owner:p,box:a,...f!==void 0?{group:f}:{},properties:Ee(c)})};d("box",a,o.properties);for(let l of o.decks)d("deck",h(l),l.properties);for(let l of o.hands)d("hand",h(l),Vt(l,o));for(let l of o.tagGroups)for(let p of l.tags)d("tag",h(p),p.properties??[],h(l))}return{identity:{schema:n.schema,project:n.content.project,version:n.content.version,hash:n.content.hash,metadata:n.metadata},totals:s,boxes:e,hands:t,properties:r,maps:(n.maps??[]).map(o=>({box:o.box,group:o.group,zones:o.zones.length,backgrounds:o.backgrounds?.length??0,sites:o.sites?.length??0}))}}function pt(n,e){let t={};for(let{bag:r}of[...n.listBags(),...e.listBags()])for(let s of r.rows())s.value!==void 0&&(t[s.path]=s.value);return Object.assign(t,ut(n.saveGame().flows[e.id])),t}function ut(n){let e={};if(n===void 0)return e;for(let[t,r]of Object.entries(n.turns))e[`turn:${t}`]=r;for(let[t,r]of Object.entries(n.cooldowns))e[`cooldown:${t}`]=r;for(let[t,r]of Object.entries(n.board))e[`board:${t}`]=[...r];return e}function gt(n,e,t={}){let r=e.id,s=()=>n.getFlow(r);return Q({mounts:()=>[...n.listBags(),...s()?.listBags()??[]].map(({bag:o})=>({bag:o})),extra:()=>ut(n.saveGame().flows[r])},t)}function ae(n,e){return JSON.stringify(ke(n,e),null,2)}function ke(n,e){return{schema:se,engine:n.saveGame(),...e!==void 0?{world:e}:{}}}function Pe(n,e){if(!e||typeof e!="object"||e.schema!==se||e.engine?.schema!==Ue)throw new Error(`not a storylets save (expected schema "${se}")`);return n.loadGame(e.engine),e.world}function ie(n,e){let t;try{t=JSON.parse(e)}catch{throw new Error("not valid JSON")}return Pe(n,t)}var ft="sl-inspector-style",_t=`
2
2
  .sl-insp { font: 12px system-ui, sans-serif; color: var(--ink, #222); background: var(--surface, #fafafa);
3
3
  border: 1px solid var(--line, #ccc); border-radius: 8px; padding: 10px 12px; max-width: 26rem; }
4
4
  .sl-insp h3 { margin: 0 0 8px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em;
@@ -27,7 +27,7 @@
27
27
  .sl-insp .sl-ident { font-family: ui-monospace, monospace; font-size: 11px; }
28
28
  .sl-insp .sl-ident b { font-weight: 600; }
29
29
  .sl-insp .sl-note { color: var(--muted, #666); }
30
- `;function Z(){if(document.getElementById(ft))return;let n=document.createElement("style");n.id=ft,n.textContent=_t,document.head.append(n)}var Gt=(n,e)=>JSON.stringify(n)===JSON.stringify(e),qt=["deal","play","write","evict","turns","diagnostic"],zt={deal:"Deal",play:"Play",write:"Write",evict:"Evict",turns:"Turns",diagnostic:"Diag"},Ut=n=>n.type==="peek"?"deal":n.type,ht=n=>n===void 0?"<unset>":JSON.stringify(n);function Ce(n){return Wt(n,"flow"in n&&n.flow?`${n.flow} `:"")}function Wt(n,e){let t=(n.turn!==void 0?`[${n.turn}] `:"[-] ")+e;switch(n.type){case"deal":{let r=n.cards.filter(s=>s.verdict==="dealt").map(s=>s.id);return`${t}deal ${n.hand}: ${r.length>0?r.join(", "):"(none)"} (${n.cards.length} considered)`}case"peek":{let r=Object.entries(n.criteria).map(([o,a])=>`${o}=${a}`).join(", "),s=n.cards.filter(o=>o.verdict==="dealt").map(o=>o.id);return`${t}peek ${n.box}${r?` [${r}]`:""}: ${s.length>0?s.join(", "):"(none)"} (${n.cards.length} considered)`}case"evict":return`${t}evict ${n.card} from ${n.hand} (${n.reason})`;case"play":return`${t}play ${n.card} -> ${n.outcome}`;case"write":return`${t}write ${n.path}: ${ht(n.prev)} -> ${ht(n.value)}`;case"turns":return`${t}turns ${n.box} -> ${n.turn}`;default:return`${t}diagnostic ${n.where}: ${n.message}`}}var Kt=n=>{let e=n.split(".");return e.length===3?`${e[0]} ${e[1]}`:e[0]};function mt(n,e,t={}){Z();let r=e,s=()=>r,o=document.createElement("div");o.className="sl-insp";let a=document.createElement("div");a.className="sl-head";let i=document.createElement("h3");i.textContent=t.title??"Runtime state",a.append(i);let l=document.createElement("button");l.type="button",l.className="sl-save",l.textContent="Save state",l.addEventListener("click",()=>{let x=new Blob([ae(n)],{type:"application/json"}),P=URL.createObjectURL(x),S=document.createElement("a");S.href=P,S.download="save.storyletsave",S.click(),URL.revokeObjectURL(P)}),a.append(l);let d=document.createElement("input");d.type="file",d.accept=".storyletsave,application/json",d.hidden=!0,d.addEventListener("change",()=>{let x=d.files?.[0];if(!x)return;let P=new FileReader;P.onload=()=>{try{ie(n,String(P.result)),r=n.getFlow(e.id)??n.openFlow(e.id),de()}catch(S){console.error("storylets inspector: load failed:",S instanceof Error?S.message:S)}d.value=""},P.readAsText(x)});let g=document.createElement("button");g.type="button",g.className="sl-load",g.textContent="Load state",g.addEventListener("click",()=>d.click()),a.append(g,d),o.append(a);let c=document.createElement("input");c.type="text",c.className="sl-filter",c.placeholder="Filter properties",o.append(c);let f=[],p=[],u="";for(let x of s().listProperties()){let P=Kt(x.path);if(P!==u){let C=document.createElement("div");C.className="sl-group",C.textContent=P,o.append(C),p.push({el:C,rows:[]}),u=P}let S=Jt(s,x,f);p[p.length-1].rows.push({el:S,text:`${x.name} ${x.path}`.toLowerCase()}),o.append(S)}c.addEventListener("input",()=>{let x=c.value.trim().toLowerCase();for(let P of p){let S=!1;for(let C of P.rows){let $=x===""||C.text.includes(x);C.el.style.display=$?"":"none",S=S||$}P.el.style.display=S?"":"none"}});let h=document.createElement("div");h.className="sl-section",h.textContent="Turns (per box)";let y=document.createElement("div");y.className="sl-turns";let E=document.createElement("div");E.className="sl-section",E.textContent="Board";let w=document.createElement("div");w.className="sl-board",o.append(h,y,E,w);let v=(x,P,S)=>{let C=document.createElement("label");C.className=S;let $=document.createElement("input");return $.type="checkbox",$.checked=!0,$.addEventListener("change",()=>P($.checked)),C.append($,x),C},k=(x,P,S,C,$)=>{let ce=document.createElement("div");ce.className=`sl-section sl-${x}`,ce.textContent=P;let z=document.createElement("div");z.className=`sl-logbar sl-${x}`;let U=document.createElement("div");U.className=`sl-log sl-${x}`;let pe=new Map,Te=()=>S().filter(I=>pe.get(Ut(I))!==!1).map(Ce),De=!0,He="",ue=(I=!1)=>{let F=S(),Ae=`${F.length}:${F.length>0?F[F.length-1].seq:-1}`;if(!I&&Ae===He)return;He=Ae;let Ne=Te();U.textContent=Ne.length>0?Ne.join(`
31
- `):$,De&&(U.scrollTop=U.scrollHeight)};for(let I of qt)pe.set(I,!0),z.append(v(zt[I],F=>{pe.set(I,F),ue(!0)},"sl-logkind"));z.append(v("Autoscroll",I=>{De=I},"sl-logscroll"));let N=document.createElement("button");N.type="button",N.className="sl-logcopy",N.textContent="Copy",N.title="Copy the visible (filtered) log to the clipboard",N.addEventListener("click",()=>{navigator.clipboard?.writeText(Te().join(`
32
- `))});let V=document.createElement("button");return V.type="button",V.className="sl-logclear",V.textContent="Clear",V.title="Drop the retained log entries (cosmetic - no game state changes)",V.addEventListener("click",()=>{C(),ue(!0)}),z.append(N,V),o.append(ce,z,U),{render:ue}},O=k("flow","Log",()=>s().log(),()=>s().clearLog(),"(empty - new Engine(bundle, { log: true }) retains the flow log)"),xt=k("run","Run log (every flow)",()=>n.log(),()=>n.clearLog(),"(empty - new Engine(bundle, { log: true }) retains the run log)"),Re=(x=!1)=>{xt.render(x),O.render(x)},Le=(x,P)=>{let S=document.createElement("div");S.className="sl-line",S.textContent=P,x.append(S)},Me=()=>{y.textContent="";for(let x of s().listBoxes())Le(y,`${x.title??x.gameId}: turn ${x.turn}`);w.textContent="";for(let[x,P]of Object.entries(s().board())){let S=P.map(C=>C.title??C.gameId);Le(w,`${x}: ${S.length>0?S.join(", "):"(empty)"}`)}},de=()=>{for(let x of f)x.read();Me(),Re()};Me(),Re(!0);let le,Oe=t.pollMs??250;return Oe>0&&(le=setInterval(de,Oe)),(t.container??document.body).append(o),{el:o,refresh:de,destroy(){le!==void 0&&clearInterval(le),o.remove()}}}function Jt(n,e,t){let r=document.createElement("div");r.className="sl-row";let s=document.createElement("span");s.className="sl-name",s.textContent=e.name,s.title=e.path;let o=()=>{try{return n().getProperty(e.path)}catch{return}},a=()=>{},i=p=>{n().setProperty(e.path,p),a()},l=document.createElement("button");l.type="button",l.className="sl-reset",l.textContent="\u21BA",l.title="Reset to default",l.addEventListener("click",()=>i(e.default));let d,g,c=p=>document.activeElement===p;switch(e.type){case"boolean":{let p=document.createElement("input");p.type="checkbox",p.addEventListener("change",()=>i(p.checked)),d=p,g=()=>{c(p)||(p.checked=o()===!0)};break}case"number":{let p=document.createElement("input");p.type="number",p.addEventListener("change",()=>i(Number(p.value))),d=p,g=()=>{c(p)||(p.value=String(o()??0))};break}case"enum":case"quality":{let p=document.createElement("select");for(let u of(e.type==="quality"?e.stages:e.values)??[]){let h=document.createElement("option");h.value=u,h.textContent=u,p.append(h)}p.addEventListener("change",()=>i(p.value)),d=p,g=()=>{c(p)||(p.value=String(o()??""))};break}case"flags":{let p=document.createElement("input");p.type="text",p.placeholder="comma, separated, flags",p.addEventListener("change",()=>i(p.value.split(",").map(u=>u.trim()).filter(u=>u.length>0))),d=p,g=()=>{c(p)||(p.value=(o()??[]).join(", "))};break}default:{let p=document.createElement("input");p.type="text",p.addEventListener("change",()=>i(p.value)),d=p,g=()=>{c(p)||(p.value=String(o()??""))}}}let f=()=>{g(),l.disabled=Gt(o(),e.default)};return a=f,f(),t.push({row:e,read:f}),r.append(s,d,l),r}var Qt=n=>n===void 0?"<unset>":JSON.stringify(n);function Be(n){let e=n.values!==void 0&&n.values.length>0?` [${n.values.join(", ")}]`:"",t=n.durable===!0?" (durable)":"";return`${n.name}: ${n.type} = ${Qt(n.default)}${e}${t}`}function $e(n){if(n.scope==="world"||n.scope==="story")return n.scope;let e=n.group!==void 0?` (${n.group})`:"";return`${n.scope} ${n.owner}${e}`}var B=(n,e,t="sl-line")=>{let r=document.createElement("div");return r.className=t,r.textContent=e,n.append(r),r},ee=(n,e,t)=>{let r=document.createElement("details");r.className="sl-fold",r.open=t;let s=document.createElement("summary");s.textContent=e,r.append(s);let o=document.createElement("div");return r.append(o),n.append(r),o};function yt(n,e={}){Z();let t=oe(n),r=e.open??!0,s=document.createElement("div");s.className="sl-insp";let o=document.createElement("div");o.className="sl-head";let a=document.createElement("h3");a.textContent=e.title??"Bundle",o.append(a),s.append(o);let{identity:i,totals:l}=t,d=document.createElement("div");d.className="sl-ident",s.append(d),B(d,`${i.project} ${i.version}`,"sl-line"),B(d,`schema ${i.schema}`,"sl-line sl-note"),B(d,`hash ${i.hash===""?"(none)":i.hash} - metadata ${i.metadata}`,"sl-line sl-note");let g=ee(s,"Hands (deal)",r);g.className="sl-hands",t.hands.length===0&&B(g,"(no hands - this bundle is peek-only)","sl-line sl-note");for(let u of t.hands){let h=u.template!==void 0?`, template ${u.template}`:"",y=u.movable===void 0?"":`, moves ${u.movable.map(E=>`${E.group} from ${E.from}`).join(" and ")}`;B(g,`${u.gameId}: box ${u.box}, slots ${u.slots}${h}${y}`+(u.title!==void 0?` - ${u.title}`:""))}let c=ee(s,"Tags by box (peek criteria)",r);c.className="sl-tags";for(let u of t.boxes){B(c,`${u.title??u.gameId}`,"sl-group"),u.tagGroups.length===0&&B(c," (no tag groups)","sl-line sl-note");for(let h of u.tagGroups)B(c,` ${h.gameId}: ${h.tags.length>0?h.tags.join(", "):"(no tags)"}`)}let f=ee(s,"Properties (declared)",r);f.className="sl-props";for(let u of t.properties){B(f,$e(u),"sl-group"),u.properties.length===0&&B(f," (none declared)","sl-line sl-note");for(let h of u.properties)B(f,` ${Be(h)}`)}if(t.maps.length>0){let u=ee(s,"Maps (carried, not read)",r);u.className="sl-maps",B(u,"Geometry the build was asked to carry. The engine ignores it.","sl-line sl-note");for(let h of t.maps)B(u,`${h.box} - ${h.group}: zones ${h.zones}, pictures ${h.backgrounds}, sites ${h.sites}`)}let p=ee(s,"Counts",r);p.className="sl-counts",B(p,`boxes ${l.boxes} - decks ${l.decks} - cards ${l.cards}`),B(p,`hands ${l.hands} - templates ${l.templates} - tag groups ${l.tagGroups}`);for(let u of t.boxes)B(p,`${u.gameId}: decks ${u.counts.decks}, cards ${u.counts.cards}, hands ${u.counts.hands}, templates ${u.counts.templates}, tag groups ${u.counts.tagGroups}, ranking.specificity ${u.ranking.specificity}`+(u.turn!==void 0?`, turn = ${u.turn.seconds}s`:"")+(u.durableCards!==void 0?`, durable cards ${u.durableCards}`:""));return(e.container??document.body).append(s),{el:s,description:t,destroy(){s.remove()}}}var Yt="ws://127.0.0.1:4472",Xt=new Set(["deal","play","evict","turns"]);function Ie(n){let e=n.id,t={};for(let[s,o]of Object.entries(n.board()))t[s]=o.map(a=>a.gameId);let r={};for(let s of n.listBoxes())r[s.gameId]=s.turn;return{t:"board",flow:e,hands:t,turns:r}}function wt(n){let e=n.url??Yt,t=n.WebSocket??globalThis.WebSocket,r=[],s=null,o=!1,a=n.build,i=null,l=null,d=new Set;if(!t)return{attach(){},detach(){},setBuild(){},close(){o=!0}};let g=()=>{if(!(!s||s.readyState!==1)){for(let w of r)try{s.send(w)}catch{}r=[]}},c=w=>{o||(r.push(JSON.stringify(w)),r.length>512&&r.splice(0,r.length-512),g())},f=()=>{try{return i?i.flows():[]}catch{return[]}},p=()=>{let w=f(),v={t:"hello",v:2,build:a,flows:w.map(O=>O.id)};n.project!==void 0&&(v.project=n.project);let k=w[0];if(k)try{v.boxes=k.listBoxes().map(O=>O.gameId)}catch{}d=new Set(w.map(O=>O.id));try{s?.send(JSON.stringify(v))}catch{}},u=w=>{try{c(Ie(w))}catch{}},h=()=>{let w=f(),v=new Set(w.map(k=>k.id));for(let k of w)d.has(k.id)||(d.add(k.id),c({t:"flowOpen",flow:k.id}),u(k));for(let k of[...d])v.has(k)||(d.delete(k),c({t:"flowClose",flow:k}))},y=(w,v)=>{try{if(h(),c({t:"trace",flow:w,event:v}),Xt.has(v.type)){let k=i?.getFlow(w);k&&u(k)}}catch{}};try{s=new t(e),s.addEventListener("open",()=>{p(),g()}),s.addEventListener("message",w=>{if(!(!n.onBundle||typeof w.data!="string"))try{let v=JSON.parse(w.data);v.t==="bundle"&&typeof v.build=="string"&&typeof v.data=="string"&&n.onBundle({build:v.build,data:v.data})}catch{}}),s.addEventListener("error",()=>{}),s.addEventListener("close",()=>{s=null,o=!0,r=[]})}catch{s=null,o=!0}let E=()=>{l?.(),l=null,i=null,d=new Set};return{attach(w){if(!o){E(),i=w;try{l=w.subscribeTrace(y)}catch{i=null;return}d=new Set(f().map(v=>v.id));for(let v of f())u(v)}},detach:E,setBuild(w){if(!(o||w===a)&&(a=w,s&&s.readyState===1)){p();for(let v of f())u(v)}},close(){o=!0,E(),r=[];try{s?.close()}catch{}s=null}}}function bt(n,e,t={}){let r;try{r=JSON.parse(e)}catch{return{ok:!1,error:"pushed bundle is not valid JSON"}}try{let s=new q(r,t);return s.loadGame(n.saveGame()),{ok:!0,engine:s,bundle:r}}catch(s){return{ok:!1,error:s instanceof Error?s.message:String(s)}}}function vt(n){let e=new _(n.world.properties,{normalise:t=>t});return{resolver:{get:t=>e.get(t),set:(t,r)=>{e.set(t,r,{host:!0})}},bag:e,values:()=>e.values,load:t=>e.load(t)}}return Bt(Zt);})();
30
+ `;function Z(){if(document.getElementById(ft))return;let n=document.createElement("style");n.id=ft,n.textContent=_t,document.head.append(n)}var Gt=(n,e)=>JSON.stringify(n)===JSON.stringify(e),qt=["deal","play","write","evict","turns","diagnostic"],zt={deal:"Deal",play:"Play",write:"Write",evict:"Evict",turns:"Turns",diagnostic:"Diag"},Ut=n=>n.type==="peek"?"deal":n.type,ht=n=>n===void 0?"<unset>":JSON.stringify(n);function Ce(n){return Wt(n,"flow"in n&&n.flow?`${n.flow} `:"")}function Wt(n,e){let t=(n.turn!==void 0?`[${n.turn}] `:"[-] ")+e;switch(n.type){case"deal":{let r=n.cards.filter(s=>s.verdict==="dealt").map(s=>s.id);return`${t}deal ${n.hand}: ${r.length>0?r.join(", "):"(none)"} (${n.cards.length} considered)`}case"peek":{let r=Object.entries(n.criteria).map(([o,a])=>`${o}=${a}`).join(", "),s=n.cards.filter(o=>o.verdict==="dealt").map(o=>o.id);return`${t}peek ${n.box}${r?` [${r}]`:""}: ${s.length>0?s.join(", "):"(none)"} (${n.cards.length} considered)`}case"evict":return`${t}evict ${n.card} from ${n.hand} (${n.reason})`;case"play":return`${t}play ${n.card}${n.outcome===""?"":` -> ${n.outcome}`}`;case"write":return`${t}write ${n.path}: ${ht(n.prev)} -> ${ht(n.value)}`;case"turns":return`${t}turns ${n.box} -> ${n.turn}`;default:return`${t}diagnostic ${n.where}: ${n.message}`}}var Kt=n=>{let e=n.split(".");return e.length===3?`${e[0]} ${e[1]}`:e[0]};function mt(n,e,t={}){Z();let r=e,s=()=>r,o=document.createElement("div");o.className="sl-insp";let a=document.createElement("div");a.className="sl-head";let i=document.createElement("h3");i.textContent=t.title??"Runtime state",a.append(i);let d=document.createElement("button");d.type="button",d.className="sl-save",d.textContent="Save state",d.addEventListener("click",()=>{let S=new Blob([ae(n)],{type:"application/json"}),P=URL.createObjectURL(S),E=document.createElement("a");E.href=P,E.download="save.storyletsave",E.click(),URL.revokeObjectURL(P)}),a.append(d);let l=document.createElement("input");l.type="file",l.accept=".storyletsave,application/json",l.hidden=!0,l.addEventListener("change",()=>{let S=l.files?.[0];if(!S)return;let P=new FileReader;P.onload=()=>{try{ie(n,String(P.result)),r=n.getFlow(e.id)??n.openFlow(e.id),de()}catch(E){console.error("storylets inspector: load failed:",E instanceof Error?E.message:E)}l.value=""},P.readAsText(S)});let p=document.createElement("button");p.type="button",p.className="sl-load",p.textContent="Load state",p.addEventListener("click",()=>l.click()),a.append(p,l),o.append(a);let c=document.createElement("input");c.type="text",c.className="sl-filter",c.placeholder="Filter properties",o.append(c);let f=[],u=[],g="";for(let S of s().listProperties()){let P=Kt(S.path);if(P!==g){let C=document.createElement("div");C.className="sl-group",C.textContent=P,o.append(C),u.push({el:C,rows:[]}),g=P}let E=Jt(s,S,f);u[u.length-1].rows.push({el:E,text:`${S.name} ${S.path}`.toLowerCase()}),o.append(E)}c.addEventListener("input",()=>{let S=c.value.trim().toLowerCase();for(let P of u){let E=!1;for(let C of P.rows){let $=S===""||C.text.includes(S);C.el.style.display=$?"":"none",E=E||$}P.el.style.display=E?"":"none"}});let m=document.createElement("div");m.className="sl-section",m.textContent="Turns (per box)";let w=document.createElement("div");w.className="sl-turns";let v=document.createElement("div");v.className="sl-section",v.textContent="Board";let y=document.createElement("div");y.className="sl-board",o.append(m,w,v,y);let x=(S,P,E)=>{let C=document.createElement("label");C.className=E;let $=document.createElement("input");return $.type="checkbox",$.checked=!0,$.addEventListener("change",()=>P($.checked)),C.append($,S),C},k=(S,P,E,C,$)=>{let ce=document.createElement("div");ce.className=`sl-section sl-${S}`,ce.textContent=P;let z=document.createElement("div");z.className=`sl-logbar sl-${S}`;let U=document.createElement("div");U.className=`sl-log sl-${S}`;let pe=new Map,Te=()=>E().filter(I=>pe.get(Ut(I))!==!1).map(Ce),De=!0,He="",ue=(I=!1)=>{let F=E(),Ae=`${F.length}:${F.length>0?F[F.length-1].seq:-1}`;if(!I&&Ae===He)return;He=Ae;let Ne=Te();U.textContent=Ne.length>0?Ne.join(`
31
+ `):$,De&&(U.scrollTop=U.scrollHeight)};for(let I of qt)pe.set(I,!0),z.append(x(zt[I],F=>{pe.set(I,F),ue(!0)},"sl-logkind"));z.append(x("Autoscroll",I=>{De=I},"sl-logscroll"));let N=document.createElement("button");N.type="button",N.className="sl-logcopy",N.textContent="Copy",N.title="Copy the visible (filtered) log to the clipboard",N.addEventListener("click",()=>{navigator.clipboard?.writeText(Te().join(`
32
+ `))});let V=document.createElement("button");return V.type="button",V.className="sl-logclear",V.textContent="Clear",V.title="Drop the retained log entries (cosmetic - no game state changes)",V.addEventListener("click",()=>{C(),ue(!0)}),z.append(N,V),o.append(ce,z,U),{render:ue}},O=k("flow","Log",()=>s().log(),()=>s().clearLog(),"(empty - new Engine(bundle, { log: true }) retains the flow log)"),xt=k("run","Run log (every flow)",()=>n.log(),()=>n.clearLog(),"(empty - new Engine(bundle, { log: true }) retains the run log)"),Re=(S=!1)=>{xt.render(S),O.render(S)},Le=(S,P)=>{let E=document.createElement("div");E.className="sl-line",E.textContent=P,S.append(E)},Me=()=>{w.textContent="";for(let S of s().listBoxes())Le(w,`${S.title??S.gameId}: turn ${S.turn}`);y.textContent="";for(let[S,P]of Object.entries(s().board())){let E=P.map(C=>C.title??C.gameId);Le(y,`${S}: ${E.length>0?E.join(", "):"(empty)"}`)}},de=()=>{for(let S of f)S.read();Me(),Re()};Me(),Re(!0);let le,Oe=t.pollMs??250;return Oe>0&&(le=setInterval(de,Oe)),(t.container??document.body).append(o),{el:o,refresh:de,destroy(){le!==void 0&&clearInterval(le),o.remove()}}}function Jt(n,e,t){let r=document.createElement("div");r.className="sl-row";let s=document.createElement("span");s.className="sl-name",s.textContent=e.name,s.title=e.path;let o=()=>{try{return n().getProperty(e.path)}catch{return}},a=()=>{},i=u=>{n().setProperty(e.path,u),a()},d=document.createElement("button");d.type="button",d.className="sl-reset",d.textContent="\u21BA",d.title="Reset to default",d.addEventListener("click",()=>i(e.default));let l,p,c=u=>document.activeElement===u;switch(e.type){case"boolean":{let u=document.createElement("input");u.type="checkbox",u.addEventListener("change",()=>i(u.checked)),l=u,p=()=>{c(u)||(u.checked=o()===!0)};break}case"number":{let u=document.createElement("input");u.type="number",u.addEventListener("change",()=>i(Number(u.value))),l=u,p=()=>{c(u)||(u.value=String(o()??0))};break}case"enum":case"quality":{let u=document.createElement("select");for(let g of(e.type==="quality"?e.stages:e.values)??[]){let m=document.createElement("option");m.value=g,m.textContent=g,u.append(m)}u.addEventListener("change",()=>i(u.value)),l=u,p=()=>{c(u)||(u.value=String(o()??""))};break}case"flags":{let u=document.createElement("input");u.type="text",u.placeholder="comma, separated, flags",u.addEventListener("change",()=>i(u.value.split(",").map(g=>g.trim()).filter(g=>g.length>0))),l=u,p=()=>{c(u)||(u.value=(o()??[]).join(", "))};break}default:{let u=document.createElement("input");u.type="text",u.addEventListener("change",()=>i(u.value)),l=u,p=()=>{c(u)||(u.value=String(o()??""))}}}let f=()=>{p(),d.disabled=Gt(o(),e.default)};return a=f,f(),t.push({row:e,read:f}),r.append(s,l,d),r}var Qt=n=>n===void 0?"<unset>":JSON.stringify(n);function Be(n){let e=n.values!==void 0&&n.values.length>0?` [${n.values.join(", ")}]`:"",t=n.durable===!0?" (durable)":"";return`${n.name}: ${n.type} = ${Qt(n.default)}${e}${t}`}function $e(n){if(n.scope==="world"||n.scope==="story")return n.scope;let e=n.group!==void 0?` (${n.group})`:"";return`${n.scope} ${n.owner}${e}`}var B=(n,e,t="sl-line")=>{let r=document.createElement("div");return r.className=t,r.textContent=e,n.append(r),r},ee=(n,e,t)=>{let r=document.createElement("details");r.className="sl-fold",r.open=t;let s=document.createElement("summary");s.textContent=e,r.append(s);let o=document.createElement("div");return r.append(o),n.append(r),o};function yt(n,e={}){Z();let t=oe(n),r=e.open??!0,s=document.createElement("div");s.className="sl-insp";let o=document.createElement("div");o.className="sl-head";let a=document.createElement("h3");a.textContent=e.title??"Bundle",o.append(a),s.append(o);let{identity:i,totals:d}=t,l=document.createElement("div");l.className="sl-ident",s.append(l),B(l,`${i.project} ${i.version}`,"sl-line"),B(l,`schema ${i.schema}`,"sl-line sl-note"),B(l,`hash ${i.hash===""?"(none)":i.hash} - metadata ${i.metadata}`,"sl-line sl-note");let p=ee(s,"Hands (deal)",r);p.className="sl-hands",t.hands.length===0&&B(p,"(no hands - this bundle is peek-only)","sl-line sl-note");for(let g of t.hands){let m=g.template!==void 0?`, template ${g.template}`:"",w=g.movable===void 0?"":`, moves ${g.movable.map(v=>`${v.group} from ${v.from}`).join(" and ")}`;B(p,`${g.gameId}: box ${g.box}, slots ${g.slots}${m}${w}`+(g.title!==void 0?` - ${g.title}`:""))}let c=ee(s,"Tags by box (peek criteria)",r);c.className="sl-tags";for(let g of t.boxes){B(c,`${g.title??g.gameId}`,"sl-group"),g.tagGroups.length===0&&B(c," (no tag groups)","sl-line sl-note");for(let m of g.tagGroups)B(c,` ${m.gameId}: ${m.tags.length>0?m.tags.join(", "):"(no tags)"}`)}let f=ee(s,"Properties (declared)",r);f.className="sl-props";for(let g of t.properties){B(f,$e(g),"sl-group"),g.properties.length===0&&B(f," (none declared)","sl-line sl-note");for(let m of g.properties)B(f,` ${Be(m)}`)}if(t.maps.length>0){let g=ee(s,"Maps (carried, not read)",r);g.className="sl-maps",B(g,"Geometry the build was asked to carry. The engine ignores it.","sl-line sl-note");for(let m of t.maps)B(g,`${m.box} - ${m.group}: zones ${m.zones}, pictures ${m.backgrounds}, sites ${m.sites}`)}let u=ee(s,"Counts",r);u.className="sl-counts",B(u,`boxes ${d.boxes} - decks ${d.decks} - cards ${d.cards}`),B(u,`hands ${d.hands} - templates ${d.templates} - tag groups ${d.tagGroups}`);for(let g of t.boxes)B(u,`${g.gameId}: decks ${g.counts.decks}, cards ${g.counts.cards}, hands ${g.counts.hands}, templates ${g.counts.templates}, tag groups ${g.counts.tagGroups}, ranking.specificity ${g.ranking.specificity}`+(g.turn!==void 0?`, turn = ${g.turn.seconds}s`:"")+(g.durableCards!==void 0?`, durable cards ${g.durableCards}`:""));return(e.container??document.body).append(s),{el:s,description:t,destroy(){s.remove()}}}var Yt="ws://127.0.0.1:4472",Xt=new Set(["deal","play","evict","turns"]);function Ie(n){let e=n.id,t={};for(let[s,o]of Object.entries(n.board()))t[s]=o.map(a=>a.gameId);let r={};for(let s of n.listBoxes())r[s.gameId]=s.turn;return{t:"board",flow:e,hands:t,turns:r}}function wt(n){let e=n.url??Yt,t=n.WebSocket??globalThis.WebSocket,r=[],s=null,o=!1,a=n.build,i=null,d=null,l=new Set;if(!t)return{attach(){},detach(){},setBuild(){},close(){o=!0}};let p=()=>{if(!(!s||s.readyState!==1)){for(let y of r)try{s.send(y)}catch{}r=[]}},c=y=>{o||(r.push(JSON.stringify(y)),r.length>512&&r.splice(0,r.length-512),p())},f=()=>{try{return i?i.flows():[]}catch{return[]}},u=()=>{let y=f(),x={t:"hello",v:2,build:a,flows:y.map(O=>O.id)};n.project!==void 0&&(x.project=n.project);let k=y[0];if(k)try{x.boxes=k.listBoxes().map(O=>O.gameId)}catch{}l=new Set(y.map(O=>O.id));try{s?.send(JSON.stringify(x))}catch{}},g=y=>{try{c(Ie(y))}catch{}},m=()=>{let y=f(),x=new Set(y.map(k=>k.id));for(let k of y)l.has(k.id)||(l.add(k.id),c({t:"flowOpen",flow:k.id}),g(k));for(let k of[...l])x.has(k)||(l.delete(k),c({t:"flowClose",flow:k}))},w=(y,x)=>{try{if(m(),c({t:"trace",flow:y,event:x}),Xt.has(x.type)){let k=i?.getFlow(y);k&&g(k)}}catch{}};try{s=new t(e),s.addEventListener("open",()=>{u(),p()}),s.addEventListener("message",y=>{if(!(!n.onBundle||typeof y.data!="string"))try{let x=JSON.parse(y.data);x.t==="bundle"&&typeof x.build=="string"&&typeof x.data=="string"&&n.onBundle({build:x.build,data:x.data})}catch{}}),s.addEventListener("error",()=>{}),s.addEventListener("close",()=>{s=null,o=!0,r=[]})}catch{s=null,o=!0}let v=()=>{d?.(),d=null,i=null,l=new Set};return{attach(y){if(!o){v(),i=y;try{d=y.subscribeTrace(w)}catch{i=null;return}l=new Set(f().map(x=>x.id));for(let x of f())g(x)}},detach:v,setBuild(y){if(!(o||y===a)&&(a=y,s&&s.readyState===1)){u();for(let x of f())g(x)}},close(){o=!0,v(),r=[];try{s?.close()}catch{}s=null}}}function bt(n,e,t={}){let r;try{r=JSON.parse(e)}catch{return{ok:!1,error:"pushed bundle is not valid JSON"}}try{let s=new q(r,t);return s.loadGame(n.saveGame()),{ok:!0,engine:s,bundle:r}}catch(s){return{ok:!1,error:s instanceof Error?s.message:String(s)}}}function vt(n){let e=new _(n.world.properties,{normalise:t=>t});return{resolver:{get:t=>e.get(t),set:(t,r)=>{e.set(t,r,{host:!0})}},bag:e,values:()=>e.values,load:t=>e.load(t)}}return Bt(Zt);})();
33
33
  //# sourceMappingURL=storyletengine.min.js.map