@storylet-studio/runtime 0.2.0 → 0.3.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,2 +1,2 @@
1
- "use strict";var StoryletEngine=(()=>{var H=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var oe=Object.prototype.hasOwnProperty;var ie=(o,e)=>{for(var t in e)H(o,t,{get:e[t],enumerable:!0})},ae=(o,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of se(e))!oe.call(o,s)&&s!==t&&H(o,s,{get:()=>e[s],enumerable:!(r=ne(e,s))||r.enumerable});return o};var de=o=>ae(H({},"__esModule",{value:!0}),o);var be={};ie(be,{Engine:()=>A,Flow:()=>T,describeBundle:()=>te,makePrng:()=>k,shuffleInPlace:()=>P});function E(o){switch(o[0]){case"b":return{kind:"bool",value:o[1]};case"n":return{kind:"number",value:o[1]};case"s":return{kind:"string",value:o[1]};case"sv":return{kind:"scopedvar",scope:o[1],name:o[2]};case"u":return{kind:"unary",op:o[1],operand:E(o[2])};case"bin":return{kind:"binary",op:o[1],left:E(o[2]),right:E(o[3])};case"call":{let e=o.slice(2).map(E);return{kind:"call",name:o[1],args:e}}case"fd":return{kind:"flagdelta",sign:o[1],name:o[2]}}}var f=class extends Error{constructor(e){super(e),this.name="EvalError"}};function $(o,e,t){let r=new Map(t.scopes.map(n=>[n.token,n.missing??"false"])),s=n=>{switch(n.kind){case"bool":return n.value;case"number":return n.value;case"string":return n.value;case"scopedvar":{let i=e.scopes[n.scope];if(i===void 0)return!1;let a=typeof i.get=="function"?i.get(n.name):i[n.name];if(a===void 0){if(r.get(n.scope)==="throw")throw new f(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);return!1}return a}case"call":{if(n.name==="advance"&&!t.functions[n.name]){let a=n.args[0];if(n.args.length!==1||a===void 0)throw new f(`advance() takes exactly 1 argument, got ${n.args.length}`);let l=N(a,e);if(l===void 0)throw new f("advance() needs a quality reference (@scope.name of a quality property)");let d=b(s(a),l,"advance");return l[Math.min(d+1,l.length-1)]}let i=t.functions[n.name];if(!i)throw new f(`unknown function '${n.name}'`);return i.eval(n.args,{evaluate:s,ctx:e})}case"flagdelta":throw new f("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(n.op==="not"){let a=s(n.operand);if(typeof a!="boolean")throw new f(`'not' requires a boolean operand, got ${typeof a}`);return!a}let i=s(n.operand);if(typeof i!="number")throw new f(`unary '-' requires a numeric operand, got ${typeof i}`);return-i}case"binary":{if(n.op==="and"){let c=s(n.left);if(typeof c!="boolean")throw new f(`'and' requires boolean operands, left is ${typeof c}`);if(!c)return!1;let h=s(n.right);if(typeof h!="boolean")throw new f(`'and' requires boolean operands, right is ${typeof h}`);return h}if(n.op==="or"){let c=s(n.left);if(typeof c!="boolean")throw new f(`'or' requires boolean operands, left is ${typeof c}`);if(c)return!0;let h=s(n.right);if(typeof h!="boolean")throw new f(`'or' requires boolean operands, right is ${typeof h}`);return h}let i=s(n.left),a=s(n.right),l=N(n.left,e),d=N(n.right,e),p=l??d;if(p!==void 0){if(l&&d&&!le(l,d)&&(n.op===">"||n.op===">="||n.op==="<"||n.op==="<="))throw new f(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);switch(n.op){case">":return b(i,p,">")>b(a,p,">");case">=":return b(i,p,">=")>=b(a,p,">=");case"<":return b(i,p,"<")<b(a,p,"<");case"<=":return b(i,p,"<=")<=b(a,p,"<=");case"+":case"-":case"*":case"/":throw new f(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(n.op){case"==":return j(i,a);case"!=":return!j(i,a);case">":return S(i,a,">"),i>a;case">=":return S(i,a,">="),i>=a;case"<":return S(i,a,"<"),i<a;case"<=":return S(i,a,"<="),i<=a;case"+":if(typeof i=="number"&&typeof a=="number"||typeof i=="string"&&typeof a=="string")return i+a;throw new f(`'+' requires two numbers or two strings, got ${typeof i} and ${typeof a}`);case"-":return S(i,a,"-"),i-a;case"*":return S(i,a,"*"),i*a;case"/":if(S(i,a,"/"),a===0)throw new f("division by zero");return i/a}}}};return s(o)}function j(o,e){if(Array.isArray(o)||Array.isArray(e)){if(!Array.isArray(o)||!Array.isArray(e)||o.length!==e.length)return!1;let t=[...o].sort(),r=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==r[s])return!1;return!0}return o===e}function S(o,e,t){if(typeof o!="number"||typeof e!="number")throw new f(`'${t}' requires numeric operands, got ${typeof o} and ${typeof e}`)}function N(o,e){if(!(o.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(o.scope,o.name)}function b(o,e,t){if(typeof o!="string")throw new f(`'${t}' on a quality compares stages, got ${typeof o}`);let r=e.indexOf(o);if(r<0)throw new f(`"${o}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var le=(o,e)=>o.length===e.length&&o.every((t,r)=>t===e[r]);function q(o){if(Number.isNaN(o)||!Number.isFinite(o))return 0;let e=Math.trunc(o)%4294967296;return e<0?e+4294967296:e}function k(o){let e=q(o);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 P(o,e){for(let t=o.length-1;t>0;t--){let r=Math.floor(e.next()*(t+1));[o[t],o[r]]=[o[r],o[t]]}}var ce={name:"check_flags",count:o=>Math.max(1,o.args.length-1)},pe=[ce];function U(o,e,t){let r=t?.countingCalls??pe;return D(o,t?.want??!0,e,r)}function D(o,e,t,r){if(o.kind==="binary"&&(o.op==="and"||o.op==="or")){let s=D(o.left,e,t,r),n=D(o.right,e,t,r);return o.op==="and"===e?s>0&&n>0?s+n:0:Math.max(s,n)}if(o.kind==="unary"&&o.op==="not")return D(o.operand,!e,t,r);if(o.kind==="call"){let s=r.find(n=>n.name===o.name);if(s){let n=s.count(o),i=t(o);return e?i?n:0:i?0:1}}return t(o)===e?1:0}var O=9999,R=o=>o.ctx.host??{},B=(o,e,t,r)=>{let s=t.evaluate(e[r]);if(typeof s!="string"||s==="")throw new f(`${o}() argument ${r+1} must be a non-empty string`);return s},z=(o,e,t)=>{if(e.length===0)throw new f(`${o}() 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 f(`${o}() first argument must be a flags property`)},_={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(o,e){if(o.length!==2)throw new f("random(a, b) requires exactly 2 arguments");let t=R(e).nextRandom;if(!t)throw new f("random() called without a PRNG in context");let r=e.evaluate(o[0]),s=e.evaluate(o[1]);if(typeof r!="number"||typeof s!="number")throw new f("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new f("random(a, b) arguments must be integers");let n=Math.min(r,s),i=Math.max(r,s);return Math.floor(t()*(i-n+1))+n}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,eval(o,e){let t=z("check_flags",o,e);for(let r=1;r<o.length;r++){let s=o[r];if(s.kind!=="flagdelta")throw new f("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(o,e){let t=[...z("set_flags",o,e)];for(let r=1;r<o.length;r++){let s=o[r];if(s.kind!=="flagdelta")throw new f("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let n=t.indexOf(s.name);n>=0&&t.splice(n,1)}}return t.sort()}},count_played:{minArgs:1,maxArgs:1,returnType:"number",eval(o,e){let t=B("count_played",o,e,0),r=R(e).countPlayed;if(!r)throw new f("count_played() called without a play log in context");return r(t)}},turns_since_played:{minArgs:1,maxArgs:1,returnType:"number",eval(o,e){let t=B("turns_since_played",o,e,0),r=R(e).turnsSincePlayed;if(!r)throw new f("turns_since_played() called without a play log in context");return r(t)}},count_played_in:{minArgs:2,maxArgs:2,returnType:"number",eval(o,e){let t=B("count_played_in",o,e,0),r=B("count_played_in",o,e,1),s=R(e).countPlayedIn;if(!s)throw new f("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(o,e){let t=B("turns_since_played_in",o,e,0),r=B("turns_since_played_in",o,e,1),s=R(e).turnsSincePlayedIn;if(!s)throw new f("turns_since_played_in() called without a play log in context");return s(t,r)}}}};function ue(o){return o.toLowerCase().replace(/['’]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function u(o){let e=o.gameId?.trim();return e||(o.title?ue(o.title):"")||o.id}var x="place";var V=class o{values={};decls=new Map;subscribers=new Set;auditors=new Set;norm;constructor(e=[],t){this.norm=t?.normalise??(r=>r.toLowerCase()),this.seed(e)}seed(e){for(let t of e){let r=this.norm(t.name);this.decls.set(r,t),this.values[r]=structuredClone(t.default??K(t))}}get(e){return this.values[this.norm(e)]}set(e,t,r){let s=this.norm(e);if(this.decls.get(s)?.writable===!1)throw new Error(`'${e}' is read-only`);let n={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let i of this.auditors)i(n);if(!n.silent)for(let i of this.subscribers)i(n);return n}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>ge(t,this.get(e),void 0,e))}declarations(){return[...this.decls.values()]}clone(){let e=new o([],{normalise:this.norm});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function ge(o,e,t,r){return{name:r??o.name.toLowerCase(),type:o.type,value:e,default:o.default??K(o),...o.values!==void 0?{values:o.values}:{},writable:t??o.writable??!0}}function K(o){if(o.default!==void 0)return o.default;switch(o.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return o.values?.[0]??"";case"flags":return[];case"quality":return o.stages?.[0]??""}}var W=(o,e)=>`${o}${e}`,X=(o,e)=>o.shared??e,fe=o=>o.sharedCopies??o.copies??1,I=o=>new V(o,{normalise:e=>e});function Y(o){return typeof o=="boolean"?o:typeof o=="number"?o!==0:typeof o=="string"?o!=="":o.length>0}var he={story:!0,box:!1,deck:!1,hand:!1,value:!1},Q=(o,e)=>e.shared??he[o],ye=(o,e)=>e.filter(t=>Q(o,t)),M=(o,e)=>e.filter(t=>!Q(o,t)),G=(o,e)=>e.template!==void 0?o.templatesById.get(e.template)?.properties??o.bundle.boxes.flatMap(t=>t.handTemplates).find(t=>t.id===e.template)?.properties??[]:e.properties??[],J=(o,e)=>{let t=o.bundle;return{story:I(e("story",t.story.properties)),box:new Map(t.boxes.map(r=>[r.id,I(e("box",r.properties))])),deck:new Map(t.boxes.flatMap(r=>r.decks.map(s=>[s.id,I(e("deck",s.properties))]))),hand:new Map(t.boxes.flatMap(r=>r.hands.map(s=>[s.id,I(e("hand",G(o,s)))]))),value:new Map(t.boxes.flatMap(r=>r.tagGroups.flatMap(s=>s.tags.map(n=>[n.id,I(e("value",n.properties??[]))]))))}},Z=o=>({story:o.story.values,box:Object.fromEntries([...o.box].map(([e,t])=>[e,t.values])),deck:Object.fromEntries([...o.deck].map(([e,t])=>[e,t.values])),hand:Object.fromEntries([...o.hand].map(([e,t])=>[e,t.values])),value:Object.fromEntries([...o.value].map(([e,t])=>[e,t.values]))}),ee=(o,e)=>{o.story.load(e?.story??{});for(let t of["box","deck","hand","value"])for(let[r,s]of Object.entries(e?.[t]??{}))o[t].get(r)?.load(s)},A=class{internals;seed;flowsById=new Map;engineTraceHandlers=new Set;hostWorld;constructor(e,t={}){this.seed=t.seed??0,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,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},shared:void 0,worldResolver:void 0,emitEngine:(s,n,i)=>{this.internals.logCap!==void 0&&(this.engineLog.push({...n,flow:s,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 a of this.engineTraceHandlers)a(s,n)},engineTracing:()=>this.engineTraceHandlers.size>0};this.internals=r;for(let s of e.boxes){r.boxesById.set(s.id,s),r.boxesByGameId.set(u(s),s);for(let n of s.tagGroups)r.groupsById.set(n.id,{group:n,box:s}),n.required===!0&&r.requiredGroups.add(n.id);for(let n of s.decks){n.shared===!0&&(r.hasShared=!0);for(let i of n.cards){let a={card:i,deck:n,box:s};r.cardsById.set(i.id,a),r.cardsByGameId.set(u(i),a),i.shared===!0&&(r.hasShared=!0)}}for(let n of s.handTemplates)r.templatesById.set(n.id,n);for(let n of s.hands)r.handsById.set(n.id,{hand:n,box:s}),r.handsByGameId.set(u(n),{hand:n,box:s})}this.initLadders(),r.flowDecls={story:M("story",e.story.properties),box:new Map(e.boxes.map(s=>[s.id,M("box",s.properties)])),deck:new Map(e.boxes.flatMap(s=>s.decks.map(n=>[n.id,M("deck",n.properties)]))),hand:new Map(e.boxes.flatMap(s=>s.hands.map(n=>[n.id,M("hand",G(r,n))]))),value:new Map(e.boxes.flatMap(s=>s.tagGroups.flatMap(n=>n.tags.map(i=>[i.id,M("value",i.properties??[])]))))},this.initShared(this.hostWorld)}initShared(e){let t=this.internals;if(t.shared=J(t,ye),e!==void 0)t.worldResolver=e;else{let r=I(t.bundle.world.properties);t.worldResolver={get:s=>r.get(s),set:(s,n)=>{r.set(s,n)}}}}initLadders(){let e=this.internals,t=n=>{let i=new Map;for(let a of n??[])a.type==="quality"&&a.stages!==void 0&&i.set(a.name,a.stages);return i},r=e.bundle;e.ladders.world=t(r.world.properties),e.ladders.story=t(r.story.properties);for(let n of r.boxes){e.ladders.box.set(n.id,t(n.properties));for(let i of n.decks)e.ladders.deck.set(i.id,t(i.properties));for(let i of n.tagGroups)for(let a of i.tags)e.ladders.value.set(a.id,t(a.properties));for(let i of n.hands)e.ladders.hand.set(i.id,t(G(e,i)))}let s=n=>[...n.values()].some(i=>i.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={}){this.flowsById.get(e)?.markClosed();let r=new T(this,this.internals,e,t.seed??this.seed);return this.flowsById.set(e,r),r}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}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.worldResolver.set)throw new Error("@world is read-only here: the host bound no write");this.internals.worldResolver.set(r.name,t);return}r.bag.set(r.name,t,{silent:!0,reason:"host setProperty"})}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(n=>n.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],[,n,i]=t,a=this.internals.shared[s].get(n);if(a!==void 0&&a.get(i)!==void 0)return{kind:"bag",bag:a,name:i};throw this.internals.flowDecls[s].get(n)?.some(l=>l.name===i)&&r(),a===void 0&&!this.internals.flowDecls[s].has(n)?new Error(`no ${s} store "${n}"`):new Error(`no property at "${e}"`)}throw new Error(`bad property path "${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}:{}})}let t=(r,s)=>{for(let n of s.rows())e.push({path:`${r}.${n.name}`,...n})};t("story",this.internals.shared.story);for(let[r,s]of this.internals.shared.box)t(`box.${r}`,s);for(let[r,s]of this.internals.shared.deck)t(`deck.${r}`,s);for(let[r,s]of this.internals.shared.hand)t(`hand.${r}`,s);for(let[r,s]of this.internals.shared.value)t(`value.${r}`,s);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:`${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:Z(this.internals.shared),spent:[...this.spent].sort()},flows:Object.fromEntries([...this.flowsById].map(([e,t])=>[e,t.snapshot()]))})}loadGame(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}"`);let t=structuredClone(e);this.reset(),ee(this.internals.shared,t.shared.props);for(let r of t.shared.spent??[])this.spent.add(r);for(let[r,s]of Object.entries(t.flows??{}))this.openFlow(r).restore(s)}},T=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=k(s),this.stores=J(t,M);for(let i of t.bundle.boxes){this.turnCounts.set(i.id,0);for(let a of i.hands)this.boardContents.set(a.id,[])}let n=(i,a)=>({get:l=>i?.get(l)??a?.get(l)});this.storyReader=n(this.stores.story,t.shared.story);for(let i of t.bundle.boxes){this.boxReaders.set(i.id,n(this.stores.box.get(i.id),t.shared.box.get(i.id)));for(let a of i.decks)this.deckReaders.set(a.id,n(this.stores.deck.get(a.id),t.shared.deck.get(a.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=E(e.ast),this.internals.nodeCache.set(e,t)),t}groupInBox(e,t){return e.tagGroups.find(r=>u(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 n of s){let i=W(r,n);this.tagPlayCount.set(i,(this.tagPlayCount.get(i)??0)+1),this.lastPlayInTag.set(i,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,n)=>{let i=this.groupInBox(e,s),a=i?.tags.find(l=>l.gameId===n);return i&&a?W(i.id,a.id):void 0},r=s=>{let n=this.internals.cardsByGameId.get(s.card);return n?(this.turnCounts.get(n.box.id)??0)-s.turn:O};return{nextRandom:()=>this.prng.next(),countPlayed:s=>this.playCount.get(s)??0,turnsSincePlayed:s=>{let n=this.lastPlayOf.get(s);return n?r(n):O},countPlayedIn:(s,n)=>{let i=t(s,n);return i===void 0?0:this.tagPlayCount.get(i)??0},turnsSincePlayedIn:(s,n)=>{let i=t(s,n),a=i===void 0?void 0:this.lastPlayInTag.get(i);return a?r(a):O}}}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,n)=>s==="world"?this.internals.ladders.world.get(n):s==="story"?this.internals.ladders.story.get(n):s==="box"?this.internals.ladders.box.get(e.id)?.get(n):s==="deck"&&t?this.internals.ladders.deck.get(t.id)?.get(n):s==="hand"?this.handLadder(r,n):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 $(this.node(e),t,_)}passes(e,t,r){if(!e)return!0;try{return Y(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={},n;if(e.template!==void 0){let i=this.internals.templatesById.get(e.template);if(!i)throw new Error(`hand "${u(e)}": unknown template "${e.template}"`);for(let[a,l]of Object.entries(i.bindings??{}))r.set(a,l);for(let[a,l]of Object.entries(e.chosen??{})){r.set(a,l);let d=this.internals.groupsById.get(a),p=d?.group.tags.find(c=>c.id===l);d&&p&&(s[u(d.group)]=u(p))}n=i.condition}else{for(let[i,a]of Object.entries(e.rule?.bindings??{})){r.set(i,a);let l=this.internals.groupsById.get(i),d=l?.group.tags.find(p=>p.id===a);l&&d&&(s[u(l.group)]=u(d))}n=e.rule?.condition}return r.set(x,e.id),this.bindStateGroups(t,r,s),{box:t,hand:e,...n!==void 0?{condition:n}:{},boundTags:r,askNames:s}}askForPeek(e,t){let r=new Map,s={};for(let[n,i]of Object.entries(t)){if(n===x){let d=this.internals.handsByGameId.get(i)??this.internals.handsById.get(i);if(!d)throw new Error(`peek: unknown hand "${i}" in home criteria`);r.set(x,d.hand.id);continue}let a=this.groupInBox(e,n);if(!a)throw new Error(`peek: unknown tag group "${n}" in box "${u(e)}"`);let l=this.tagByGameId(a,i)??a.tags.find(d=>d.id===i);if(!l)throw new Error(`peek: unknown tag "${i}" in group "${u(a)}"`);r.set(a.id,l.id),s[u(a)]=u(l)}return this.bindStateGroups(e,r,s),{box:e,boundTags:r,askNames:s}}bindStateGroups(e,t,r){for(let s of e.tagGroups){if(s.boundBy===void 0||t.has(s.id))continue;let n=/^@(world|story)\.([a-z][a-z0-9_-]*)$/.exec(s.boundBy);if(!n){this.emit({type:"diagnostic",where:`tag group ${u(s)}`,message:`boundBy "${s.boundBy}" is not a @world or @story property reference`});continue}let i;try{i=this.getProperty(`${n[1]}.${n[2]}`)}catch{this.emit({type:"diagnostic",where:`tag group ${u(s)}`,message:`boundBy "${s.boundBy}" names a property that is not declared`});continue}let a=typeof i=="string"?i:String(i),l=s.tags.find(d=>u(d)===a);if(!l){this.emit({type:"diagnostic",where:`tag group ${u(s)}`,message:`${s.boundBy} is "${a}", which is not one of its tags`});continue}t.set(s.id,l.id),r[u(s)]=u(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,n]of e.boundTags)if(s!==x)for(let[i,a]of Object.entries(this.valuesOf("value",n)))t[i]=a,r.set(i,{kind:"value",id:n});if(e.hand)for(let[s,n]of Object.entries(this.valuesOf("hand",e.hand.id)))t[s]=n,r.set(s,{kind:"hand",id:e.hand.id});for(let[s,n]of Object.entries(e.askNames))t[s]=n,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 n=e.id;return(r.get(n)??0)>=this.copiesOf(e)?"claimed":t&&(s.get(n)??0)>=fe(e)?"claimed-elsewhere":null}tagsMatch(e,t){let r=e.tags?.[x];if(r!==void 0&&r.length>0){let s=t.get(x);if(s===void 0||!r.includes(s))return!1}for(let[s,n]of t){if(s===x)continue;let i=e.tags?.[s];if(i===void 0){if(this.internals.requiredGroups.has(s))return!1;continue}if(!i.includes(n))return!1}return!0}runAsk(e,t,r){let{box:s}=e,n=this.buildHandEnv(e),i=(c,h)=>{r?.push({id:c,verdict:h})};if(!this.passes(e.condition,this.evalCtx(s,void 0,n),`hand ${e.hand?u(e.hand):""} condition`))return{ordered:[],handEnv:n};let a=new Map;for(let c of s.decks)a.set(c.id,this.passes(c.condition,this.evalCtx(s,c,n),`deck ${c.gameId} gate`));let l=this.turnCounts.get(s.id)??0,d=[];for(let c of s.decks){let h=this.evalCtx(s,c,n),m=c.shared??!1;for(let g of c.cards){let y=X(g,m);if(!a.get(c.id)){i(g.id,"deck-gate");continue}if(y&&this.engine.isTaken(g.id)){i(g.id,"taken");continue}if((this.cooldowns[g.id]??0)>l){i(g.id,"cooldown");continue}if(!this.tagsMatch(g,n.boundTags)){i(g.id,"tags");continue}if(g.condition&&!this.passes(g.condition,h,this.tracing?`card ${g.gameId} condition`:void 0)){i(g.id,"condition");continue}let w=t(g,y);if(w){i(g.id,w);continue}let C;if(typeof g.priority=="number")C=g.priority;else try{let v=this.eval(g.priority,h);if(typeof v!="number"){i(g.id,"priority");continue}C=v}catch(v){this.tracing&&this.emit({type:"diagnostic",where:`card ${g.gameId} priority`,message:v instanceof Error?v.message:String(v)}),i(g.id,"priority");continue}let L=0;if(s.ranking.specificity&&g.condition){let v=this.node(g.condition);L=U(v,re=>{try{return Y($(re,h,_))}catch{return!1}})}d.push({entry:{card:g,deck:c,box:s},priority:C,spec:L})}}d.sort((c,h)=>h.priority-c.priority||h.spec-c.spec);let p=0;for(;p<d.length;){let c=p+1;for(;c<d.length&&d[c].priority===d[p].priority&&d[c].spec===d[p].spec;)c++;if(c-p>1){let h=d.slice(p,c);P(h,this.prng);for(let m=0;m<h.length;m++)d[p+m]=h[m]}p=c}for(let c of d)r?.push({id:c.entry.card.id,verdict:"dealt",priority:c.priority,specificity:c.spec});return{ordered:d.map(c=>c.entry),handEnv:n}}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:u(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 n=this.askForPeek(s,t),i=this.claims(),a=this.internals.hasShared?this.engine.sharedClaims():new Map,l=this.tracing?[]:void 0,{ordered:d}=this.runAsk(n,(c,h)=>this.claimVerdict(c,h,i,a),l),p=r===void 0?d:d.slice(0,Math.max(r,0));return l&&(this.capTrace(l,new Set(p.map(c=>c.card.id))),this.emit({type:"peek",box:u(s),criteria:t,cards:l},this.turnCounts.get(s.id)??0)),{box:u(s),cards:p.map(c=>this.view(c))}}deal(e){this.assertOpen();let{hand:t}=this.resolveHand(e);return this.dealMany([e])[u(t)]??[]}dealMany(e){this.assertOpen();let t=(e??[...this.internals.handsById.keys()].sort()).map(n=>this.resolveHand(n));P(t,this.prng);for(let{hand:n,box:i}of t){let a=this.askForHand(n,i),l=this.buildHandEnv(a),d=this.passes(a.condition,this.evalCtx(i,void 0,l)),p=new Map;for(let y of i.decks)p.set(y.id,this.passes(y.condition,this.evalCtx(i,y,l)));let c=this.turnCounts.get(i.id)??0,h=[],m=(y,w)=>(h.push({card:y,reason:w}),!1),g=(this.boardContents.get(n.id)??[]).filter(y=>{if(!d)return m(y,"hand-condition");let w=this.internals.cardsById.get(y);return w?p.get(w.deck.id)?(this.cooldowns[y]??0)>c?m(y,"cooldown"):this.tagsMatch(w.card,l.boundTags)?this.passes(w.card.condition,this.evalCtx(i,w.deck,l),`card ${w.card.gameId} condition`)?!0:m(y,"condition"):m(y,"tags"):m(y,"deck-gate"):m(y,"vanished")});if(this.boardContents.set(n.id,g),this.tracing)for(let y of h)this.emit({type:"evict",hand:n.id,card:y.card,reason:y.reason},c)}let r=this.claims(),s=this.internals.hasShared?this.engine.sharedClaims():new Map;for(let{hand:n,box:i}of t){let a=this.boardContents.get(n.id)??[],l=this.handCapacity(n)-a.length;if(l<=0)continue;let d=this.askForHand(n,i),p=new Set(a),c=this.tracing?[]:void 0,{ordered:h}=this.runAsk(d,(g,y)=>p.has(g.id)?"claimed":this.claimVerdict(g,y,r,s),c),m=h.slice(0,l).map(g=>g.card.id);this.boardContents.set(n.id,[...a,...m]);for(let g of m)r.set(g,(r.get(g)??0)+1),s.set(g,(s.get(g)??0)+1);c&&(this.capTrace(c,new Set(m)),this.emit({type:"deal",hand:u(n),cards:c},this.turnCounts.get(i.id)??0))}return Object.fromEntries(t.map(({hand:n})=>[u(n),(this.boardContents.get(n.id)??[]).map(i=>this.view(this.internals.cardsById.get(i)))]))}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])=>[u(this.internals.handsById.get(r).hand),s.map(n=>this.view(this.internals.cardsById.get(n)))]))}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:n}=this.resolveHand(t);if(!(this.boardContents.get(s.id)??[]).includes(r.card.id))throw new Error(`card "${u(r.card)}" is not dealt to hand "${u(s)}"`);return{entry:r,ask:this.askForHand(s,n)}}outcomes(e,t){this.assertOpen();let{entry:r,ask:s}=this.resolveDealt(e,t),n=this.evalCtx(r.box,r.deck,this.buildHandEnv(s));return r.card.outcomes.map(i=>({id:i.id,gameId:u(i),...i.title!==void 0?{title:i.title}:{},...i.purpose!==void 0?{purpose:i.purpose}:{},available:this.passes(i.condition,n)}))}play(e,t,r,s={}){this.assertOpen();let{entry:n,ask:i}=this.resolveDealt(e,r),a=n.card.outcomes.find(g=>u(g)===t);if(!a)throw new Error(`card "${u(n.card)}" has no outcome "${t}"`);let l=this.buildHandEnv(i),d=this.evalCtx(n.box,n.deck,l);if(!this.passes(a.condition,d))throw new Error(`outcome "${t}" on "${u(n.card)}" is gated shut`);let p=(this.turnCounts.get(n.box.id)??0)+(s.advanceTurns??this.internals.bundle.settings.playAdvancesTurns),c=[];for(let[g,y]of Object.entries(a.changes))c.push({target:g,value:this.eval(y,d)});for(let{target:g,value:y}of c){let{path:w,prev:C}=this.applyWrite(g,y,n,l);this.tracing&&this.emit({type:"write",target:g,path:w,value:y,...C!==void 0?{prev:C}:{}},p)}let h={card:u(n.card),outcome:u(a),turn:p};this.playLog.push(h),this.indexPlay(h),n.card.redraw==="never"?X(n.card,n.deck.shared??!1)?this.engine.markTaken(n.card.id):this.cooldowns[n.card.id]=Number.MAX_SAFE_INTEGER:typeof n.card.redraw=="number"&&(this.cooldowns[n.card.id]=p+n.card.redraw);let m=i.hand.id;this.boardContents.set(m,(this.boardContents.get(m)??[]).filter(g=>g!==n.card.id)),this.turnCounts.set(n.box.id,p),this.tracing&&this.emit({type:"play",card:n.card.id,outcome:u(a),turn:p},p)}landIn(e,t,r,s,n){let i=e==="story"?this.stores.story:t!==void 0?this.stores[e].get(t):void 0,a=e==="story"?this.internals.shared.story:t!==void 0?this.internals.shared[e].get(t):void 0,l=i!==void 0&&i.get(r)!==void 0?i:a!==void 0&&a.get(r)!==void 0?a:void 0;if(l===void 0)throw new Error(`no property at "${n}"`);let d=l.set(r,s);return{path:n,...d.prev!==void 0?{prev:d.prev}:{}}}applyWrite(e,t,r,s){let n=/^@([a-z]+)\.([A-Za-z_][A-Za-z0-9_-]*)$/.exec(e);if(!n)throw new Error(`bad change target "${e}"`);let[,i,a]=n;switch(i){case"world":{let l=this.internals.worldResolver;if(!l.set)throw new Error(`@world.${a} cannot be written: the host bound @world read-only`);let d=l.get(a);return l.set(a,t),{path:`world.${a}`,...d!==void 0?{prev:d}:{}}}case"story":return this.landIn("story",void 0,a,t,`story.${a}`);case"box":return this.landIn("box",r.box.id,a,t,`box.${r.box.id}.${a}`);case"deck":return this.landIn("deck",r.deck.id,a,t,`deck.${r.deck.id}.${a}`);case"hand":{let l=s.sources.get(a);if(!l)throw new Error(`@hand.${a} is not composed in this ask`);if(l.kind==="criteria")throw new Error(`@hand.${a} is a chosen tag / criteria name and cannot be written`);return this.landIn(l.kind,l.id,a,t,`${l.kind}.${l.id}.${a}`)}default:throw new Error(`bad change target scope "@${i}"`)}}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:u(r),turn:s},s)}listBoxes(){return this.assertOpen(),this.internals.bundle.boxes.map(e=>({id:e.id,gameId:u(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:`${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}:{}})}let t=(r,s,n)=>{for(let i of[s,n])if(i!==void 0)for(let a of i.rows())e.push({path:`${r}.${a.name}`,...a})};t("story",this.internals.shared.story,this.stores.story);for(let r of["box","deck","hand","value"]){let s=new Set([...this.internals.shared[r].keys(),...this.stores[r].keys()]);for(let n of s)t(`${r}.${n}`,this.internals.shared[r].get(n),this.stores[r].get(n))}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.worldResolver.set)throw new Error("@world is read-only here: the host bound no write");this.internals.worldResolver.set(r.name,t);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"})}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=this.stores[r].get(t[1]),n=this.internals.shared[r].get(t[1]);if(s===void 0&&n===void 0)throw new Error(`no ${t[0]} store "${t[1]}"`);return{kind:"bag",...s!==void 0?{own:s}:{},...n!==void 0?{shared:n}:{},name:t[2]}}throw new Error(`bad property path "${e}"`)}snapshot(){return{props:Z(this.stores),turns:Object.fromEntries(this.turnCounts),prng:this.prng.state(),cooldowns:this.cooldowns,board:Object.fromEntries(this.boardContents),playLog:this.playLog}}restore(e){ee(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=k(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 F=o=>o.map(e=>({name:e.name,type:e.type,default:e.default,...e.values!==void 0?{values:e.values}:{},...e.purpose!==void 0?{purpose:e.purpose}:{}})),me=(o,e)=>o.template!==void 0?e.handTemplates.find(t=>t.id===o.template)?.properties??[]:o.properties??[],we=(o,e)=>{if(o.slots!==void 0)return o.slots;let t=o.template!==void 0?e.handTemplates.find(r=>r.id===o.template)?.slots:o.rule?.slots;return t===void 0?"unbounded":t};function te(o){let e=[],t=[],r=[{scope:"world",owner:"",properties:F(o.world.properties)},{scope:"story",owner:"",properties:F(o.story.properties)}],s={boxes:0,decks:0,cards:0,hands:0,templates:0,tagGroups:0};for(let n of o.boxes){let i=u(n),a=n.decks.reduce((d,p)=>d+p.cards.length,0);e.push({gameId:i,...n.title!==void 0?{title:n.title}:{},ranking:{specificity:n.ranking.specificity},tagGroups:n.tagGroups.map(d=>({gameId:u(d),tags:d.tags.map(p=>u(p))})),counts:{decks:n.decks.length,cards:a,hands:n.hands.length,templates:n.handTemplates.length,tagGroups:n.tagGroups.length}}),s.boxes+=1,s.decks+=n.decks.length,s.cards+=a,s.hands+=n.hands.length,s.templates+=n.handTemplates.length,s.tagGroups+=n.tagGroups.length;for(let d of n.hands){let p=d.template!==void 0?n.handTemplates.find(c=>c.id===d.template):void 0;t.push({gameId:u(d),...d.title!==void 0?{title:d.title}:{},box:i,slots:we(d,n),...p!==void 0?{template:u(p)}:{}})}let l=(d,p,c,h)=>{c.length!==0&&r.push({scope:d,owner:p,box:i,...h!==void 0?{group:h}:{},properties:F(c)})};l("box",i,n.properties);for(let d of n.decks)l("deck",u(d),d.properties);for(let d of n.hands)l("hand",u(d),me(d,n));for(let d of n.tagGroups)for(let p of d.tags)l("tag",u(p),p.properties??[],u(d))}return{identity:{schema:o.schema,project:o.content.project,version:o.content.version,hash:o.content.hash,metadata:o.metadata},totals:s,boxes:e,hands:t,properties:r,maps:(o.maps??[]).map(n=>({box:n.box,group:n.group,zones:n.zones.length,backgrounds:n.backgrounds?.length??0}))}}return de(be);})();
1
+ "use strict";var StoryletEngine=(()=>{var V=Object.defineProperty;var ne=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var oe=Object.prototype.hasOwnProperty;var ie=(o,e)=>{for(var t in e)V(o,t,{get:e[t],enumerable:!0})},ae=(o,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of se(e))!oe.call(o,s)&&s!==t&&V(o,s,{get:()=>e[s],enumerable:!(r=ne(e,s))||r.enumerable});return o};var de=o=>ae(V({},"__esModule",{value:!0}),o);var be={};ie(be,{Engine:()=>H,Flow:()=>T,describeBundle:()=>te,makePrng:()=>k,shuffleInPlace:()=>P});function E(o){switch(o[0]){case"b":return{kind:"bool",value:o[1]};case"n":return{kind:"number",value:o[1]};case"s":return{kind:"string",value:o[1]};case"sv":return{kind:"scopedvar",scope:o[1],name:o[2]};case"u":return{kind:"unary",op:o[1],operand:E(o[2])};case"bin":return{kind:"binary",op:o[1],left:E(o[2]),right:E(o[3])};case"call":{let e=o.slice(2).map(E);return{kind:"call",name:o[1],args:e}}case"fd":return{kind:"flagdelta",sign:o[1],name:o[2]}}}var f=class extends Error{constructor(e){super(e),this.name="EvalError"}};function $(o,e,t){let r=new Map(t.scopes.map(n=>[n.token,n.missing??"false"])),s=n=>{switch(n.kind){case"bool":return n.value;case"number":return n.value;case"string":return n.value;case"scopedvar":{let i=e.scopes[n.scope];if(i===void 0)return!1;let a=typeof i.get=="function"?i.get(n.name):i[n.name];if(a===void 0){if(r.get(n.scope)==="throw")throw new f(`@${n.scope}.${n.name} is not declared on the current ${n.scope}.`);return!1}return a}case"call":{if(n.name==="advance"&&!t.functions[n.name]){let a=n.args[0];if(n.args.length!==1||a===void 0)throw new f(`advance() takes exactly 1 argument, got ${n.args.length}`);let l=N(a,e);if(l===void 0)throw new f("advance() needs a quality reference (@scope.name of a quality property)");let d=b(s(a),l,"advance");return l[Math.min(d+1,l.length-1)]}let i=t.functions[n.name];if(!i)throw new f(`unknown function '${n.name}'`);return i.eval(n.args,{evaluate:s,ctx:e})}case"flagdelta":throw new f("flagdelta node is only valid as an argument to a flag-delta function");case"unary":{if(n.op==="not"){let a=s(n.operand);if(typeof a!="boolean")throw new f(`'not' requires a boolean operand, got ${typeof a}`);return!a}let i=s(n.operand);if(typeof i!="number")throw new f(`unary '-' requires a numeric operand, got ${typeof i}`);return-i}case"binary":{if(n.op==="and"){let c=s(n.left);if(typeof c!="boolean")throw new f(`'and' requires boolean operands, left is ${typeof c}`);if(!c)return!1;let h=s(n.right);if(typeof h!="boolean")throw new f(`'and' requires boolean operands, right is ${typeof h}`);return h}if(n.op==="or"){let c=s(n.left);if(typeof c!="boolean")throw new f(`'or' requires boolean operands, left is ${typeof c}`);if(c)return!0;let h=s(n.right);if(typeof h!="boolean")throw new f(`'or' requires boolean operands, right is ${typeof h}`);return h}let i=s(n.left),a=s(n.right),l=N(n.left,e),d=N(n.right,e),p=l??d;if(p!==void 0){if(l&&d&&!le(l,d)&&(n.op===">"||n.op===">="||n.op==="<"||n.op==="<="))throw new f(`'${n.op}' compares two different qualities, whose stage orders are unrelated`);switch(n.op){case">":return b(i,p,">")>b(a,p,">");case">=":return b(i,p,">=")>=b(a,p,">=");case"<":return b(i,p,"<")<b(a,p,"<");case"<=":return b(i,p,"<=")<=b(a,p,"<=");case"+":case"-":case"*":case"/":throw new f(`'${n.op}' cannot be applied to a quality - a stage is a position, not a number; use advance() to move it`);default:break}}switch(n.op){case"==":return j(i,a);case"!=":return!j(i,a);case">":return S(i,a,">"),i>a;case">=":return S(i,a,">="),i>=a;case"<":return S(i,a,"<"),i<a;case"<=":return S(i,a,"<="),i<=a;case"+":if(typeof i=="number"&&typeof a=="number"||typeof i=="string"&&typeof a=="string")return i+a;throw new f(`'+' requires two numbers or two strings, got ${typeof i} and ${typeof a}`);case"-":return S(i,a,"-"),i-a;case"*":return S(i,a,"*"),i*a;case"/":if(S(i,a,"/"),a===0)throw new f("division by zero");return i/a}}}};return s(o)}function j(o,e){if(Array.isArray(o)||Array.isArray(e)){if(!Array.isArray(o)||!Array.isArray(e)||o.length!==e.length)return!1;let t=[...o].sort(),r=[...e].sort();for(let s=0;s<t.length;s++)if(t[s]!==r[s])return!1;return!0}return o===e}function S(o,e,t){if(typeof o!="number"||typeof e!="number")throw new f(`'${t}' requires numeric operands, got ${typeof o} and ${typeof e}`)}function N(o,e){if(!(o.kind!=="scopedvar"||e.qualities===void 0))return e.qualities(o.scope,o.name)}function b(o,e,t){if(typeof o!="string")throw new f(`'${t}' on a quality compares stages, got ${typeof o}`);let r=e.indexOf(o);if(r<0)throw new f(`"${o}" is not a stage of this quality (stages: ${e.join(", ")})`);return r}var le=(o,e)=>o.length===e.length&&o.every((t,r)=>t===e[r]);function q(o){if(Number.isNaN(o)||!Number.isFinite(o))return 0;let e=Math.trunc(o)%4294967296;return e<0?e+4294967296:e}function k(o){let e=q(o);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 P(o,e){for(let t=o.length-1;t>0;t--){let r=Math.floor(e.next()*(t+1));[o[t],o[r]]=[o[r],o[t]]}}var ce={name:"check_flags",count:o=>Math.max(1,o.args.length-1)},pe=[ce];function U(o,e,t){let r=t?.countingCalls??pe;return D(o,t?.want??!0,e,r)}function D(o,e,t,r){if(o.kind==="binary"&&(o.op==="and"||o.op==="or")){let s=D(o.left,e,t,r),n=D(o.right,e,t,r);return o.op==="and"===e?s>0&&n>0?s+n:0:Math.max(s,n)}if(o.kind==="unary"&&o.op==="not")return D(o.operand,!e,t,r);if(o.kind==="call"){let s=r.find(n=>n.name===o.name);if(s){let n=s.count(o),i=t(o);return e?i?n:0:i?0:1}}return t(o)===e?1:0}var O=9999,R=o=>o.ctx.host??{},B=(o,e,t,r)=>{let s=t.evaluate(e[r]);if(typeof s!="string"||s==="")throw new f(`${o}() argument ${r+1} must be a non-empty string`);return s},z=(o,e,t)=>{if(e.length===0)throw new f(`${o}() 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 f(`${o}() first argument must be a flags property`)},_={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(o,e){if(o.length!==2)throw new f("random(a, b) requires exactly 2 arguments");let t=R(e).nextRandom;if(!t)throw new f("random() called without a PRNG in context");let r=e.evaluate(o[0]),s=e.evaluate(o[1]);if(typeof r!="number"||typeof s!="number")throw new f("random(a, b) arguments must be numbers");if(!Number.isInteger(r)||!Number.isInteger(s))throw new f("random(a, b) arguments must be integers");let n=Math.min(r,s),i=Math.max(r,s);return Math.floor(t()*(i-n+1))+n}},check_flags:{minArgs:1,returnType:"boolean",flagDeltaArgs:!0,eval(o,e){let t=z("check_flags",o,e);for(let r=1;r<o.length;r++){let s=o[r];if(s.kind!=="flagdelta")throw new f("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(o,e){let t=[...z("set_flags",o,e)];for(let r=1;r<o.length;r++){let s=o[r];if(s.kind!=="flagdelta")throw new f("set_flags() flag args must be +flagName or -flagName");if(s.sign==="+")t.includes(s.name)||t.push(s.name);else{let n=t.indexOf(s.name);n>=0&&t.splice(n,1)}}return t.sort()}},count_played:{minArgs:1,maxArgs:1,returnType:"number",eval(o,e){let t=B("count_played",o,e,0),r=R(e).countPlayed;if(!r)throw new f("count_played() called without a play log in context");return r(t)}},turns_since_played:{minArgs:1,maxArgs:1,returnType:"number",eval(o,e){let t=B("turns_since_played",o,e,0),r=R(e).turnsSincePlayed;if(!r)throw new f("turns_since_played() called without a play log in context");return r(t)}},count_played_in:{minArgs:2,maxArgs:2,returnType:"number",eval(o,e){let t=B("count_played_in",o,e,0),r=B("count_played_in",o,e,1),s=R(e).countPlayedIn;if(!s)throw new f("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(o,e){let t=B("turns_since_played_in",o,e,0),r=B("turns_since_played_in",o,e,1),s=R(e).turnsSincePlayedIn;if(!s)throw new f("turns_since_played_in() called without a play log in context");return s(t,r)}}}};function ue(o){return o.toLowerCase().replace(/['’]/g,"").replace(/[^a-z0-9-]+/g,"-").replace(/-+/g,"-").replace(/^-+|-+$/g,"")}function u(o){let e=o.gameId?.trim();return e||(o.title?ue(o.title):"")||o.id}var v="place";var A=class o{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??K(t))}}get(e){return this.values[this.norm(e)]}set(e,t,r){let s=this.norm(e);if(this.decls.get(s)?.writable===!1)throw new Error(`'${e}' is read-only`);let n={name:s,prev:this.values[s],next:t,silent:r?.silent??!1,reason:r?.reason};this.values[s]=t;for(let i of this.auditors)i(n);if(!n.silent)for(let i of this.subscribers)i(n);return n}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}onAudit(e){return this.auditors.add(e),()=>this.auditors.delete(e)}rows(){return[...this.decls.entries()].map(([e,t])=>ge(t,this.get(e),void 0,e,this.pathPrefix))}declarations(){return[...this.decls.values()]}clone(){let e=new o([],{normalise:this.norm,pathPrefix:this.pathPrefix});return e.decls=new Map(this.decls),Object.assign(e.values,structuredClone(this.values)),e}reseed(e){for(let t of Object.keys(this.values))delete this.values[t];this.decls.clear(),this.seed(e)}save(){return structuredClone(this.values)}load(e){for(let[t,r]of Object.entries(e))this.values[this.norm(t)]=r}};function ge(o,e,t,r,s=""){let n=r??o.name.toLowerCase();return{name:n,path:s+n,type:o.type,value:e,default:o.default??K(o),...o.values!==void 0?{values:o.values}:{},...o.stages!==void 0?{stages:o.stages}:{},writable:t??o.writable??!0}}function K(o){if(o.default!==void 0)return o.default;switch(o.type){case"boolean":return!1;case"number":return 0;case"string":return"";case"enum":return o.values?.[0]??"";case"flags":return[];case"quality":return o.stages?.[0]??"";default:return!1}}var W=(o,e)=>`${o}${e}`,X=(o,e)=>o.shared??e,fe=o=>o.sharedCopies??o.copies??1,I=(o,e)=>new A(o,{normalise:t=>t,pathPrefix:e});function Y(o){return typeof o=="boolean"?o:typeof o=="number"?o!==0:typeof o=="string"?o!=="":o.length>0}var he={story:!0,box:!1,deck:!1,hand:!1,value:!1},Q=(o,e)=>e.shared??he[o],ye=(o,e)=>e.filter(t=>Q(o,t)),M=(o,e)=>e.filter(t=>!Q(o,t)),G=(o,e)=>e.template!==void 0?o.templatesById.get(e.template)?.properties??o.bundle.boxes.flatMap(t=>t.handTemplates).find(t=>t.id===e.template)?.properties??[]:e.properties??[],J=(o,e)=>{let t=o.bundle;return{story:I(e("story",t.story.properties),"story."),box:new Map(t.boxes.map(r=>[r.id,I(e("box",r.properties),`box.${r.id}.`)])),deck:new Map(t.boxes.flatMap(r=>r.decks.map(s=>[s.id,I(e("deck",s.properties),`deck.${s.id}.`)]))),hand:new Map(t.boxes.flatMap(r=>r.hands.map(s=>[s.id,I(e("hand",G(o,s)),`hand.${s.id}.`)]))),value:new Map(t.boxes.flatMap(r=>r.tagGroups.flatMap(s=>s.tags.map(n=>[n.id,I(e("value",n.properties??[]),`value.${n.id}.`)]))))}},Z=o=>({story:o.story.values,box:Object.fromEntries([...o.box].map(([e,t])=>[e,t.values])),deck:Object.fromEntries([...o.deck].map(([e,t])=>[e,t.values])),hand:Object.fromEntries([...o.hand].map(([e,t])=>[e,t.values])),value:Object.fromEntries([...o.value].map(([e,t])=>[e,t.values]))}),ee=(o,e)=>{o.story.load(e?.story??{});for(let t of["box","deck","hand","value"])for(let[r,s]of Object.entries(e?.[t]??{}))o[t].get(r)?.load(s)},H=class{internals;seed;flowsById=new Map;engineTraceHandlers=new Set;hostWorld;constructor(e,t={}){this.seed=t.seed??0,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,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},shared:void 0,worldResolver:void 0,emitEngine:(s,n,i)=>{this.internals.logCap!==void 0&&(this.engineLog.push({...n,flow:s,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 a of this.engineTraceHandlers)a(s,n)},engineTracing:()=>this.engineTraceHandlers.size>0};this.internals=r;for(let s of e.boxes){r.boxesById.set(s.id,s),r.boxesByGameId.set(u(s),s);for(let n of s.tagGroups)r.groupsById.set(n.id,{group:n,box:s}),n.required===!0&&r.requiredGroups.add(n.id);for(let n of s.decks){n.shared===!0&&(r.hasShared=!0);for(let i of n.cards){let a={card:i,deck:n,box:s};r.cardsById.set(i.id,a),r.cardsByGameId.set(u(i),a),i.shared===!0&&(r.hasShared=!0)}}for(let n of s.handTemplates)r.templatesById.set(n.id,n);for(let n of s.hands)r.handsById.set(n.id,{hand:n,box:s}),r.handsByGameId.set(u(n),{hand:n,box:s})}this.initLadders(),r.flowDecls={story:M("story",e.story.properties),box:new Map(e.boxes.map(s=>[s.id,M("box",s.properties)])),deck:new Map(e.boxes.flatMap(s=>s.decks.map(n=>[n.id,M("deck",n.properties)]))),hand:new Map(e.boxes.flatMap(s=>s.hands.map(n=>[n.id,M("hand",G(r,n))]))),value:new Map(e.boxes.flatMap(s=>s.tagGroups.flatMap(n=>n.tags.map(i=>[i.id,M("value",i.properties??[])]))))},this.initShared(this.hostWorld)}initShared(e){let t=this.internals;if(t.shared=J(t,ye),e!==void 0)t.worldResolver=e;else{let r=I(t.bundle.world.properties,"world.");t.worldResolver={get:s=>r.get(s),set:(s,n)=>{r.set(s,n)}}}}initLadders(){let e=this.internals,t=n=>{let i=new Map;for(let a of n??[])a.type==="quality"&&a.stages!==void 0&&i.set(a.name,a.stages);return i},r=e.bundle;e.ladders.world=t(r.world.properties),e.ladders.story=t(r.story.properties);for(let n of r.boxes){e.ladders.box.set(n.id,t(n.properties));for(let i of n.decks)e.ladders.deck.set(i.id,t(i.properties));for(let i of n.tagGroups)for(let a of i.tags)e.ladders.value.set(a.id,t(a.properties));for(let i of n.hands)e.ladders.hand.set(i.id,t(G(e,i)))}let s=n=>[...n.values()].some(i=>i.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={}){this.flowsById.get(e)?.markClosed();let r=new T(this,this.internals,e,t.seed??this.seed);return this.flowsById.set(e,r),r}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}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.worldResolver.set)throw new Error("@world is read-only here: the host bound no write");this.internals.worldResolver.set(r.name,t);return}r.bag.set(r.name,t,{silent:!0,reason:"host setProperty"})}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(n=>n.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],[,n,i]=t,a=this.internals.shared[s].get(n);if(a!==void 0&&a.get(i)!==void 0)return{kind:"bag",bag:a,name:i};throw this.internals.flowDecls[s].get(n)?.some(l=>l.name===i)&&r(),a===void 0&&!this.internals.flowDecls[s].has(n)?new Error(`no ${s} store "${n}"`):new Error(`no property at "${e}"`)}throw new Error(`bad property path "${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.worldResolver.set!==void 0})}let t=(r,s)=>{for(let n of s.rows())e.push(n)};t("story",this.internals.shared.story);for(let[r,s]of this.internals.shared.box)t(`box.${r}`,s);for(let[r,s]of this.internals.shared.deck)t(`deck.${r}`,s);for(let[r,s]of this.internals.shared.hand)t(`hand.${r}`,s);for(let[r,s]of this.internals.shared.value)t(`value.${r}`,s);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:`${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:Z(this.internals.shared),spent:[...this.spent].sort()},flows:Object.fromEntries([...this.flowsById].map(([e,t])=>[e,t.snapshot()]))})}loadGame(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}"`);let t=structuredClone(e);this.reset(),ee(this.internals.shared,t.shared.props);for(let r of t.shared.spent??[])this.spent.add(r);for(let[r,s]of Object.entries(t.flows??{}))this.openFlow(r).restore(s)}},T=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=k(s),this.stores=J(t,M);for(let i of t.bundle.boxes){this.turnCounts.set(i.id,0);for(let a of i.hands)this.boardContents.set(a.id,[])}let n=(i,a)=>({get:l=>i?.get(l)??a?.get(l)});this.storyReader=n(this.stores.story,t.shared.story);for(let i of t.bundle.boxes){this.boxReaders.set(i.id,n(this.stores.box.get(i.id),t.shared.box.get(i.id)));for(let a of i.decks)this.deckReaders.set(a.id,n(this.stores.deck.get(a.id),t.shared.deck.get(a.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=E(e.ast),this.internals.nodeCache.set(e,t)),t}groupInBox(e,t){return e.tagGroups.find(r=>u(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 n of s){let i=W(r,n);this.tagPlayCount.set(i,(this.tagPlayCount.get(i)??0)+1),this.lastPlayInTag.set(i,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,n)=>{let i=this.groupInBox(e,s),a=i?.tags.find(l=>l.gameId===n);return i&&a?W(i.id,a.id):void 0},r=s=>{let n=this.internals.cardsByGameId.get(s.card);return n?(this.turnCounts.get(n.box.id)??0)-s.turn:O};return{nextRandom:()=>this.prng.next(),countPlayed:s=>this.playCount.get(s)??0,turnsSincePlayed:s=>{let n=this.lastPlayOf.get(s);return n?r(n):O},countPlayedIn:(s,n)=>{let i=t(s,n);return i===void 0?0:this.tagPlayCount.get(i)??0},turnsSincePlayedIn:(s,n)=>{let i=t(s,n),a=i===void 0?void 0:this.lastPlayInTag.get(i);return a?r(a):O}}}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,n)=>s==="world"?this.internals.ladders.world.get(n):s==="story"?this.internals.ladders.story.get(n):s==="box"?this.internals.ladders.box.get(e.id)?.get(n):s==="deck"&&t?this.internals.ladders.deck.get(t.id)?.get(n):s==="hand"?this.handLadder(r,n):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 $(this.node(e),t,_)}passes(e,t,r){if(!e)return!0;try{return Y(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={},n;if(e.template!==void 0){let i=this.internals.templatesById.get(e.template);if(!i)throw new Error(`hand "${u(e)}": unknown template "${e.template}"`);for(let[a,l]of Object.entries(i.bindings??{}))r.set(a,l);for(let[a,l]of Object.entries(e.chosen??{})){r.set(a,l);let d=this.internals.groupsById.get(a),p=d?.group.tags.find(c=>c.id===l);d&&p&&(s[u(d.group)]=u(p))}n=i.condition}else{for(let[i,a]of Object.entries(e.rule?.bindings??{})){r.set(i,a);let l=this.internals.groupsById.get(i),d=l?.group.tags.find(p=>p.id===a);l&&d&&(s[u(l.group)]=u(d))}n=e.rule?.condition}return r.set(v,e.id),this.bindStateGroups(t,r,s),{box:t,hand:e,...n!==void 0?{condition:n}:{},boundTags:r,askNames:s}}askForPeek(e,t){let r=new Map,s={};for(let[n,i]of Object.entries(t)){if(n===v){let d=this.internals.handsByGameId.get(i)??this.internals.handsById.get(i);if(!d)throw new Error(`peek: unknown hand "${i}" in home criteria`);r.set(v,d.hand.id);continue}let a=this.groupInBox(e,n);if(!a)throw new Error(`peek: unknown tag group "${n}" in box "${u(e)}"`);let l=this.tagByGameId(a,i)??a.tags.find(d=>d.id===i);if(!l)throw new Error(`peek: unknown tag "${i}" in group "${u(a)}"`);r.set(a.id,l.id),s[u(a)]=u(l)}return this.bindStateGroups(e,r,s),{box:e,boundTags:r,askNames:s}}bindStateGroups(e,t,r){for(let s of e.tagGroups){if(s.boundBy===void 0||t.has(s.id))continue;let n=/^@(world|story)\.([a-z][a-z0-9_-]*)$/.exec(s.boundBy);if(!n){this.emit({type:"diagnostic",where:`tag group ${u(s)}`,message:`boundBy "${s.boundBy}" is not a @world or @story property reference`});continue}let i;try{i=this.getProperty(`${n[1]}.${n[2]}`)}catch{this.emit({type:"diagnostic",where:`tag group ${u(s)}`,message:`boundBy "${s.boundBy}" names a property that is not declared`});continue}let a=typeof i=="string"?i:String(i),l=s.tags.find(d=>u(d)===a);if(!l){this.emit({type:"diagnostic",where:`tag group ${u(s)}`,message:`${s.boundBy} is "${a}", which is not one of its tags`});continue}t.set(s.id,l.id),r[u(s)]=u(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,n]of e.boundTags)if(s!==v)for(let[i,a]of Object.entries(this.valuesOf("value",n)))t[i]=a,r.set(i,{kind:"value",id:n});if(e.hand)for(let[s,n]of Object.entries(this.valuesOf("hand",e.hand.id)))t[s]=n,r.set(s,{kind:"hand",id:e.hand.id});for(let[s,n]of Object.entries(e.askNames))t[s]=n,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 n=e.id;return(r.get(n)??0)>=this.copiesOf(e)?"claimed":t&&(s.get(n)??0)>=fe(e)?"claimed-elsewhere":null}tagsMatch(e,t){let r=e.tags?.[v];if(r!==void 0&&r.length>0){let s=t.get(v);if(s===void 0||!r.includes(s))return!1}for(let[s,n]of t){if(s===v)continue;let i=e.tags?.[s];if(i===void 0){if(this.internals.requiredGroups.has(s))return!1;continue}if(!i.includes(n))return!1}return!0}runAsk(e,t,r){let{box:s}=e,n=this.buildHandEnv(e),i=(c,h)=>{r?.push({id:c,verdict:h})};if(!this.passes(e.condition,this.evalCtx(s,void 0,n),`hand ${e.hand?u(e.hand):""} condition`))return{ordered:[],handEnv:n};let a=new Map;for(let c of s.decks)a.set(c.id,this.passes(c.condition,this.evalCtx(s,c,n),`deck ${c.gameId} gate`));let l=this.turnCounts.get(s.id)??0,d=[];for(let c of s.decks){let h=this.evalCtx(s,c,n),m=c.shared??!1;for(let g of c.cards){let y=X(g,m);if(!a.get(c.id)){i(g.id,"deck-gate");continue}if(y&&this.engine.isTaken(g.id)){i(g.id,"taken");continue}if((this.cooldowns[g.id]??0)>l){i(g.id,"cooldown");continue}if(!this.tagsMatch(g,n.boundTags)){i(g.id,"tags");continue}if(g.condition&&!this.passes(g.condition,h,this.tracing?`card ${g.gameId} condition`:void 0)){i(g.id,"condition");continue}let w=t(g,y);if(w){i(g.id,w);continue}let C;if(typeof g.priority=="number")C=g.priority;else try{let x=this.eval(g.priority,h);if(typeof x!="number"){i(g.id,"priority");continue}C=x}catch(x){this.tracing&&this.emit({type:"diagnostic",where:`card ${g.gameId} priority`,message:x instanceof Error?x.message:String(x)}),i(g.id,"priority");continue}let L=0;if(s.ranking.specificity&&g.condition){let x=this.node(g.condition);L=U(x,re=>{try{return Y($(re,h,_))}catch{return!1}})}d.push({entry:{card:g,deck:c,box:s},priority:C,spec:L})}}d.sort((c,h)=>h.priority-c.priority||h.spec-c.spec);let p=0;for(;p<d.length;){let c=p+1;for(;c<d.length&&d[c].priority===d[p].priority&&d[c].spec===d[p].spec;)c++;if(c-p>1){let h=d.slice(p,c);P(h,this.prng);for(let m=0;m<h.length;m++)d[p+m]=h[m]}p=c}for(let c of d)r?.push({id:c.entry.card.id,verdict:"dealt",priority:c.priority,specificity:c.spec});return{ordered:d.map(c=>c.entry),handEnv:n}}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:u(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 n=this.askForPeek(s,t),i=this.claims(),a=this.internals.hasShared?this.engine.sharedClaims():new Map,l=this.tracing?[]:void 0,{ordered:d}=this.runAsk(n,(c,h)=>this.claimVerdict(c,h,i,a),l),p=r===void 0?d:d.slice(0,Math.max(r,0));return l&&(this.capTrace(l,new Set(p.map(c=>c.card.id))),this.emit({type:"peek",box:u(s),criteria:t,cards:l},this.turnCounts.get(s.id)??0)),{box:u(s),cards:p.map(c=>this.view(c))}}deal(e){this.assertOpen();let{hand:t}=this.resolveHand(e);return this.dealMany([e])[u(t)]??[]}dealMany(e){this.assertOpen();let t=(e??[...this.internals.handsById.keys()].sort()).map(n=>this.resolveHand(n));P(t,this.prng);for(let{hand:n,box:i}of t){let a=this.askForHand(n,i),l=this.buildHandEnv(a),d=this.passes(a.condition,this.evalCtx(i,void 0,l)),p=new Map;for(let y of i.decks)p.set(y.id,this.passes(y.condition,this.evalCtx(i,y,l)));let c=this.turnCounts.get(i.id)??0,h=[],m=(y,w)=>(h.push({card:y,reason:w}),!1),g=(this.boardContents.get(n.id)??[]).filter(y=>{if(!d)return m(y,"hand-condition");let w=this.internals.cardsById.get(y);return w?p.get(w.deck.id)?(this.cooldowns[y]??0)>c?m(y,"cooldown"):this.tagsMatch(w.card,l.boundTags)?this.passes(w.card.condition,this.evalCtx(i,w.deck,l),`card ${w.card.gameId} condition`)?!0:m(y,"condition"):m(y,"tags"):m(y,"deck-gate"):m(y,"vanished")});if(this.boardContents.set(n.id,g),this.tracing)for(let y of h)this.emit({type:"evict",hand:n.id,card:y.card,reason:y.reason},c)}let r=this.claims(),s=this.internals.hasShared?this.engine.sharedClaims():new Map;for(let{hand:n,box:i}of t){let a=this.boardContents.get(n.id)??[],l=this.handCapacity(n)-a.length;if(l<=0)continue;let d=this.askForHand(n,i),p=new Set(a),c=this.tracing?[]:void 0,{ordered:h}=this.runAsk(d,(g,y)=>p.has(g.id)?"claimed":this.claimVerdict(g,y,r,s),c),m=h.slice(0,l).map(g=>g.card.id);this.boardContents.set(n.id,[...a,...m]);for(let g of m)r.set(g,(r.get(g)??0)+1),s.set(g,(s.get(g)??0)+1);c&&(this.capTrace(c,new Set(m)),this.emit({type:"deal",hand:u(n),cards:c},this.turnCounts.get(i.id)??0))}return Object.fromEntries(t.map(({hand:n})=>[u(n),(this.boardContents.get(n.id)??[]).map(i=>this.view(this.internals.cardsById.get(i)))]))}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])=>[u(this.internals.handsById.get(r).hand),s.map(n=>this.view(this.internals.cardsById.get(n)))]))}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:n}=this.resolveHand(t);if(!(this.boardContents.get(s.id)??[]).includes(r.card.id))throw new Error(`card "${u(r.card)}" is not dealt to hand "${u(s)}"`);return{entry:r,ask:this.askForHand(s,n)}}outcomes(e,t){this.assertOpen();let{entry:r,ask:s}=this.resolveDealt(e,t),n=this.evalCtx(r.box,r.deck,this.buildHandEnv(s));return r.card.outcomes.map(i=>({id:i.id,gameId:u(i),...i.title!==void 0?{title:i.title}:{},...i.purpose!==void 0?{purpose:i.purpose}:{},available:this.passes(i.condition,n)}))}play(e,t,r,s={}){this.assertOpen();let{entry:n,ask:i}=this.resolveDealt(e,r),a=n.card.outcomes.find(g=>u(g)===t);if(!a)throw new Error(`card "${u(n.card)}" has no outcome "${t}"`);let l=this.buildHandEnv(i),d=this.evalCtx(n.box,n.deck,l);if(!this.passes(a.condition,d))throw new Error(`outcome "${t}" on "${u(n.card)}" is gated shut`);let p=(this.turnCounts.get(n.box.id)??0)+(s.advanceTurns??this.internals.bundle.settings.playAdvancesTurns),c=[];for(let[g,y]of Object.entries(a.changes))c.push({target:g,value:this.eval(y,d)});for(let{target:g,value:y}of c){let{path:w,prev:C}=this.applyWrite(g,y,n,l);this.tracing&&this.emit({type:"write",target:g,path:w,value:y,...C!==void 0?{prev:C}:{}},p)}let h={card:u(n.card),outcome:u(a),turn:p};this.playLog.push(h),this.indexPlay(h),n.card.redraw==="never"?X(n.card,n.deck.shared??!1)?this.engine.markTaken(n.card.id):this.cooldowns[n.card.id]=Number.MAX_SAFE_INTEGER:typeof n.card.redraw=="number"&&(this.cooldowns[n.card.id]=p+n.card.redraw);let m=i.hand.id;this.boardContents.set(m,(this.boardContents.get(m)??[]).filter(g=>g!==n.card.id)),this.turnCounts.set(n.box.id,p),this.tracing&&this.emit({type:"play",card:n.card.id,outcome:u(a),turn:p},p)}landIn(e,t,r,s,n){let i=e==="story"?this.stores.story:t!==void 0?this.stores[e].get(t):void 0,a=e==="story"?this.internals.shared.story:t!==void 0?this.internals.shared[e].get(t):void 0,l=i!==void 0&&i.get(r)!==void 0?i:a!==void 0&&a.get(r)!==void 0?a:void 0;if(l===void 0)throw new Error(`no property at "${n}"`);let d=l.set(r,s);return{path:n,...d.prev!==void 0?{prev:d.prev}:{}}}applyWrite(e,t,r,s){let n=/^@([a-z]+)\.([A-Za-z_][A-Za-z0-9_-]*)$/.exec(e);if(!n)throw new Error(`bad change target "${e}"`);let[,i,a]=n;switch(i){case"world":{let l=this.internals.worldResolver;if(!l.set)throw new Error(`@world.${a} cannot be written: the host bound @world read-only`);let d=l.get(a);return l.set(a,t),{path:`world.${a}`,...d!==void 0?{prev:d}:{}}}case"story":return this.landIn("story",void 0,a,t,`story.${a}`);case"box":return this.landIn("box",r.box.id,a,t,`box.${r.box.id}.${a}`);case"deck":return this.landIn("deck",r.deck.id,a,t,`deck.${r.deck.id}.${a}`);case"hand":{let l=s.sources.get(a);if(!l)throw new Error(`@hand.${a} is not composed in this ask`);if(l.kind==="criteria")throw new Error(`@hand.${a} is a chosen tag / criteria name and cannot be written`);return this.landIn(l.kind,l.id,a,t,`${l.kind}.${l.id}.${a}`)}default:throw new Error(`bad change target scope "@${i}"`)}}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:u(r),turn:s},s)}listBoxes(){return this.assertOpen(),this.internals.bundle.boxes.map(e=>({id:e.id,gameId:u(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:`${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.worldResolver.set!==void 0})}let t=(r,s,n)=>{for(let i of[s,n])if(i!==void 0)for(let a of i.rows())e.push(a)};t("story",this.internals.shared.story,this.stores.story);for(let r of["box","deck","hand","value"]){let s=new Set([...this.internals.shared[r].keys(),...this.stores[r].keys()]);for(let n of s)t(`${r}.${n}`,this.internals.shared[r].get(n),this.stores[r].get(n))}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.worldResolver.set)throw new Error("@world is read-only here: the host bound no write");this.internals.worldResolver.set(r.name,t);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"})}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=this.stores[r].get(t[1]),n=this.internals.shared[r].get(t[1]);if(s===void 0&&n===void 0)throw new Error(`no ${t[0]} store "${t[1]}"`);return{kind:"bag",...s!==void 0?{own:s}:{},...n!==void 0?{shared:n}:{},name:t[2]}}throw new Error(`bad property path "${e}"`)}snapshot(){return{props:Z(this.stores),turns:Object.fromEntries(this.turnCounts),prng:this.prng.state(),cooldowns:this.cooldowns,board:Object.fromEntries(this.boardContents),playLog:this.playLog}}restore(e){ee(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=k(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 F=o=>o.map(e=>({name:e.name,type:e.type,default:e.default,...e.values!==void 0?{values:e.values}:{},...e.purpose!==void 0?{purpose:e.purpose}:{}})),me=(o,e)=>o.template!==void 0?e.handTemplates.find(t=>t.id===o.template)?.properties??[]:o.properties??[],we=(o,e)=>{if(o.slots!==void 0)return o.slots;let t=o.template!==void 0?e.handTemplates.find(r=>r.id===o.template)?.slots:o.rule?.slots;return t===void 0?"unbounded":t};function te(o){let e=[],t=[],r=[{scope:"world",owner:"",properties:F(o.world.properties)},{scope:"story",owner:"",properties:F(o.story.properties)}],s={boxes:0,decks:0,cards:0,hands:0,templates:0,tagGroups:0};for(let n of o.boxes){let i=u(n),a=n.decks.reduce((d,p)=>d+p.cards.length,0);e.push({gameId:i,...n.title!==void 0?{title:n.title}:{},ranking:{specificity:n.ranking.specificity},tagGroups:n.tagGroups.map(d=>({gameId:u(d),tags:d.tags.map(p=>u(p))})),counts:{decks:n.decks.length,cards:a,hands:n.hands.length,templates:n.handTemplates.length,tagGroups:n.tagGroups.length}}),s.boxes+=1,s.decks+=n.decks.length,s.cards+=a,s.hands+=n.hands.length,s.templates+=n.handTemplates.length,s.tagGroups+=n.tagGroups.length;for(let d of n.hands){let p=d.template!==void 0?n.handTemplates.find(c=>c.id===d.template):void 0;t.push({gameId:u(d),...d.title!==void 0?{title:d.title}:{},box:i,slots:we(d,n),...p!==void 0?{template:u(p)}:{}})}let l=(d,p,c,h)=>{c.length!==0&&r.push({scope:d,owner:p,box:i,...h!==void 0?{group:h}:{},properties:F(c)})};l("box",i,n.properties);for(let d of n.decks)l("deck",u(d),d.properties);for(let d of n.hands)l("hand",u(d),me(d,n));for(let d of n.tagGroups)for(let p of d.tags)l("tag",u(p),p.properties??[],u(d))}return{identity:{schema:o.schema,project:o.content.project,version:o.content.version,hash:o.content.hash,metadata:o.metadata},totals:s,boxes:e,hands:t,properties:r,maps:(o.maps??[]).map(n=>({box:n.box,group:n.group,zones:n.zones.length,backgrounds:n.backgrounds?.length??0}))}}return de(be);})();
2
2
  //# sourceMappingURL=storyletengine.min.js.map