@storylet-studio/runtime 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +55 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -8
- package/dist/index.d.ts +18 -8
- package/dist/index.js +55 -18
- package/dist/index.js.map +1 -1
- package/package.json +8 -3
- package/dist/storyletengine.min.js +0 -2
- package/dist/storyletengine.min.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storylet-studio/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Storylet Engine JS reference runtime: the session (peek / deal / play / outcomes / board / save), held to the conformance corpus.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/storylet-studio/storylets.git",
|
|
10
|
+
"directory": "packages/runtime"
|
|
11
|
+
},
|
|
7
12
|
"author": "Ian Thomas",
|
|
8
13
|
"publishConfig": {
|
|
9
14
|
"access": "public"
|
|
@@ -28,9 +33,9 @@
|
|
|
28
33
|
},
|
|
29
34
|
"dependencies": {
|
|
30
35
|
"@storylet-studio/dialect": "^0.1.0",
|
|
31
|
-
"@storylet-studio/model": "
|
|
36
|
+
"@storylet-studio/model": ">=0.1.0 <1.0.0",
|
|
32
37
|
"@wildwinter/expr": "^0.5.0",
|
|
33
38
|
"@wildwinter/expr-specificity": "^0.1.0",
|
|
34
|
-
"@wildwinter/scoperegistry": "^0.
|
|
39
|
+
"@wildwinter/scoperegistry": "^0.5.0"
|
|
35
40
|
}
|
|
36
41
|
}
|
|
@@ -1,2 +0,0 @@
|
|
|
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);})();
|
|
2
|
-
//# sourceMappingURL=storyletengine.min.js.map
|