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